RosettaCodeData/Task/Mastermind/Julia/mastermind.julia

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()