210 lines
6.7 KiB
Plaintext
210 lines
6.7 KiB
Plaintext
using Gtk, Colors, Cairo, Graphics
|
|
|
|
struct Guess
|
|
code::Vector{Color}
|
|
guess::Vector{Color}
|
|
hint::Vector{Color}
|
|
end
|
|
|
|
function Guess(code, guess)
|
|
len = length(code)
|
|
hints = fill(colorant"gray", len) # gray default
|
|
for (i, g) in enumerate(guess), (j, c) in enumerate(code)
|
|
if g == c
|
|
if i == j
|
|
hints[i] = colorant"black"
|
|
elseif hints[i] != colorant"black"
|
|
hints[i] = colorant"white"
|
|
end
|
|
end
|
|
end
|
|
g = Guess([c for c in code], [c for c in guess], [c for c in hints])
|
|
end
|
|
|
|
tovec(guess) = [x for x in [guess.code; guess.guess; guess.hint]]
|
|
|
|
function mastermindapp()
|
|
allu(s) = length(unique(s)) == length(s)
|
|
ccode(c, n, rok) = while true a = rand(c, n); if rok || allu(a) return a end end
|
|
|
|
numcolors, codelength, maxguesses, allowrep, gameover = 10, 4, 10, false, false
|
|
colors = distinguishable_colors(numcolors)
|
|
code = ccode(colors, numcolors, allowrep)
|
|
guesshistory = Vector{Guess}()
|
|
|
|
win = GtkWindow("Mastermind Game", 900, 750) |> (GtkFrame() |> (box = GtkBox(:v)))
|
|
settingsbox = GtkBox(:h)
|
|
playtoolbar = GtkToolbar()
|
|
|
|
setcolors = GtkScale(false, 4:20)
|
|
set_gtk_property!(setcolors, :hexpand, true)
|
|
adj = GtkAdjustment(setcolors)
|
|
set_gtk_property!(adj, :value, 10)
|
|
clabel = GtkLabel("Number of Colors")
|
|
|
|
setcodelength = GtkScale(false, 4:10)
|
|
set_gtk_property!(setcodelength, :hexpand, true)
|
|
adj = GtkAdjustment(setcodelength)
|
|
set_gtk_property!(adj, :value, 4)
|
|
slabel = GtkLabel("Code Length")
|
|
|
|
setnumguesses = GtkScale(false, 4:40)
|
|
set_gtk_property!(setnumguesses, :hexpand, true)
|
|
adj = GtkAdjustment(setnumguesses)
|
|
set_gtk_property!(adj, :value, 10)
|
|
nlabel = GtkLabel("Max Guesses")
|
|
|
|
allowrepeatcolor = GtkScale(false, 0:1)
|
|
set_gtk_property!(allowrepeatcolor, :hexpand, true)
|
|
rlabel = GtkLabel("Allow Repeated Colors (0 = No)")
|
|
|
|
newgame = GtkToolButton("New Game")
|
|
set_gtk_property!(newgame, :label, "New Game")
|
|
set_gtk_property!(newgame, :is_important, true)
|
|
|
|
tryguess = GtkToolButton("Submit Current Guess")
|
|
set_gtk_property!(tryguess, :label, "Submit Current Guess")
|
|
set_gtk_property!(tryguess, :is_important, true)
|
|
|
|
eraselast = GtkToolButton("Erase Last (Unsubmitted) Pick")
|
|
set_gtk_property!(eraselast, :label, "Erase Last (Unsubmitted) Pick")
|
|
set_gtk_property!(eraselast, :is_important, true)
|
|
|
|
map(w->push!(settingsbox, w),[clabel, setcolors, slabel, setcodelength,
|
|
nlabel, setnumguesses, rlabel, allowrepeatcolor])
|
|
map(w->push!(playtoolbar, w),[newgame, tryguess, eraselast])
|
|
|
|
scrwin = GtkScrolledWindow()
|
|
can = GtkCanvas()
|
|
set_gtk_property!(can, :expand, true)
|
|
map(w -> push!(box, w),[settingsbox, playtoolbar, scrwin])
|
|
push!(scrwin, can)
|
|
|
|
currentguess = RGB[]
|
|
guessesused = 0
|
|
colorpositions = Point[]
|
|
|
|
function newgame!(w)
|
|
empty!(guesshistory)
|
|
|
|
numcolors = Int(GAccessor.value(setcolors))
|
|
codelength = Int(GAccessor.value(setcodelength))
|
|
maxguesses = Int(GAccessor.value(setnumguesses))
|
|
allowrep = Int(GAccessor.value(allowrepeatcolor))
|
|
|
|
colors = distinguishable_colors(numcolors)
|
|
code = ccode(colors, codelength, allowrep == 1)
|
|
empty!(currentguess)
|
|
currentneeded = codelength
|
|
guessesused = 0
|
|
gameover = false
|
|
draw(can)
|
|
end
|
|
signal_connect(newgame!, newgame, :clicked)
|
|
|
|
function saywon!()
|
|
warn_dialog("You have WON the game!", win)
|
|
gameover = true
|
|
end
|
|
|
|
function outofguesses!()
|
|
warn_dialog("You have Run out of moves! Game over.", win)
|
|
gameover = true
|
|
end
|
|
|
|
can.mouse.button1press = @guarded (widget, event) -> begin
|
|
if !gameover && (i = findfirst(p ->
|
|
sqrt((p.x - event.x)^2 + (p.y - event.y)^2) < 20,
|
|
colorpositions)) != nothing
|
|
if length(currentguess) < codelength
|
|
if allowrep == 0 && !allu(currentguess)
|
|
warn_dialog("Please erase the duplicate color.", win)
|
|
else
|
|
push!(currentguess, colors[i])
|
|
draw(can)
|
|
end
|
|
else
|
|
warn_dialog("You need to submit this guess if ready.", win)
|
|
end
|
|
end
|
|
end
|
|
|
|
@guarded draw(can) do widget
|
|
ctx = Gtk.getgc(can)
|
|
select_font_face(ctx, "Courier", Cairo.FONT_SLANT_NORMAL, Cairo.FONT_WEIGHT_BOLD)
|
|
fontpointsize = 12
|
|
set_font_size(ctx, fontpointsize)
|
|
workcolor = colorant"black"
|
|
set_source(ctx, workcolor)
|
|
move_to(ctx, 0, fontpointsize)
|
|
show_text(ctx, "Color options: " * "-"^70)
|
|
stroke(ctx)
|
|
empty!(colorpositions)
|
|
for i in 1:numcolors
|
|
set_source(ctx, colors[i])
|
|
circle(ctx, i * 40, 40, 20)
|
|
push!(colorpositions, Point(i * 40, 40))
|
|
fill(ctx)
|
|
end
|
|
set_gtk_property!(can, :expand, false) # kludge for good text overwriting
|
|
move_to(ctx, 0, 80)
|
|
set_source(ctx, workcolor)
|
|
show_text(ctx, string(maxguesses - guessesused, pad = 2) * " moves remaining.")
|
|
stroke(ctx)
|
|
set_gtk_property!(can, :expand, true)
|
|
for i in 1:codelength
|
|
set_source(ctx, i > length(currentguess) ? colorant"lightgray" : currentguess[i])
|
|
circle(ctx, i * 40, 110, 20)
|
|
fill(ctx)
|
|
end
|
|
if length(guesshistory) > 0
|
|
move_to(ctx, 0, 155)
|
|
set_source(ctx, workcolor)
|
|
show_text(ctx, "Past Guesses: " * "-"^70)
|
|
for (i, g) in enumerate(guesshistory), (j, c) in enumerate(tovec(g)[codelength+1:end])
|
|
x = j * 40 + (j > codelength ? 20 : 0)
|
|
y = 150 + 40 * i
|
|
set_source(ctx, c)
|
|
circle(ctx, x, y, 20)
|
|
fill(ctx)
|
|
end
|
|
end
|
|
Gtk.showall(win)
|
|
end
|
|
|
|
function submitguess!(w)
|
|
if length(currentguess) == length(code)
|
|
g = Guess(code, currentguess)
|
|
push!(guesshistory, g)
|
|
empty!(currentguess)
|
|
guessesused += 1
|
|
draw(can)
|
|
if all(i -> g.code[i] == g.guess[i], 1:length(code))
|
|
saywon!()
|
|
elseif guessesused > maxguesses
|
|
outofguesses!()
|
|
end
|
|
end
|
|
end
|
|
signal_connect(submitguess!, tryguess, :clicked)
|
|
|
|
function undolast!(w)
|
|
if length(currentguess) > 0
|
|
pop!(currentguess)
|
|
draw(can)
|
|
end
|
|
end
|
|
signal_connect(undolast!, eraselast, :clicked)
|
|
|
|
newgame!(win)
|
|
Gtk.showall(win)
|
|
|
|
condition = Condition()
|
|
endit(w) = notify(condition)
|
|
signal_connect(endit, win, :destroy)
|
|
showall(win)
|
|
wait(condition)
|
|
end
|
|
|
|
mastermindapp()
|