Black box: Difference between revisions
Thundergnat (talk | contribs) (alphabetize, minor clean-up) |
(julia example) |
||
Line 954:
Score = 14 Guesses = 1 Status = In play
</pre>
=={{header|Julia}}==
Gtk library GUI version.
<lang julia>using Colors, Cairo, Graphics, Gtk
struct BoxPosition
x::Int
y::Int
BoxPosition(i = 0, j = 0) = new(i, j)
end
@enum TrialResult Miss Hit Reflect Detour
struct TrialBeam
entry::BoxPosition
exit::Union{BoxPosition, Nothing}
result::TrialResult
end
function blackboxapp(boxlength=8, boxwidth=8, numballs=4)
r, turncount, guesses = 20, 0, BoxPosition[]
showballs, guessing, boxoffsetx, boxoffsety = false, false, r, r
boxes = fill(colorant"wheat", boxlength + 4, boxwidth + 4)
beamhistory, ballpositions = Vector{TrialBeam}(), Vector{BoxPosition}()
win = GtkWindow("Black Box Game", 420, 800) |> (GtkFrame() |> (box = GtkBox(:v)))
settingsbox = GtkBox(:v)
playtoolbar = GtkToolbar()
newgame = GtkToolButton("New Game")
set_gtk_property!(newgame, :label, "New Game")
set_gtk_property!(newgame, :is_important, true)
tryguess = GtkToolButton("Guess")
set_gtk_property!(tryguess, :label, "Create a Guess")
set_gtk_property!(tryguess, :is_important, true)
cancelguess = GtkToolButton("Cancel Guess")
set_gtk_property!(cancelguess, :label, "Cancel Guess")
set_gtk_property!(cancelguess, :is_important, true)
reveal = GtkToolButton("Reveal")
set_gtk_property!(reveal, :label, "Reveal Box")
set_gtk_property!(reveal, :is_important, true)
map(w->push!(playtoolbar, w),[newgame, tryguess, cancelguess, reveal])
scrwin = GtkScrolledWindow()
can = GtkCanvas()
set_gtk_property!(can, :expand, true)
map(w -> push!(box, w),[settingsbox, playtoolbar, scrwin])
push!(scrwin, can)
function newgame!(w)
empty!(ballpositions)
empty!(guesses)
empty!(beamhistory)
guessing, showballs = false, false
fill!(boxes, colorant"wheat")
boxes[2, 3:end-2] .= boxes[end-1, 3:end-2] .= colorant"red"
boxes[3:end-2, 2] .= boxes[3:end-2, end-1] .= colorant"red"
boxes[3:end-2, 3:end-2] .= colorant"black"
while length(ballpositions) < numballs
p = BoxPosition(rand(3:boxlength+2), rand(3:boxwidth+2))
if !(p in ballpositions)
push!(ballpositions, p)
end
end
draw(can)
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)
# print black box graphic
for i in 1:boxlength + 4, j in 1:boxwidth + 4
set_source(ctx, boxes[i, j])
move_to(ctx, boxoffsetx + i * r, boxoffsety + j * r)
rectangle(ctx, boxoffsetx + i * r, boxoffsety + j * r, r, r)
fill(ctx)
# show ball placements if reveal -> showballs
if showballs && BoxPosition(i, j) in ballpositions
set_source(ctx, colorant"green")
circle(ctx, boxoffsetx + (i + 0.5) * r , boxoffsety + (j + 0.5) * r, 0.4 * r)
fill(ctx)
end
# show current guesses if guessing
if guessing && BoxPosition(i, j) in guesses
set_source(ctx, colorant"red")
move_to(ctx, boxoffsetx + i * r + 2, boxoffsety + j * r + fontpointsize)
show_text(ctx, "?")
stroke(ctx)
end
end
# draw dividing lines
set_line_width(ctx, 2)
set_source(ctx, colorant"wheat")
for i in 4:boxlength + 2
move_to(ctx, boxoffsetx + i * r, boxoffsety + 3 * r)
line_to(ctx, boxoffsetx + i * r, boxoffsety + (boxlength + 3) * r)
stroke(ctx)
end
for j in 4:boxwidth + 2
move_to(ctx, boxoffsetx + 3 * r, boxoffsety + j * r)
line_to(ctx, boxoffsetx + (boxlength + 3) * r, boxoffsety + j * r)
stroke(ctx)
end
# show latest trial beams and results and trial history
set_source(ctx, colorant"white")
rectangle(ctx, 0, 320, 400, 420)
fill(ctx)
set_source(ctx, colorant"black")
move_to(ctx, 0, 320)
show_text(ctx, " Test Beam History")
stroke(ctx)
move_to(ctx, 0, 320 + fontpointsize * 1.5)
show_text(ctx, "# Start Result End")
stroke(ctx)
for (i, p) in enumerate(beamhistory)
move_to(ctx, 0, 320 + fontpointsize * (i + 1.5))
set_source(ctx, colorant"black")
s = rpad(i, 3) * rpad("($(p.entry.x - 2),$(p.entry.y - 2))", 8) * rpad(p.result, 12) *
(p.exit == nothing ? " " : "($(p.exit.x - 2), $(p.exit.y - 2))")
show_text(ctx, s)
stroke(ctx)
move_to(ctx, graphicxyfrombox(p.entry, 0.5 * fontpointsize)...)
set_source(ctx, colorant"yellow")
show_text(ctx, string(i))
stroke(ctx)
if p.exit != nothing
move_to(ctx, graphicxyfrombox(p.exit, 0.5 * fontpointsize)...)
set_source(ctx, colorant"lightblue")
show_text(ctx, string(i))
stroke(ctx)
end
end
Gtk.showall(win)
end
tryguess!(w) = if !guessing empty!(guesses); guessing = true end
cancelguess() = (empty!(guesses); guessing = false)
reveal!(w) = (showballs = true; draw(can); Gtk.showall(win))
boxfromgraphicxy(x, y) = Int(round(x / r - 1.5)), Int(round(y / r - 1.5))
graphicxyfrombox(p, oset) = boxoffsetx + p.x * r + oset/2, boxoffsety + p.y * r + oset * 2
dirnext(x, y, dir) = x + dir[1], y + dir[2]
rightward(d) = (-d[2], d[1])
leftward(d) = (d[2], -d[1])
rearward(direction) = (-direction[1], -direction[2])
ballfront(x, y, d) = BoxPosition(x + d[1], y + d[2]) in ballpositions
ballright(x, y, d) = BoxPosition((dirnext(x, y, d) .+ rightward(d))...) in ballpositions
ballleft(x, y, d) = BoxPosition((dirnext(x, y, d) .+ leftward(d))...) in ballpositions
twocorners(x, y, d) = !ballfront(x, y, d) && ballright(x, y, d) && ballleft(x, y, d)
enteringstartzone(x, y, direction) = atstart(dirnext(x, y, direction)...)
function atstart(x, y)
return ((x == 2 || x == boxlength + 3) && (2 < y <= boxwidth + 3)) ||
((y == 2 || y == boxwidth + 3) && (2 < x <= boxlength + 3))
end
function runpath(x, y)
startp = BoxPosition(x, y)
direction = (x == 2) ? (1, 0) : (x == boxlength + 3) ? (-1, 0) :
(y == 2) ? (0, 1) : (0, -1)
while true
if ballfront(x, y, direction)
return Hit, nothing
elseif twocorners(x, y, direction)
if atstart(x, y)
return Reflect, startp
end
direction = rearward(direction)
continue
elseif ballleft(x, y, direction)
if atstart(x, y)
return Reflect, startp
end
direction = rightward(direction)
continue
elseif ballright(x, y, direction)
if atstart(x, y)
return Reflect, startp
end
direction = leftward(direction)
continue
elseif enteringstartzone(x, y, direction)
x2, y2 = dirnext(x, y, direction)
endp = BoxPosition(x2, y2)
if x2 == startp.x && y2 == startp.y
return Reflect, endp
else
if startp.x == x2 || startp.y == y2
return Miss, endp
else
return Detour, endp
end
end
end
x, y = dirnext(x, y, direction)
@assert((2 < x < boxlength + 3) && (2 < y < boxwidth + 3))
end
end
can.mouse.button1press = @guarded (widget, event) -> begin
x, y = boxfromgraphicxy(event.x, event.y)
if guessing
# get click on area of guess
if 2 < x < boxlength + 3 && 2 < y < boxwidth + 3
push!(guesses, BoxPosition(x, y))
if length(guesses) >= numballs
if sort(map(p -> [p.x, p.y], guesses)) ==
sort(map(p -> [p.x, p.y], ballpositions))
warn_dialog("You have WON the game!.", win)
else
warn_dialog("You have made at least one bad guess!", win)
cancelguess()
end
end
draw(can)
end
else
# test beam
if atstart(x, y)
result, endpoint = runpath(x, y)
push!(beamhistory, TrialBeam(BoxPosition(x, y), endpoint, result))
if length(beamhistory) > 32
popfirst!(beamhistory)
end
draw(can)
end
end
end
signal_connect(newgame!, newgame, :clicked)
signal_connect(tryguess!, tryguess, :clicked)
signal_connect(reveal!, reveal, :clicked)
newgame!(win)
show(can)
Gtk.showall(win)
condition = Condition()
endit(w) = notify(condition)
signal_connect(endit, win, :destroy)
Gtk.showall(win)
wait(condition)
end
blackboxapp()
</lang>
|
Revision as of 19:56, 22 March 2020
Implement a version of the Black Box game beginners configuration: 4 Atoms in an 8 x 8 grid.
Determine where the hidden atoms are in the box, by observing how the light beams fired into the box react when leaving it.
Possible results:
'H': the beam hit an atom and stopped
'R': Either the beam was reflected back the way it came or there was a ball just to one side of its entry point
'Numbers': indicate that the beam entered one of those squares and emerged from the other
Extra credit (Different game types):
-More or less atoms (maybe random)
-Different grid sizes
Go
Terminal based game.
Just the basic configuration - 4 atoms in an 8 x 8 grid.
To test it against known output (as opposed to playing a sensible game), the program has been fixed (wikiGame = true) to reproduce the atom position in the Wikipedia article's example game, followed by a complete set of beams and one incorrect and three correct guesses.
Set wikiGame to false to play a normal 'random' game. <lang go>package main
import (
"bufio" "fmt" "log" "math/rand" "os" "strings" "time"
)
var (
b = make([]rune, 100) // displayed board h = make([]rune, 100) // hidden atoms scanner = bufio.NewScanner(os.Stdin) wikiGame = true // set to false for a 'random' game
)
func initialize() {
for i := 0; i < 100; i++ { b[i] = ' ' h[i] = 'F' } if !wikiGame { hideAtoms() } else { h[32] = 'T' h[37] = 'T' h[64] = 'T' h[87] = 'T' } fmt.Println(` === BLACK BOX ===
H Hit (scores 1) R Reflection (scores 1) 1-9, Detour (scores 2) a-c Detour for 10-12 (scores 2) G Guess (maximum 4) Y Correct guess N Incorrect guess (scores 5) A Unguessed atom Cells are numbered a0 to j9. Corner cells do nothing. Use edge cells to fire beam. Use middle cells to add/delete a guess. Game ends automatically after 4 guesses. Enter q to abort game at any time. `)
}
func drawGrid(score, guesses int) {
fmt.Printf(" 0 1 2 3 4 5 6 7 8 9 \n") fmt.Printf("\n") fmt.Printf(" ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗\n") fmt.Printf("a %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c\n", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9]) fmt.Printf(" ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗\n") fmt.Printf("b ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║\n", b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]) fmt.Printf(" ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣\n") fmt.Printf("c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║\n", b[20], b[21], b[22], b[23], b[24], b[25], b[26], b[27], b[28], b[29]) fmt.Printf(" ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣\n") fmt.Printf("d ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║\n", b[30], b[31], b[32], b[33], b[34], b[35], b[36], b[37], b[38], b[39]) fmt.Printf(" ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣\n") fmt.Printf("e ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║\n", b[40], b[41], b[42], b[43], b[44], b[45], b[46], b[47], b[48], b[49]) fmt.Printf(" ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣\n") fmt.Printf("f ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║\n", b[50], b[51], b[52], b[53], b[54], b[55], b[56], b[57], b[58], b[59]) fmt.Printf(" ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣\n") fmt.Printf("g ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║\n", b[60], b[61], b[62], b[63], b[64], b[65], b[66], b[67], b[68], b[69]) fmt.Printf(" ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣\n") fmt.Printf("h ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║\n", b[70], b[71], b[72], b[73], b[74], b[75], b[76], b[77], b[78], b[79]) fmt.Printf(" ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣\n") fmt.Printf("i ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║\n", b[80], b[81], b[82], b[83], b[84], b[85], b[86], b[87], b[88], b[89]) fmt.Printf(" ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝\n") fmt.Printf("j %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c ║ %c\n", b[90], b[91], b[92], b[93], b[94], b[95], b[96], b[97], b[98], b[99]) fmt.Printf(" ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝\n") status := "In play" if guesses == 4 { status = "Game over!" } fmt.Println("\n Score =", score, "\tGuesses =", guesses, "\t Status =", status, "\n")
}
func hideAtoms() {
placed := 0 for placed < 4 { a := 11 + rand.Intn(78) // 11 to 88 inclusive m := a % 10 if m == 0 || m == 9 || h[a] == 'T' { continue } h[a] = 'T' placed++ }
}
func nextCell() int {
var ix int for { fmt.Print(" Choose cell : ") scanner.Scan() sq := strings.ToLower(scanner.Text()) if len(sq) == 1 && sq[0] == 'q' { log.Fatal("program aborted") } if len(sq) != 2 || sq[0] < 'a' || sq[0] > 'j' || sq[1] < '0' || sq[1] > '9' { continue } ix = int((sq[0]-'a')*10 + sq[1] - 48) if atCorner(ix) { continue } break } check(scanner.Err()) fmt.Println() return ix
}
func atCorner(ix int) bool { return ix == 0 || ix == 9 || ix == 90 || ix == 99 }
func inRange(ix int) bool { return ix >= 1 && ix <= 98 && ix != 9 && ix != 90 }
func atTop(ix int) bool { return ix >= 1 && ix <= 8 }
func atBottom(ix int) bool { return ix >= 91 && ix <= 98 }
func atLeft(ix int) bool { return inRange(ix) && ix%10 == 0 }
func atRight(ix int) bool { return inRange(ix) && ix%10 == 9 }
func inMiddle(ix int) bool {
return inRange(ix) && !atTop(ix) && !atBottom(ix) && !atLeft(ix) && !atRight(ix)
}
func play() {
score, guesses := 0, 0 num := '0'
outer:
for { drawGrid(score, guesses) ix := nextCell() if !inMiddle(ix) && b[ix] != ' ' { // already processed continue } var inc, def int switch { case atTop(ix): inc, def = 10, 1 case atBottom(ix): inc, def = -10, 1 case atLeft(ix): inc, def = 1, 10 case atRight(ix): inc, def = -1, 10 default: if b[ix] != 'G' { b[ix] = 'G' guesses++ if guesses == 4 { break outer } } else { b[ix] = ' ' guesses-- } continue } var x int first := true for x = ix + inc; inMiddle(x); x += inc { if h[x] == 'T' { // hit b[ix] = 'H' score++ first = false continue outer } if first && (inMiddle(x+def) && h[x+def] == 'T') || (inMiddle(x-def) && h[x-def] == 'T') { // reflection b[ix] = 'R' score++ first = false continue outer } first = false y := x + inc - def if inMiddle(y) && h[y] == 'T' { // deflection switch inc { case 1, -1: inc, def = 10, 1 case 10, -10: inc, def = 1, 10 } } y = x + inc + def if inMiddle(y) && h[y] == 'T' { // deflection or double deflection switch inc { case 1, -1: inc, def = -10, 1 case 10, -10: inc, def = -1, 10 } } } if num != '9' { num++ } else { num = 'a' } if b[ix] == ' ' { score++ } b[ix] = num if inRange(x) { if ix == x { b[ix] = 'R' } else { if b[x] == ' ' { score++ } b[x] = num } } } drawGrid(score, guesses) finalScore(score, guesses)
}
func check(err error) {
if err != nil { log.Fatal(err) }
}
func finalScore(score, guesses int) {
for i := 11; i <= 88; i++ { m := i % 10 if m == 0 || m == 9 { continue } if b[i] == 'G' && h[i] == 'T' { b[i] = 'Y' } else if b[i] == 'G' && h[i] == 'F' { b[i] = 'N' score += 5 } else if b[i] == ' ' && h[i] == 'T' { b[i] = 'A' } } drawGrid(score, guesses)
}
func main() {
rand.Seed(time.Now().UnixNano()) for { initialize() play() inner: for { fmt.Print(" Play again y/n : ") scanner.Scan() yn := strings.ToLower(scanner.Text()) switch yn { case "n": return case "y": break inner } } check(scanner.Err()) }
}</lang>
- Output:
As the grid is displayed 29 times in all, this has been abbreviated to show just the first 2 and the last 3.
=== BLACK BOX === H Hit (scores 1) R Reflection (scores 1) 1-9, Detour (scores 2) a-c Detour for 10-12 (scores 2) G Guess (maximum 4) Y Correct guess N Incorrect guess (scores 5) A Unguessed atom Cells are numbered a0 to j9. Corner cells do nothing. Use edge cells to fire beam. Use middle cells to add/delete a guess. Game ends automatically after 4 guesses. Enter q to abort game at any time. 0 1 2 3 4 5 6 7 8 9 ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗ a ║ ║ ║ ║ ║ ║ ║ ║ ║ ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗ b ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ c ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ d ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ e ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ f ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ g ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ h ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ i ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝ j ║ ║ ║ ║ ║ ║ ║ ║ ║ ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝ Score = 0 Guesses = 0 Status = In play Choose cell : b0 0 1 2 3 4 5 6 7 8 9 ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗ a ║ ║ ║ ║ ║ ║ ║ ║ ║ ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗ b ║ 1 ║ ║ ║ ║ ║ ║ ║ ║ ║ 1 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ c ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ d ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ e ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ f ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ g ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ h ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ i ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝ j ║ ║ ║ ║ ║ ║ ║ ║ ║ ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝ Score = 2 Guesses = 0 Status = In play Choose cell : c0 ................ (Screens 3 to 26 omitted) ................ Score = 32 Guesses = 2 Status = In play Choose cell : g4 0 1 2 3 4 5 6 7 8 9 ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗ a ║ 2 ║ H ║ 9 ║ H ║ 7 ║ 9 ║ H ║ 8 ║ ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗ b ║ 1 ║ ║ ║ ║ ║ ║ ║ ║ ║ 1 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ c ║ 2 ║ ║ ║ ║ ║ ║ ║ ║ ║ 8 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ d ║ H ║ G ║ ║ ║ ║ ║ ║ G ║ ║ H ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ e ║ 3 ║ ║ ║ ║ ║ ║ ║ ║ ║ 6 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ f ║ 4 ║ ║ ║ ║ ║ ║ ║ ║ ║ 7 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ g ║ H ║ ║ ║ ║ G ║ ║ ║ ║ ║ H ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ h ║ 5 ║ ║ ║ ║ ║ ║ ║ ║ ║ 6 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ i ║ H ║ ║ ║ ║ ║ ║ ║ ║ ║ H ║ ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝ j ║ 3 ║ H ║ 5 ║ H ║ 4 ║ R ║ H ║ R ║ ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝ Score = 32 Guesses = 3 Status = In play Choose cell : i7 0 1 2 3 4 5 6 7 8 9 ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗ a ║ 2 ║ H ║ 9 ║ H ║ 7 ║ 9 ║ H ║ 8 ║ ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗ b ║ 1 ║ ║ ║ ║ ║ ║ ║ ║ ║ 1 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ c ║ 2 ║ ║ ║ ║ ║ ║ ║ ║ ║ 8 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ d ║ H ║ G ║ ║ ║ ║ ║ ║ G ║ ║ H ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ e ║ 3 ║ ║ ║ ║ ║ ║ ║ ║ ║ 6 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ f ║ 4 ║ ║ ║ ║ ║ ║ ║ ║ ║ 7 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ g ║ H ║ ║ ║ ║ G ║ ║ ║ ║ ║ H ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ h ║ 5 ║ ║ ║ ║ ║ ║ ║ ║ ║ 6 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ i ║ H ║ ║ ║ ║ ║ ║ ║ G ║ ║ H ║ ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝ j ║ 3 ║ H ║ 5 ║ H ║ 4 ║ R ║ H ║ R ║ ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝ Score = 32 Guesses = 4 Status = Game over! 0 1 2 3 4 5 6 7 8 9 ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗ a ║ 2 ║ H ║ 9 ║ H ║ 7 ║ 9 ║ H ║ 8 ║ ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗ b ║ 1 ║ ║ ║ ║ ║ ║ ║ ║ ║ 1 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ c ║ 2 ║ ║ ║ ║ ║ ║ ║ ║ ║ 8 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ d ║ H ║ N ║ A ║ ║ ║ ║ ║ Y ║ ║ H ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ e ║ 3 ║ ║ ║ ║ ║ ║ ║ ║ ║ 6 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ f ║ 4 ║ ║ ║ ║ ║ ║ ║ ║ ║ 7 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ g ║ H ║ ║ ║ ║ Y ║ ║ ║ ║ ║ H ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ h ║ 5 ║ ║ ║ ║ ║ ║ ║ ║ ║ 6 ║ ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣ i ║ H ║ ║ ║ ║ ║ ║ ║ Y ║ ║ H ║ ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝ j ║ 3 ║ H ║ 5 ║ H ║ 4 ║ R ║ H ║ R ║ ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝ Score = 37 Guesses = 4 Status = Game over! Play again y/n : n
JavaScript
Play it here. <lang javascript> var sel, again, check, score, done, atoms, guesses, beamCnt, brdSize;
function updateScore( s ) {
score += s || 0; para.innerHTML = "Score: " + score;
} function checkIt() {
check.className = "hide"; again.className = "again"; done = true; var b, id; for( var j = 0; j < brdSize; j++ ) { for( var i = 0; i < brdSize; i++ ) { if( board[i][j].H ) { b = document.getElementById( "atom" + ( i + j * brdSize ) ); b.innerHTML = "⚈"; if( board[i][j].T ) { b.style.color = "#0a2"; } else { b.style.color = "#f00"; updateScore( 5 ); } } } }
} function isValid( n ) {
return n > -1 && n < brdSize;
} function stepBeam( sx, sy, dx, dy ) {
var s = brdSize - 2 if( dx ) { if( board[sx][sy].H ) return {r:"H", x:sx, y:sy}; if( ( (sx == 1 && dx == 1) || (sx == s && dx == -1) ) && ( ( sy > 0 && board[sx][sy - 1].H ) || ( sy < s && board[sx][sy + 1].H ) ) ) return {r:"R", x:sx, y:sy}; if( isValid( sx + dx ) ) { if( isValid( sy - 1 ) && board[sx + dx][sy - 1].H ) { dx = 0; dy = 1; } if( isValid( sy + 1 ) && board[sx + dx][sy + 1].H ) { dx = 0; dy = -1; } sx += dx; return stepBeam( sx, sy, dx, dy ); } else { return {r:"O", x:sx, y:sy}; } } else { if( board[sx][sy].H ) return {r:"H", x:sx, y:sy}; if( ( (sy == 1 && dy == 1) || (sy == s && dy == -1) ) && ( ( sx > 0 && board[sx - 1][sy].H ) || ( sx < s && board[sx + 1][sy].H ) ) ) return {r:"R", x:sx, y:sy}; if( isValid( sy + dy ) ) { if( isValid( sx - 1 ) && board[sx - 1][sy + dy].H ) { dy = 0; dx = 1; } if( isValid( sx + 1 ) && board[sx + 1][sy + dy].H ) { dy = 0; dx = -1; } sy += dy; return stepBeam( sx, sy, dx, dy ); } else { return {r:"O", x:sx, y:sy}; } }
} function fireBeam( btn ) {
var sx = btn.i, sy = btn.j, dx = 0, dy = 0;
if( sx == 0 || sx == brdSize - 1 ) dx = sx == 0 ? 1 : - 1; else if( sy == 0 || sy == brdSize - 1 ) dy = sy == 0 ? 1 : - 1; var s = stepBeam( sx + dx, sy + dy, dx, dy ); switch( s.r ) { case "H": btn.innerHTML = "H"; updateScore( 1 ); break; case "R": btn.innerHTML = "R"; updateScore( 1 ); break; case "O": if( s.x == sx && s.y == sy ) { btn.innerHTML = "R"; updateScore( 1 ); } else { var b = document.getElementById( "fire" + ( s.x + s.y * brdSize ) ); btn.innerHTML = "" + beamCnt; b.innerHTML = "" + beamCnt; beamCnt++; updateScore( 2 ); } }
} function setAtom( btn ) {
if( done ) return; var b = document.getElementById( "atom" + ( btn.i + btn.j * brdSize ) ); if( board[btn.i][btn.j].T == 0 && guesses < atoms ) { board[btn.i][btn.j].T = 1; guesses++; b.innerHTML = "⚈"; } else if( board[btn.i][btn.j].T == 1 && guesses > 0 ) { board[btn.i][btn.j].T = 0; guesses--; b.innerHTML = " "; } if( guesses == atoms ) check.className = "check"; else check.className = "hide";
} function startGame() {
score = 0; updateScore(); check.className = again.className = "hide"; var e = document.getElementById( "mid" ); if( e.firstChild ) e.removeChild( e.firstChild ); brdSize = sel.value; done = false;
if( brdSize < 5 ) return;
var brd = document.createElement( "div" ); brd.id = "board"; brd.style.height = brd.style.width = 5.2 * brdSize + "vh" e.appendChild( brd ); var b, c, d; for( var j = 0; j < brdSize; j++ ) { for( var i = 0; i < brdSize; i++ ) { b = document.createElement( "button" ); b.i = i; b.j = j; if( j == 0 && i == 0 || j == 0 && i == brdSize - 1 || j == brdSize - 1 && i == 0 || j == brdSize - 1 && i == brdSize - 1 ) { b.className = "corner"; } else { if( j == 0 || j == brdSize - 1 || i == 0 || i == brdSize - 1 ) { b.className = "fire"; b.id = "fire" + ( i + j * brdSize ); } else { b.className = "atom"; b.id = "atom" + ( i + j * brdSize ); } b.addEventListener( "click", function( e ) { if( e.target.className == "fire" && e.target.innerHTML == " " ) fireBeam( e.target ); else if( e.target.className == "atom" ) setAtom( e.target ); }, false ); } b.appendChild( document.createTextNode( " " ) ); brd.appendChild( b ); } }
board = new Array( brdSize ); for( var j = 0; j < brdSize; j++ ) { board[j] = new Array( brdSize ); for( i = 0; i < brdSize; i++ ) { board[j][i] = {H: 0, T: 0}; } }
guesses = 0; beamCnt = 1; atoms = brdSize == 7 ? 3 : brdSize == 10 ? 4 : 4 + Math.floor( Math.random() * 5 );
var s = brdSize - 2, i, j; for( var k = 0; k < atoms; k++ ) { while( true ) { i = 1 + Math.floor( Math.random() * s ); j = 1 + Math.floor( Math.random() * s ); if( board[i][j].H == 0 ) break; } board[i][j].H = 1; }
} function init() {
sel = document.createElement( "select"); sel.options.add( new Option( "5 x 5 [3 atoms]", 7 ) ); sel.options.add( new Option( "8 x 8 [4 atoms]", 10 ) ); sel.options.add( new Option( "10 x 10 [4 - 8 atoms]", 12 ) ); sel.addEventListener( "change", startGame, false ); document.getElementById( "top" ).appendChild( sel ); check = document.createElement( "button" ); check.appendChild( document.createTextNode( "Check it!" ) ); check.className = "hide"; check.addEventListener( "click", checkIt, false ); again = document.createElement( "button" ); again.appendChild( document.createTextNode( "Again" ) ); again.className = "hide"; again.addEventListener( "click", startGame, false ); para = document.createElement( "p" ); para.className = "txt"; var d = document.getElementById( "bot" ); d.appendChild( para ); d.appendChild( check ); d.appendChild( again ); startGame();
} </lang>
zkl
<lang zkl>const ATM="A", F="F", HIT="H", G="G", GN="N", R="R", BLNK=" ", GY="Y";
var
brd,hdn, // displayed board & hidden atoms wikiGame = True; // set to False for a 'random' game
fcn initialize{
brd,hdn = List.createLong(100,BLNK), List.createLong(100,F);
if(not wikiGame) hideAtoms(); else hdn[32] = hdn[37] = hdn[64] = hdn[87] = ATM;
// else hdn[64] = hdn[66] = ATM; // Double deflection case
println(
- <<<"
=== BLACK BOX === H Hit (scores 1) R Reflection (scores 1) 1-9, Detour (scores 2) a-c Detour for 10-12 (scores 2) G Guess (maximum 4) Y Correct guess N Incorrect guess (scores 5) A Unguessed atom Cells are numbered a0 to j9. Corner cells do nothing. Use edge cells to fire beam. Use middle cells to add/delete a guess. Game ends automatically after 4 guesses. Enter q to abort game at any time.\n\n");
- <<<
}
fcn drawGrid(score, guesses){
var [const] vbs="\u2550\u2550\u2550\u256c", bt=(vbs.del(-3,3)), be1=String(" %s",vbs*7,bt,"%s").fmt, b1=be1("\u2554","\u2557"), e1=be1("\u255a","\u255d"),
be2=String(" %s", vbs*9, bt,"%s").fmt, b2=be2("\u2554", "\u2557"), b3=be2("\u256c", "\u256c"), e2=be2("\u255a", "\u255d"),
g1=String("%s%s ","\u2551 %s "*9).fmt, // a brd[0]=brd[90]=" " g2=String("%s ","\u2551 %s "*11).del(-3,3).fmt; // b c d .. i
println(" 0 1 2 3 4 5 6 7 8 9 \n",b1); grid,sep,n := g1, b2, -10; foreach c in (["a".."i"]){ println(grid(c,brd[n+=10,10].xplode())); println((c=="i") and e2 or sep); grid,sep = g2,b3; } println(g1("j",brd[90,10].xplode()), "\n", e1);
status:=(guesses==4) and "Game over!" or "In play"; println("\n Score = ", score, "\tGuesses = ", guesses, "\t Status = ", status);
}
fcn hideAtoms{
n:=4; do{ a,m:=(11).random(89), a % 10; // 11 to 88 inclusive if(m==0 or m==9 or hdn[a]==ATM) continue; hdn[a]=ATM; n-=1; }while(n);
}
fcn nextCell{
while(True){ s,c,n,sz := ask(" Choose cell [a-j][0-9]: ").strip().toLower(), s[0,1], s[1,1], s.len(); if(sz==1 and c=="q") System.exit(); if(not (sz==2 and ("a"<=c<="j") and ("0"<=n<="9"))) continue; ix:=10*(c.toAsc() - 97) + n; // row major, "a"-'a' if(not atCorner(ix)){ println(); return(ix); } }
}
fcn atCorner(ix){ ix==0 or ix==9 or ix==90 or ix==99 } fcn inRange(ix) { (1<=ix<=98) and ix!=9 and ix!=90 } fcn atTop(ix) { 1<=ix<= 8 } fcn atBottom(ix){ 91<=ix<=98 } fcn atLeft(ix) { inRange(ix) and ix%10 ==0 } fcn atRight(ix) { inRange(ix) and ix%10 ==9 } fcn inMiddle(ix){
inRange(ix) and not ( atTop(ix) or atBottom(ix) or atLeft(ix) or atRight(ix) )
} fcn play{
score,guesses,num := 0,0, 0x30; // '0' while(True){ drawGrid(score, guesses); ix:=nextCell(); if(not inMiddle(ix) and brd[ix]!=BLNK) continue; // already processed inc,def := 0,0; if (atTop(ix)) inc,def = 10, 1; else if(atBottom(ix)) inc,def = -10, 1; else if(atLeft(ix)) inc,def = 1, 10; else if(atRight(ix)) inc,def = -1, 10; else{
if(brd[ix]!=G){ brd[ix]=G; if( (guesses+=1) ==4) break(1); // you done }else{ brd[ix]=BLNK; guesses-=1; } continue;
} x,first := ix + inc, True; while(inMiddle(x)){
if(hdn[x]==ATM){ // hit brd[ix]=HIT; score+=1; first=False; continue(2); } if(first and (inMiddle(x + def) and hdn[x + def]==ATM) or (inMiddle(x - def) and hdn[x - def]==ATM)){ // reflection brd[ix]=R; score+=1; first=False; continue(2); } first=False; y:=x + inc - def; if(inMiddle(y) and hdn[y]==ATM){ // deflection switch(inc){ case( 1, -1){ inc, def = 10, 1 } case(10, -10){ inc, def = 1,10 } } } y=x + inc + def; if(inMiddle(y) and hdn[y]==ATM){ // deflection or double deflection switch(inc){ case( 1, -1){ inc, def = -10, 1 } case(10, -10){ inc, def = -1, 10 } } } x+=inc;
}// while inMiddle
if(brd[ix]==BLNK) score+=1; if(num!=0x39) num+=1; else num=97; // '0' & 'a' brd[ix]=num.toChar(); // right back at ya if(inRange(x)){
if(ix==x) brd[ix]=R; else{ if(brd[x]==BLNK) score+=1; brd[x]=num.toChar(); }
} } drawGrid( score, guesses); finalScore(score, guesses);
}
fcn finalScore(score, guesses){ println(hdn.toString(*));
foreach i in ([11..88]){ m:=i%10; if(m==0 or m==9) continue; if(brd[i]==G and hdn[i]==ATM) brd[i]=GY; else if(brd[i]==G and hdn[i]==F){ brd[i]=GN; score+=5; } else if(brd[i]==BLNK and hdn[i]==ATM) brd[i]=ATM; } drawGrid(score, guesses);
}
while(True){
initialize(); play(); while(True){ yn:=ask(" Play again y/n : ").strip().toLower(); if(yn=="n") break(2); else if(yn=="y") break(1); }
}</lang> Showing [results of] most of the Wikipedia actions:
- Output:
=== BLACK BOX === H Hit (scores 1) R Reflection (scores 1) 1-9, Detour (scores 2) a-c Detour for 10-12 (scores 2) G Guess (maximum 4) Y Correct guess N Incorrect guess (scores 5) A Unguessed atom Cells are numbered a0 to j9. Corner cells do nothing. Use edge cells to fire beam. Use middle cells to add/delete a guess. Game ends automatically after 4 guesses. Enter q to abort game at any time. 0 1 2 3 4 5 6 7 8 9 ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗ a ║ ║ ║ ║ ║ ║ ║ ║ ║ ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗ b ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ c ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ d ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ e ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ f ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ g ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ h ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ i ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝ j ║ ║ ║ ║ ║ ║ ║ ║ ║ ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝ Score = 0 Guesses = 0 Status = In play Choose cell [a-j][0-9]: g9 0 1 2 3 4 5 6 7 8 9 ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗ a ║ ║ ║ ║ ║ ║ ║ ║ ║ ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗ b ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ c ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ d ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ e ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ f ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ g ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ H ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ h ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ i ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝ j ║ ║ ║ ║ ║ ║ ║ ║ ║ ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝ Score = 1 Guesses = 0 Status = In play ... Choose cell [a-j][0-9]: f0 0 1 2 3 4 5 6 7 8 9 ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗ a ║ ║ ║ 3 ║ ║ 1 ║ 3 ║ ║ ║ ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗ b ║ 2 ║ ║ ║ ║ ║ ║ ║ ║ ║ 2 ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ c ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ d ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ e ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ 4 ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ f ║ 5 ║ ║ ║ ║ ║ ║ ║ ║ ║ 1 ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ g ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ H ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ h ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ 4 ║ ╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬ i ║ ║ ║ ║ ║ ║ ║ G ║ ║ ║ ║ ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝ j ║ ║ ║ ║ ║ 5 ║ R ║ H ║ R ║ ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝ Score = 14 Guesses = 1 Status = In play
Julia
Gtk library GUI version. <lang julia>using Colors, Cairo, Graphics, Gtk
struct BoxPosition
x::Int y::Int BoxPosition(i = 0, j = 0) = new(i, j)
end
@enum TrialResult Miss Hit Reflect Detour
struct TrialBeam
entry::BoxPosition exit::Union{BoxPosition, Nothing} result::TrialResult
end
function blackboxapp(boxlength=8, boxwidth=8, numballs=4)
r, turncount, guesses = 20, 0, BoxPosition[] showballs, guessing, boxoffsetx, boxoffsety = false, false, r, r boxes = fill(colorant"wheat", boxlength + 4, boxwidth + 4) beamhistory, ballpositions = Vector{TrialBeam}(), Vector{BoxPosition}() win = GtkWindow("Black Box Game", 420, 800) |> (GtkFrame() |> (box = GtkBox(:v))) settingsbox = GtkBox(:v) playtoolbar = GtkToolbar()
newgame = GtkToolButton("New Game") set_gtk_property!(newgame, :label, "New Game") set_gtk_property!(newgame, :is_important, true)
tryguess = GtkToolButton("Guess") set_gtk_property!(tryguess, :label, "Create a Guess") set_gtk_property!(tryguess, :is_important, true)
cancelguess = GtkToolButton("Cancel Guess") set_gtk_property!(cancelguess, :label, "Cancel Guess") set_gtk_property!(cancelguess, :is_important, true)
reveal = GtkToolButton("Reveal") set_gtk_property!(reveal, :label, "Reveal Box") set_gtk_property!(reveal, :is_important, true)
map(w->push!(playtoolbar, w),[newgame, tryguess, cancelguess, reveal])
scrwin = GtkScrolledWindow() can = GtkCanvas() set_gtk_property!(can, :expand, true) map(w -> push!(box, w),[settingsbox, playtoolbar, scrwin]) push!(scrwin, can)
function newgame!(w) empty!(ballpositions) empty!(guesses) empty!(beamhistory) guessing, showballs = false, false fill!(boxes, colorant"wheat") boxes[2, 3:end-2] .= boxes[end-1, 3:end-2] .= colorant"red" boxes[3:end-2, 2] .= boxes[3:end-2, end-1] .= colorant"red" boxes[3:end-2, 3:end-2] .= colorant"black" while length(ballpositions) < numballs p = BoxPosition(rand(3:boxlength+2), rand(3:boxwidth+2)) if !(p in ballpositions) push!(ballpositions, p) end end draw(can) 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) # print black box graphic for i in 1:boxlength + 4, j in 1:boxwidth + 4 set_source(ctx, boxes[i, j]) move_to(ctx, boxoffsetx + i * r, boxoffsety + j * r) rectangle(ctx, boxoffsetx + i * r, boxoffsety + j * r, r, r) fill(ctx) # show ball placements if reveal -> showballs if showballs && BoxPosition(i, j) in ballpositions set_source(ctx, colorant"green") circle(ctx, boxoffsetx + (i + 0.5) * r , boxoffsety + (j + 0.5) * r, 0.4 * r) fill(ctx) end # show current guesses if guessing if guessing && BoxPosition(i, j) in guesses set_source(ctx, colorant"red") move_to(ctx, boxoffsetx + i * r + 2, boxoffsety + j * r + fontpointsize) show_text(ctx, "?") stroke(ctx) end end # draw dividing lines set_line_width(ctx, 2) set_source(ctx, colorant"wheat") for i in 4:boxlength + 2 move_to(ctx, boxoffsetx + i * r, boxoffsety + 3 * r) line_to(ctx, boxoffsetx + i * r, boxoffsety + (boxlength + 3) * r) stroke(ctx) end for j in 4:boxwidth + 2 move_to(ctx, boxoffsetx + 3 * r, boxoffsety + j * r) line_to(ctx, boxoffsetx + (boxlength + 3) * r, boxoffsety + j * r) stroke(ctx) end # show latest trial beams and results and trial history set_source(ctx, colorant"white") rectangle(ctx, 0, 320, 400, 420) fill(ctx) set_source(ctx, colorant"black") move_to(ctx, 0, 320) show_text(ctx, " Test Beam History") stroke(ctx) move_to(ctx, 0, 320 + fontpointsize * 1.5) show_text(ctx, "# Start Result End") stroke(ctx) for (i, p) in enumerate(beamhistory) move_to(ctx, 0, 320 + fontpointsize * (i + 1.5)) set_source(ctx, colorant"black") s = rpad(i, 3) * rpad("($(p.entry.x - 2),$(p.entry.y - 2))", 8) * rpad(p.result, 12) * (p.exit == nothing ? " " : "($(p.exit.x - 2), $(p.exit.y - 2))") show_text(ctx, s) stroke(ctx) move_to(ctx, graphicxyfrombox(p.entry, 0.5 * fontpointsize)...) set_source(ctx, colorant"yellow") show_text(ctx, string(i)) stroke(ctx) if p.exit != nothing move_to(ctx, graphicxyfrombox(p.exit, 0.5 * fontpointsize)...) set_source(ctx, colorant"lightblue") show_text(ctx, string(i)) stroke(ctx) end end Gtk.showall(win) end
tryguess!(w) = if !guessing empty!(guesses); guessing = true end cancelguess() = (empty!(guesses); guessing = false) reveal!(w) = (showballs = true; draw(can); Gtk.showall(win)) boxfromgraphicxy(x, y) = Int(round(x / r - 1.5)), Int(round(y / r - 1.5)) graphicxyfrombox(p, oset) = boxoffsetx + p.x * r + oset/2, boxoffsety + p.y * r + oset * 2 dirnext(x, y, dir) = x + dir[1], y + dir[2] rightward(d) = (-d[2], d[1]) leftward(d) = (d[2], -d[1]) rearward(direction) = (-direction[1], -direction[2]) ballfront(x, y, d) = BoxPosition(x + d[1], y + d[2]) in ballpositions ballright(x, y, d) = BoxPosition((dirnext(x, y, d) .+ rightward(d))...) in ballpositions ballleft(x, y, d) = BoxPosition((dirnext(x, y, d) .+ leftward(d))...) in ballpositions twocorners(x, y, d) = !ballfront(x, y, d) && ballright(x, y, d) && ballleft(x, y, d) enteringstartzone(x, y, direction) = atstart(dirnext(x, y, direction)...)
function atstart(x, y) return ((x == 2 || x == boxlength + 3) && (2 < y <= boxwidth + 3)) || ((y == 2 || y == boxwidth + 3) && (2 < x <= boxlength + 3)) end
function runpath(x, y) startp = BoxPosition(x, y) direction = (x == 2) ? (1, 0) : (x == boxlength + 3) ? (-1, 0) : (y == 2) ? (0, 1) : (0, -1) while true if ballfront(x, y, direction) return Hit, nothing elseif twocorners(x, y, direction) if atstart(x, y) return Reflect, startp end direction = rearward(direction) continue elseif ballleft(x, y, direction) if atstart(x, y) return Reflect, startp end direction = rightward(direction) continue elseif ballright(x, y, direction) if atstart(x, y) return Reflect, startp end direction = leftward(direction) continue elseif enteringstartzone(x, y, direction) x2, y2 = dirnext(x, y, direction) endp = BoxPosition(x2, y2) if x2 == startp.x && y2 == startp.y return Reflect, endp else if startp.x == x2 || startp.y == y2 return Miss, endp else return Detour, endp end end end x, y = dirnext(x, y, direction) @assert((2 < x < boxlength + 3) && (2 < y < boxwidth + 3)) end end
can.mouse.button1press = @guarded (widget, event) -> begin x, y = boxfromgraphicxy(event.x, event.y) if guessing # get click on area of guess if 2 < x < boxlength + 3 && 2 < y < boxwidth + 3 push!(guesses, BoxPosition(x, y)) if length(guesses) >= numballs if sort(map(p -> [p.x, p.y], guesses)) == sort(map(p -> [p.x, p.y], ballpositions)) warn_dialog("You have WON the game!.", win) else warn_dialog("You have made at least one bad guess!", win) cancelguess() end end draw(can) end else # test beam if atstart(x, y) result, endpoint = runpath(x, y) push!(beamhistory, TrialBeam(BoxPosition(x, y), endpoint, result)) if length(beamhistory) > 32 popfirst!(beamhistory) end draw(can) end end end
signal_connect(newgame!, newgame, :clicked) signal_connect(tryguess!, tryguess, :clicked) signal_connect(reveal!, reveal, :clicked)
newgame!(win) show(can) Gtk.showall(win)
condition = Condition() endit(w) = notify(condition) signal_connect(endit, win, :destroy) Gtk.showall(win) wait(condition)
end
blackboxapp() </lang>