Black box

From Rosetta Code
Revision as of 01:43, 24 July 2022 by Thundergnat (talk | contribs) (Thundergnat moved page Black Box to Black box: capitalization policy)
Black box is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

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>


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, guesscount, correctguesses = 20, 0, BoxPosition[], 0, 0
   showballs, boxoffsetx, boxoffsety = false, r, r
   boxes = fill(colorant"wheat", boxlength + 4, boxwidth + 4)
   beamhistory, ballpositions = Vector{TrialBeam}(), Vector{BoxPosition}()
   win = GtkWindow("Black Box Game", 348, 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)
   reveal = GtkToolButton("Reveal")
   set_gtk_property!(reveal, :label, "Reveal Box")
   set_gtk_property!(reveal, :is_important, true)
   map(w->push!(playtoolbar, w),[newgame, 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, guesscount, correctguesses = false, false, 0, 0
       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)
           p = BoxPosition(i, j)
           # show current guesses
           if p in guesses
               set_source(ctx, colorant"red")
               move_to(ctx, boxoffsetx + i * r + 2, boxoffsety + j * r + fontpointsize)
               show_text(ctx, p in ballpositions ? "+" : "-")
               stroke(ctx)
           end
           # show ball placements if reveal -> showballs
           if showballs && p 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
       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 scoring update
       set_source(ctx, colorant"white")
       rectangle(ctx, 0, 305, 400, 50)
       fill(ctx)
       correct, incorrect = string(correctguesses), string(guesscount - correctguesses)
       score = string(2 * correctguesses - guesscount)
       set_source(ctx, colorant"black")
       move_to(ctx, 0, 320)
       show_text(ctx, " Correct: $correct  Incorrect: $incorrect  Score: $score")
       stroke(ctx)
       # show latest trial beams and results and trial history
       set_source(ctx, colorant"white")
       rectangle(ctx, 0, 360, 400, 420)
       fill(ctx)
       set_source(ctx, colorant"black")
       move_to(ctx, 0, 360)
       show_text(ctx, "      Test Beam History")
       stroke(ctx)
       move_to(ctx, 0, 360 + fontpointsize * 1.5)
       show_text(ctx, " #  Start   Result      End")
       stroke(ctx)
       for (i, p) in enumerate(beamhistory)
           move_to(ctx, 0, 360 + 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
   reveal!(w) = (showballs = !showballs; 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)
       # get click in blackbox area as a guess
       if 2 < x < boxlength + 3 && 2 < y < boxwidth + 3
           p = BoxPosition(x, y)
           if !(p in guesses)
               push!(guesses, BoxPosition(x, y))
               guesscount += 1
               if p in ballpositions
                   correctguesses += 1
               end
           end
           draw(can)
       # test beam
       elseif 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
   condition = Condition()
   endit(w) = notify(condition)
   signal_connect(endit, win, :destroy)
   signal_connect(newgame!, newgame, :clicked)
   signal_connect(reveal!, reveal, :clicked)
   newgame!(win)
   Gtk.showall(win)
   wait(condition)

end

blackboxapp() </lang>

Nim

Translation of: Go

<lang Nim>import random, sequtils, strutils

const WikiGame = true

type

 Game = object
   b: array[100, char]   # displayed board.
   h: array[100, char]   # hidden atoms.


proc hideAtoms(game: var Game) =

 var placed = 0
 while placed < 4:
   let a = rand(11..88)
   let m = a mod 10
   if m == 0 or m == 9 or game.h[a] == 'T':
     continue
   game.h[a] = 'T'
   inc placed


proc initGame(): Game =

 for i in 0..99:
   result.b[i] = ' '
   result.h[i] = 'F'
 if not WikiGame:
   result.hideAtoms()
 else:
   result.h[32] = 'T'
   result.h[37] = 'T'
   result.h[64] = 'T'
   result.h[87] = 'T'


proc drawGrid(game: Game; score, guesses: Natural) =

 echo "      0   1   2   3   4   5   6   7   8   9\n"
 echo "        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗"
 echo "a     $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $#".format(game.b[0..9].mapIt($it))
 echo "    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗"
 echo "b   ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║".format(game.b[10..19].mapIt($it))
 echo "    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣"
 echo "c   ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║".format(game.b[20..29].mapIt($it))
 echo "    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣"
 echo "d   ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║".format(game.b[30..39].mapIt($it))
 echo "    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣"
 echo "e   ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║".format(game.b[40..49].mapIt($it))
 echo "    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣"
 echo "f   ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║".format(game.b[50..59].mapIt($it))
 echo "    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣"
 echo "g   ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║".format(game.b[60..69].mapIt($it))
 echo "    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣"
 echo "h   ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║".format(game.b[70..79].mapIt($it))
 echo "    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣"
 echo "i   ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║".format(game.b[80..89].mapIt($it))
 echo "    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝"
 echo "j     $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $# ║ $#".format(game.b[90..99].mapIt($it))
 echo "        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝"
 let status = if guesses == 4: "Game over!" else: "In play"
 echo "\n        Score = ", score, "\tGuesses = ", guesses, "\t Status = ", status, '\n'


proc finalScore(game: var Game; score, guesses: Natural) =

 var score = score
 for i in 11..88:
   let m = i mod 10
   if m in [0, 9]: continue
   if game.b[i] == 'G':
     if game.h[i] == 'T':
       game.b[i] = 'Y'
     else:
       game.b[i] = 'N'
       inc score, 5
   elif game.b[i] == ' ' and game.h[i] == 'T':
     game.b[i] = 'A'
 game.drawGrid(score, guesses)


func atCorner(ix: int): bool = ix in [0, 9, 90, 99]

func inRange(ix: int): bool = ix in 1..98 and ix notin [9, 90]

func atTop(ix: int): bool = ix in 1..8

func atBottom(ix: int): bool = ix in 91..98

func atLeft(ix: int): bool = ix.inRange and ix mod 10 == 0

func atRight(ix: int): bool = ix.inRange and ix mod 10 == 9

func inMiddle(ix: int): bool =

 ix.inRange and not (ix.atTop or ix.atBottom or ix.atLeft or ix.atRight)


proc nextCell(game: Game): int =

 while true:
   stdout.write "    Choose cell: "
   stdout.flushFile()
   try:
     let sq = stdin.readLine().toLowerAscii
     if sq == "q":
       quit "Quitting.", QuitSuccess
     if sq.len != 2 or sq[0] notin 'a'..'j' or sq[1] notin '0'..'9':
       continue
     result = int((ord(sq[0]) - ord('a')) * 10 + ord(sq[1]) - ord('0'))
     if not result.atCorner: break
   except EOFError:
     echo()
     quit "Encountered end of file. Quitting.", QuitFailure
 echo()


proc play(game: var Game) =

 var score, guesses = 0
 var num = '0'
 block outer:
   while true:
     block inner:
       game.drawGrid(score, guesses)
       let ix = game.nextCell()
       if not ix.inMiddle and game.b[ix] != ' ':     # already processed.
         continue
       var incr, def: int
       if ix.atTop:
         (incr, def) = (10, 1)
       elif ix.atBottom:
         (incr, def) = (-10, 1)
       elif ix.atLeft:
         (incr, def) = (1, 10)
       elif ix.atRight:
         (incr, def) = (-1, 10)
       else:
         if game.b[ix] != 'G':
           game.b[ix] = 'G'
           inc guesses
           if guesses == 4: break outer
         else:
           game.b[ix] = ' '
           dec guesses
         continue
       var first = true
       var x = ix + incr
       while x.inMiddle:
         if game.h[x] == 'T':
           # Hit.
           game.b[ix] = 'H'
           inc score
           first = false
           break inner
         if first and (x + def).inMiddle and game.h[x + def] == 'T' or
                      (x - def).inMiddle and game.h[x - def] == 'T':
           # Reflection.
           game.b[ix] = 'R'
           inc score
           first = false
           break inner
         first = false
         var y = x + incr - def
         if y.inMiddle and game.h[y] == 'T':
           # Deflection.
           (incr, def) = if incr in [-1, 1]: (10, 1) else: (1, 10)
         y = x + incr + def
         if y.inMiddle and game.h[y] == 'T':
           # Deflection or double deflection.
           (incr, def) = if incr in [-1, 1]: (-10, 1) else: (-1, 10)
         inc x, incr
       num = if num != '9': succ(num) else: 'a'
       if game.b[ix] == ' ': inc score
       game.b[ix] = num
       if x.inRange:
         if ix == x:
           game.b[ix] = 'R'
         else:
           if game.b[x] == ' ': inc score
           game.b[x] = num
 game.drawGrid(score, guesses)
 game.finalScore(score, guesses)


proc main() =

 randomize()
 while true:
   var game = initGame()
   echo """
   === 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.
   """
   game.play()
   while true:
     stdout.write "    Play again (y/n): "
     stdout.flushFile()
     case stdin.readLine().toLowerAscii()
     of "n": return
     of "y": break

main()</lang>

Output:

Using same input as in Wren entry with WikiGame = true:

    === 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

      0   1   2   3   4   5   6   7   8   9

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 4	Guesses = 0	 Status = In play

    Choose cell: d7

      0   1   2   3   4   5   6   7   8   9

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║   ║   ║   ║   ║   ║ G ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 4	Guesses = 1	 Status = In play

    Choose cell: d4

      0   1   2   3   4   5   6   7   8   9

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║   ║   ║ G ║   ║   ║ G ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 4	Guesses = 2	 Status = In play

    Choose cell: e3

      0   1   2   3   4   5   6   7   8   9

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║   ║   ║ G ║   ║   ║ G ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║ G ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 4	Guesses = 3	 Status = In play

    Choose cell: h2

      0   1   2   3   4   5   6   7   8   9

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║   ║   ║ G ║   ║   ║ G ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║ G ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║ G ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 4	Guesses = 4	 Status = Game over!

      0   1   2   3   4   5   6   7   8   9

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║ A ║   ║ N ║   ║   ║ Y ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║ N ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║ A ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║ N ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║ A ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 19	Guesses = 4	 Status = Game over!

    Play again (y/n): n

Phix

Library: Phix/pGUI

A configurable GUI version of the Black Box game, with a Knuth solver/helper. <lang Phix>-- demo\rosetta\Black_Box.exw constant title = "Black Box", help_text = """ Discover the location of objects/atoms using the fewest probes/rays.

See distributed version for much longer help text and other comments. """ integer size, -- eg 8

       s1, s2,         -- size+1|2
       count,          -- eg 4
       mask            -- eg #0b100000 (first such >size^2-count+1)
                       -- Note that new_game() contains limiting code.

sequence gameboard, -- actual, count 1's and size*size-count 0's.

        eboard,    -- one of "", as being enumerated through
        results,   -- results of rays/probes, {x,y,c,x,y} format
        guessxy,   -- locations (each element is {x,y})
        guessclr,  -- colours of "" (CD_BLUE for a guess,
                   --                CD_GREEN for correct,
                   --                CD_RED for wrong,
                   --                CD_YELLOW/CYAN for hints.
        hidden,    -- "" as saved during setup
        possibles, -- up to 635,376 integer codes for 8*8 with 4 game,
                   -- each entry being possible for content of results, 
                   -- but never deliberately driven over 100,000.
        knowns,    -- these "are" atoms (but "maybe" if tried<maxtry)
        minmaxmove -- best move available, see minmaxcount

integer possible, -- # of possibles checked to be plausible(), ie

                   -- [posssible+1..$] are all subject to imminent
                   -- deletion by the idle handler, if invalid.
       hinted,     -- # of probes analysed by hint_explore().
       minmaxcount -- best (so far)

atom tried, maxtry -- # of enumerations attempted/theoretical max. bool hints_used = false -- (affects the scoring)

function probe(integer x, y, sequence board, bool bSort=true) -- -- returns {x,y,c,rx,ry} primarily for use in redraw_cb(), and -- secondarily for use in plausible(). -- where c is: -1 for reflection, 0 for hit, and +1 otherwise. -- Note that for the latter you need to allocate an actual colour -- elsewhere (if this did that it would spanner plausible() etc), -- and also note that -2 is now in use for the ray/probe hint. -- Also x,y and rx,ry re-ordered lowest-first to avoid duplicates, -- except for hint exploration, which passes a bSort of false. --

   integer rx = x, ry = y, -- current/emerge point (ray)
           dx = 0, dy = 0, -- direction of travel
           moves = 0       -- debug aid
   if    x=0 then  dx = +1     -- left entry, moving right
   elsif y=0 then  dy = +1     -- top        "		   down
   elsif x=s1 then dx = -1     -- right      "		   left	
   elsif y=s1 then dy = -1     -- btm        "			 up
   else ?9/0 -- (sanity check)
   end if
   while true do
       integer nx = rx+dx,     -- next logical position
               ny = ry+dy,
               idx = (ny-1)*size+nx
       if nx=0 or nx=s1 or ny=0 or ny=s1 then
           if x=nx and y=ny then
               return {x,y,-1,0,0} -- Reflection
           elsif bSort then
               {{x,y},{nx,ny}} = sort({{x,y},{nx,ny}})
           end if
           return {x,y,1,nx,ny}    -- Emerges here
       elsif idx<=0 then
           ?9/0                    -- (sanity check)
       elsif board[idx] then
           return {x,y,0,0,0}      -- Hit
       --
       -- aside: rather than check diagonally, nx/ny are
       --      simply discarded when a deflection occurs,
       --      and we actually check things laterally.
       --
       elsif dx=0 then
           -- up/down movement, check sides
           if nx>1 and board[idx-1] then
               if nx<size and board[idx+1] then
                   dy = -dy            -- 180
               else
                   {dx,dy} = {1,0}     -- right
                   -- (yep, both up & down deflected
                   --  right by an atom on the left)
               end if
           elsif nx<size and board[idx+1] then
               {dx,dy} = {-1,0}        -- left
               --  (ditto left by one on the right)
           else
               {rx,ry} = {nx,ny}
           end if
       elsif dy=0 then
           -- left/right movement, check above/below
           if ny>1 and board[idx-size] then
               if ny<size and board[idx+size] then
                   dx = -dx            -- 180
               else
                   {dx,dy} = {0,1}     -- down
                   -- (yep, left & right are both
                   --  deflected down by one above)
               end if
           elsif ny<size and board[idx+size] then
               {dx,dy} = {0,-1}        -- up
               -- (ditto up by one below)
           else
               {rx,ry} = {nx,ny}
           end if
       else
           ?9/0 -- (sanity check, dx,dy=={0,0}!?)
       end if
       if rx=0 or rx=s1 or ry=0 or ry=s1 then
           {dx,dy} = {0,0} -- (outer swivel===reflection)
       end if
       -- guard against infinite loops, why not.
       -- *2 because swivel/move counted separately.
       moves += 1
       if moves>2*size*size then ?9/0 end if
   end while       

end function

function plausible(sequence board)

   for i=1 to length(results) do
       sequence ri = results[i]
       integer {x,y} = ri
       if probe(x,y,board)!=ri then return false end if
   end for
   return true

end function

-- -- For the smaller games we could use almost any storage method, but to facilitate larger -- boards with more atoms we should be as stingy with memory as possible. To that end an -- enumeration is stored as a compact set of offsets to the next piece. For instance the -- board {0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,....1} is stored as offsets {4,6,8,64-18} -- further using an appropriate mask to give (((((46*#40)+8)*#40)+6)*#40)+4 which can be -- stored as a single integer/atom, yet unpacked quite easily, see next. Note there is -- code in new_game(), for valuechanged_cb(), that ensures we can store count*bits, and -- more by luck than judgement that (partly) helps avoid configurations that would take -- far longer than the universe has existed to enumerate and scan even just the once. -- function unpack(atom code)

   sequence board = repeat(0,size*size)
   integer offset = 0, r, check = 0
   while code do
       r = remainder(code,mask)
       if r<=0 then ?9/0 end if        -- sanity check
       offset += r
       board[offset] = 1
       code = floor(code/mask)
       check += 1
   end while
   if check!=count then ?9/0 end if    -- sanity check
   return board

end function

function pack(sequence board)

   atom code = 0, pmask = 1
   integer idx = 0, check = 0
   while true do
       integer prev = idx
       idx = find(1,board,idx+1)
       if idx=0 then exit end if
       code = code + (idx-prev)*pmask
       check += 1
       pmask *= mask
   end while
   if check!=count then ?9/0 end if    -- sanity check

-- if unpack(code)!=board then ?9/0 end if -- ""

   return code

end function

procedure trim_possibles() -- -- Re-process the possibles table as follows: -- 111...222322323...$ -- where 111... is possibly empty ok [1..possible], -- and 222322323 is some chunk [possible+1..limit], -- with 2s for oks and 3s for now-failing entries, -- which gets processed in a right-to-left order, -- such that fails(3) get replaced from the (1)s, -- being careful to quit early on any overlap, and -- /or re-test same slot if the 111... exhausted. -- Finally, trim off the dead head of possibles[]. -- The result is quite scrambled, but care we not. --

   integer limit = min(possible+100_000,length(possibles)),
           limit0 = limit,
           kill = 1 -- (actually 1 over)
   while limit>max(possible,kill-1) do
       if not plausible(unpack(possibles[limit])) then
           possibles[limit] = possibles[kill]
           if kill<=possible then
               limit -= 1
           end if
           kill += 1
       else
           limit -= 1
       end if
   end while
   possibles = possibles[kill..$]
   possible = limit0-kill+1

end procedure

procedure enumerate()

   atom limit = min(tried+100_000,maxtry)
   while tried<limit and length(possibles)<100_000 do
       tried += 1
       if plausible(eboard) then
           possibles &= pack(eboard)
           possible += 1
       end if
       --
       -- think abacus: find the first bead you can shift left,
       --                and slam the rest of them hard right.
       -- similar to binary counting, but you must always have
       --                exactly 'count' beads (ie 1's), eg
       -- choose(2*2,2) is 6:
       --       0b0011  0b0101  0b0110  0b1001  0b1010  0b1100
       --
       -- However, because we are scanning from top left down
       -- to bottom right, it turned out better to do them in
       -- reverse order, hence shift right and slam left (not
       -- quite an exact mirror, but close enough).
       --
       integer idx = find(1,eboard), last = 1
       while true do
           eboard[idx] = 0
           idx += 1
           if idx>size*size then exit end if
           if eboard[idx]=0 then 
               eboard[idx] = 1
               exit
           end if
           eboard[last] = 1
           last += 1
       end while
       if idx=0 then exit end if
   end while

end procedure

function idx_from_edge(integer x,y) -- convert {x,y}, where one but not both of x,y are either 0 -- or s1, and the other is strictly 1..size, into 1..4*size. -- if x=0 then x = 0 -- (logically, but obvs. pointless)

   if x=s1 then x = size
   elsif y=0 then y = size*2
   elsif y=s1 then y = size*3
   elsif x!=0 then ?9/0 end if -- not an edge?!
   return x+y

end function

function edge_from_idx(integer xy) -- convert 1..4*size into {0,1..size}/{s1,1..size}/{1..size,0}/{1..size,s1}

   sequence res
   integer c = floor((xy-1)/size)
   switch c do
       case 0: res = {0,xy}
       case 1: res = {s1,xy-size}
       case 2: res = {xy-size*2,0}
       case 3: res = {xy-size*3,s1}
       default: ?9/0
   end switch
   return res

end function

-- this is currently inlined, in case you were looking for it: --procedure idx_from_x_y(integer x, y) -- convert {1,1}..{size,size} to 1..size*size, for flat indexing -- return (y-1)*size+x --end function

function x_y_from_idx(integer idx) -- convert 1..size*size to {1,1}..{size,size} -- (absence of floor() on /size is a deliberate sanity check)

   integer x = remainder(idx-1,size)+1,
           y = (idx-x)/size + 1
   return {x,y}

end function

function next_hint()

   sequence edges = repeat(0,size*4)
   integer x,y,r
   for i=1 to length(results) do
       {x,y,r} = results[i]
       for j=1 to 1+(r==1) do
           integer idx = idx_from_edge(x,y)
           if edges[idx] then ?9/0 end if
           edges[idx] = 1
           {?,?,?,x,y} = results[i]
       end for
   end for
   integer new_hinted = find(0,edges,hinted+1)
   return new_hinted

end function

procedure explore_hints(integer new_hinted)

   if new_hinted then
       -- originally, it proved better to scan these backwards...
       -- it now breaks (wrong tiles, I guess) if not flipped...
       new_hinted = size*4+1-new_hinted
       integer {x,y} = edge_from_idx(new_hinted), k
       sequence rxy = {}, counts = {}
       for i=1 to possible do
           sequence p = probe(x,y,unpack(possibles[i]),false)
           k = find(p,rxy)
           if k=0 then
               rxy = append(rxy,p)
               counts = append(counts,1)
           else
               counts[k] += 1
           end if
       end for
       k = max(counts)
       if hinted=0
       or minmaxcount=0
       or k<minmaxcount then
           minmaxcount = k
           k = maxsq(counts,true)
           minmaxmove = rxy[k]
           minmaxmove[3] = -2
       end if
       new_hinted = size*4+1-new_hinted  -- unflip
       hinted = new_hinted
   else
       hinted = size*4
   end if

end procedure

procedure find_common()

   sequence all = repeat(1,size*size),
            none = repeat(0,size*size)
   for i=1 to possible do
       all = sq_and(all,unpack(possibles[i]))
       if all==none then exit end if
   end for
   knowns = {}
   for i=1 to length(all) do
       if all[i] then
           knowns = append(knowns,x_y_from_idx(i))
       end if
   end for

end procedure

include pGUI.e Ihandle dlg, game_canvas, gridsize, atoms, score, hints, debug,

       progress, declare

constant colour_table = {CD_RED,

                        CD_LIGHT_GREEN,
                        CD_YELLOW,
                        CD_BLUE,
                        CD_ORANGE,
                        CD_PURPLE,
                        CD_CYAN,
                        CD_MAGENTA,
                        CD_GREEN,
                        CD_DARK_GREEN,
                        #bfef45,   -- Lime
                        #fabebe,   -- Pink
                        #469990,   -- Teal
                        #e6beff,   -- Lavender
                        #9A6324,   -- Brown
                        #fffac8,   -- Beige
                        #800000,   -- Maroon
                        #aaffc3,   -- Mint
                        #808000,   -- Olive
                        #ffd8b1,   -- Apricot
                        #000075}   -- Navy

function colour(integer c)

   c = mod(c-1,length(colour_table))+1
   return colour_table[c]

end function

constant CD_HINTS = CD_DARK_GREY, -- (where to fire probe)

        CD_MAYBE = CD_YELLOW,      -- (probably an atom [scan not yet finished])
        CD_KNOWN = CD_CYAN         -- (known atoms [scan finished])

procedure redraw()

   IupUpdate(game_canvas)

end procedure

function idle_action()

   integer new_hinted = 0
   if possible<length(possibles) then  
       trim_possibles()
       hinted = 0
   elsif tried<maxtry and length(possibles)<100_000 then
       enumerate()
       hinted = 0
   elsif IupGetInt(hints,"VALUE")
     and hinted<size*4 then
       if possible>1 
       and hinted<size*4 then
           new_hinted = next_hint()
           explore_hints(new_hinted)
           redraw()
       end if
       if possible=1
       or hinted=size*4 then
           hinted = size*4
           find_common()
           redraw()
       end if
   else
       return IUP_IGNORE -- (disables idle)
   end if
   string title = sprintf("%,d / %,d (%d%%)",{possible,tried,100*(tried/maxtry)})
   if new_hinted then
       title &= sprintf(", move %d/%d",{new_hinted,size*4})
   end if
   IupSetStrAttribute(progress,"TITLE",title)
   return IUP_DEFAULT

end function constant idle_action_cb = Icallback("idle_action")

procedure start_idle()

   IupSetAttribute(progress,"TITLE","-")
   IupSetGlobalFunction("IDLE_ACTION",idle_action_cb)

end procedure

procedure new_game()

   size = IupGetInt(gridsize,"VALUE")
   s1 = size+1
   s2 = size+2
   count = IupGetInt(atoms,"VALUE")
   while true do -- in case count too big
       mask = #02
       integer bits = 1
       while mask<=size*size-count+1 do mask*=2 bits+=1 end while
       --
       -- Prevent overflow: must be able to store count*bits in a Phix atom.
       -- count limits are therefore 13 on 5x5, 7 on 10x10, and 5 on 20x20,
       -- on 32-bit, but 64-bit does 16 on 5x5, 9 on 10x10, and 7 on 20x20.
       -- Many if not all of the silly-sized games this prohibits could not 
       -- possibly be fully analysed within a typical human lifespan anyway.
       -- Besides just 5 atoms allows ambiguous/therefore unplayable games.
       -- See also the comments before unpack() above. Trying to store too
       -- many bits would trigger the sanity checks in pack()/unpack().
       --
       integer mb = iff(machine_bits()=32?53:64),
               maxcount = min(floor(mb/bits),size*size)
       if count<=maxcount then exit end if
       count = maxcount
       IupSetInt(atoms,"VALUE",count)
   end while
   eboard = repeat(0,size*size)
   eboard[1..count] = 1
   tried = 0
   maxtry = choose(size*size,count)
   possibles = {}
   possible = 0
   results = {}
   guessxy = {}
   guessclr = {}
   hidden = {}
   knowns = {}
   minmaxcount = 0
   gameboard = repeat(0,size*size)
   bool active = IupGetInt(debug,"VALUE")
   integer done = 0, x, y, xy
   while done<count do
       x = rand(size)
       y = rand(size)
       xy = (y-1)*size+x
       if gameboard[xy]=0 then
           gameboard[xy] = 1
           hidden = append(hidden,{x,y})
           done += 1
       elsif not find(0,gameboard) then
           ?9/0 -- let's not loop forever!
                -- (should now be prevented by maxcount above)
       end if
   end while
   IupSetInt(declare, "ACTIVE", active)
   if active then
       guessxy = hidden
       guessclr = repeat(CD_BLUE,length(guessxy))
   end if
   hints_used = (IupGetInt(hints,"VALUE") and not active)
   start_idle()

end procedure

-- saved in redraw_cb(), for click testing in button_cb(): integer wh, -- width and height

       mx, my  -- margins

-- saved in declare_cb(), for adding to the score (10 each) integer wrong = 0

function redraw_cb(Ihandle ih, integer /*posx*/, integer /*posy*/)

   integer {w,h} = IupGetIntInt(ih, "DRAWSIZE")
   -- calc width/height and margins (saved for button_cb):
   wh = min(floor((w-10)/s2),floor((h-10)/s2))
   mx = floor((w-wh*(s2))/2)
   my = floor((h-wh*(s2))/2)
   
   cdCanvas cddbuffer = IupGetAttributePtr(ih,"DBUFFER")
   IupGLMakeCurrent(ih)
   cdCanvasActivate(cddbuffer)
   cdCanvasClear(cddbuffer)
   -- outer edges (using one huge '+' shape)   
   cdCanvasSetForeground(cddbuffer,CD_GREY)
   cdCanvasBox(cddbuffer,mx+wh,mx+wh*s1,my,my+wh*s2)
   cdCanvasBox(cddbuffer,mx,mx+wh*s2,my+wh,my+wh*s1)
   -- the inner size*size board (square)
   cdCanvasSetForeground(cddbuffer,CD_LIGHT_GREY)
   cdCanvasBox(cddbuffer,mx+wh,mx+wh*s1,my+wh,my+wh*s1)
   -- draw the grid lines
   cdCanvasSetForeground(cddbuffer,CD_WHITE)
   integer {lx,ly} = {mx,my}
   for i=1 to size+1 do
       lx += wh
       ly += wh
       cdCanvasLine(cddbuffer,lx,my,lx,my+wh*s2)
       cdCanvasLine(cddbuffer,mx,ly,mx+wh*s2,ly)
   end for
   sequence edges = repeat(0,size*4)
   integer x,y,c = 1, h2 = floor(wh/2), r,
           rfrom = (minmaxcount==0 or IupGetInt(hints,"VALUE")=0)
   for i=rfrom to length(results) do
       {x,y,r} = iff(i=0?minmaxmove:results[i])
       integer cb, ct
       string txt
       {txt,cb,ct} = iff(r=-2?{"+",CD_HINTS,CD_BLACK}:
                     iff(r=-1?{"R",CD_WHITE,CD_BLACK}:
                     iff(r==0?{"H",CD_BLACK,CD_WHITE}:
                  {sprintf("%d",c),CD_GREY,colour(c)})))
       for j=1 to 1+(r==1) do
           cdCanvasSetForeground(cddbuffer,cb)
           integer cx = mx+wh*x,
                   cy = my+wh*(s1-y)
           cdCanvasBox(cddbuffer,cx+1,cx+wh,cy+1,cy+wh)
           cdCanvasSetForeground(cddbuffer,ct)
           cdCanvasFont(cddbuffer, "Helvetica", CD_BOLD, h2)
           cdCanvasText(cddbuffer, cx+h2, cy+h2, txt)
           if i!=0 then
               integer idx = idx_from_edge(x,y)
               if edges[idx] then ?9/0 end if
               edges[idx] = 1
               if r=1 then
                   {?,?,?,x,y} = results[i]
                   c += (j=2)
               end if
           end if
       end for
   end for
   sequence gxy = guessxy,
            gclr = guessclr
   if IupGetInt(hints,"VALUE") then
       for i=1 to length(knowns) do
           sequence ki = knowns[i]
           if not find(ki,gxy) then
               gxy = append(gxy,ki)
               gclr = append(gclr,iff(tried<maxtry?CD_MAYBE:CD_KNOWN))
           end if
       end for
   end if
   for i=1 to length(gxy) do
       {x,y} = gxy[i]
       atom cx = mx+(x+0.5)*wh,
            cy = my+(s1-y+0.5)*wh
       r = floor(wh*4/5)
       cdCanvasSetForeground(cddbuffer,gclr[i])
       cdCanvasCircle(cddbuffer, cx, cy, r)
   end for
   cdCanvasFlush(cddbuffer)

-- IupSetStrAttribute(score,"TITLE","%d",{iff(hints_used?9999:sum(edges)+wrong*10)})

   IupSetStrAttribute(score,"TITLE","%d",{sum(edges)+wrong*10})
   return IUP_DEFAULT

end function

function map_cb(Ihandle ih)

   IupGLMakeCurrent(ih)
   atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
   cdCanvas cddbuffer = cdCreateCanvas(CD_GL, "10x10 %g", {res})
   IupSetAttributePtr(ih,"DBUFFER",cddbuffer)
   cdCanvasSetBackground(cddbuffer, CD_PARCHMENT)
   {} = cdCanvasTextAlignment(cddbuffer, CD_CENTER)
   return IUP_DEFAULT

end function

function canvas_resize_cb(Ihandle canvas)

   cdCanvas cddbuffer = IupGetAttributePtr(canvas,"DBUFFER")
   integer {canvas_width, canvas_height} = IupGetIntInt(canvas, "DRAWSIZE")
   atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
   cdCanvasSetAttribute(cddbuffer, "SIZE", "%dx%d %g", {canvas_width, canvas_height, res})
   return IUP_DEFAULT

end function

function declare_cb(Ihandle /*declare*/)

   sequence add_h = repeat(true,length(hidden))
   wrong = max(0,count-length(guessxy))
   for i=1 to length(guessxy) do
       integer k = find(guessxy[i],hidden)
       if k then
           guessclr[i] = CD_GREEN
           add_h[k] = false
       else
           guessclr[i] = CD_RED
           wrong += 1
       end if
   end for
   for i=1 to length(add_h) do
       if add_h[i] then
           guessxy = append(guessxy,hidden[i])
           guessclr = append(guessclr,CD_BLUE)
       end if
   end for
   IupSetAttribute(declare, "ACTIVE", "NO")
   redraw()
   return IUP_DEFAULT

end function

function button_cb(Ihandle canvas, integer button, pressed, x, y, atom /*pStatus*/)

   Ihandle frame = IupGetParent(canvas)
   string title = IupGetAttribute(frame,"TITLE")
   if button=IUP_BUTTON1 and not pressed then      -- (left button released)
       x = floor((x-mx)/wh)
       y = floor((y-my)/wh)
       -- obviously, an x/y of 0 means left/top,
       --            whereas s1 means right/btm,
       --            and 1..size(both) is inner.
       bool outerx = (x>=0 and x<=s1),
            outery = (y>=0 and y<=s1),
            innerx = (x>=1 and x<=size),
            innery = (y>=1 and y<=size)
       if innerx and innery then
           sequence guess = {x,y}
           integer k = find(guess,guessxy)
           if k then
               guessxy[k..k] = {}
               guessclr[k..k] = {}
           else
               guessxy = append(guessxy,guess)
               guessclr = append(guessclr,CD_BLUE)
           end if
           bool bActive = (length(guessxy)==count)
           IupSetInt(declare, "ACTIVE", bActive)
           if IupGetInt(debug,"VALUE")
           and length(guessxy)=count then
               hidden = guessxy
               gameboard = repeat(0,size*size)
               for i=1 to count do
                   {x,y} = hidden[i]
                   integer xy = (y-1)*size+x
                   gameboard[xy] = 1
               end for
               results = {}
           end if
           redraw()
       elsif (outerx and innery)
          or (outery and innerx) then
           sequence r = probe(x,y,gameboard)
           if not find(r,results) then
               results = append(results,r)
               possible = 0
               start_idle()
           end if
           redraw()
       end if
   end if
   return IUP_CONTINUE

end function

function new_game_cb(Ihandle /*ih*/)

   new_game()
   redraw()
   return IUP_DEFAULT

end function

function exit_cb(Ihandle /*ih*/)

   return IUP_CLOSE

end function

function help_cb(Ihandln /*ih*/)

   IupMessage(title,help_text)
   return IUP_DEFAULT

end function

function key_cb(Ihandle /*dlg*/, atom c)

   if c=K_ESC then return IUP_CLOSE end if
   if c=K_F1 then return help_cb(NULL) end if
   if c='?' then
       -- an old diagnostic that I kept in...
       for i=1 to min(5,length(possibles)) do
           sequence s = unpack(possibles[i])
           for j=1 to size do
               ?s[1..size]
               s = s[size+1..$]
           end for
           puts(1,"\n")
       end for
       possible = 0
       start_idle()
   end if
   return IUP_CONTINUE

end function

function valuechanged_cb(Ihandle ih)

   if ih=hints then
       hints_used = true
       start_idle()
   else
       new_game()
   end if
   redraw()
   return IUP_DEFAULT

end function constant cb_valuechanged = Icallback("valuechanged_cb")

procedure main()

   IupOpen()

   gridsize = IupText("SPIN=Yes, SPINMIN=1, SPINMAX=20, VALUE=8, RASTERSIZE=34x")
   atoms = IupText("SPIN=Yes, SPINMIN=1, SPINMAX=16, VALUE=4, RASTERSIZE=34x")
   score = IupLabel("","EXPAND=HORIZONTAL, PADDING=5x4")
   hints = IupToggle("  Show Hints?","VALUE=YES, RIGHTBUTTON=YES, PADDING=5x4")
   debug = IupToggle("Debug Mode?","VALUE=NO, RIGHTBUTTON=YES, PADDING=5x4")
   progress = IupLabel("-","EXPAND=HORIZONTAL, PADDING=5x4")
   declare = IupButton("Declare",Icallback("declare_cb"),"PADDING=5x4, ACTIVE=NO")
   game_canvas = IupGLCanvas("RASTERSIZE=400x400")
   Ihandle newgame = IupButton("New Game",Icallback("new_game_cb"),"PADDING=5x4"),
           help = IupButton("Help (F1)",Icallback("help_cb"),"PADDING=5x4"),
           quit = IupButton("E&xit",Icallback("exit_cb"),"PADDING=5x4"),
           vbox = IupVbox({IupHbox({IupLabel("Size","PADDING=5x4"),gridsize,
                                    IupFill(),
                                    IupLabel("Atoms","PADDING=5x4"),atoms}),
                           IupHbox({hints,IupFill(),debug}),
                           IupHbox({progress}),
                           IupHbox({IupLabel("Score","PADDING=5x4"),score}),
                           IupHbox({declare,newgame,help,quit})},"MARGIN=5x5"),
           game_frame = IupFrame(IupHbox({game_canvas},"MARGIN=3x3"),"TITLE=Game"),
           option_frame = IupFrame(vbox,"TITLE=Options"),
           full = IupHbox({game_frame,option_frame})
   IupSetCallbacks({gridsize,atoms,hints,debug}, {"VALUECHANGED_CB", cb_valuechanged})
   IupSetCallbacks(game_canvas, {"ACTION", Icallback("redraw_cb"),
                                 "MAP_CB", Icallback("map_cb"),
                                 "RESIZE_CB", Icallback("canvas_resize_cb"),
                                 "BUTTON_CB", Icallback("button_cb")})
   dlg = IupDialog(IupHbox({full},"MARGIN=3x3"))
   IupSetAttribute(dlg, "TITLE", title)
   IupSetCallback(dlg, "K_ANY", Icallback("key_cb"))
   IupSetAttributeHandle(dlg,"DEFAULTENTER", declare)  --erm...??
   new_game()
   IupShowXY(dlg,IUP_CENTER,IUP_CENTER)
   IupSetAttribute(dlg, "RASTERSIZE", NULL)
   IupSetStrAttribute(dlg, "MINSIZE", IupGetAttribute(dlg,"RASTERSIZE"))
   sequence fixsize = {score,progress}
   for i=1 to length(fixsize) do
       Ihandle fi = fixsize[i]
       IupSetAttributes(fi, "RASTERSIZE=%s, EXPAND=NO", {IupGetAttribute(fi,"RASTERSIZE")})
   end for
   IupMainLoop()
   IupClose()

end procedure

main()</lang>

Wren

Translation of: Go
Library: Wren-fmt
Library: Wren-ioutil
Library: Wren-str

<lang ecmascript>import "random" for Random import "/fmt" for Fmt import "/ioutil" for Input import "/str" for Str

var b = List.filled(100, null) // displayed board var h = List.filled(100, null) // hidden atoms var wikiGame = true // set to false for a 'random' game var rand = Random.new()

var hideAtoms = Fn.new {

   var placed = 0
   while (placed < 4) {
       var a = rand.int(11, 89) // 11 to 88 inclusive
       var m = a % 10
       if (m == 0 || m == 9 || h[a] == "T") continue
       h[a] = "T"
       placed = placed + 1
   }

}

var initialize = Fn.new {

   for (i in 0..99) {
       b[i] = " "
       h[i] = "F"
   }
   if (!wikiGame) {
       hideAtoms.call()
   } else {
       h[32] = "T"
       h[37] = "T"
       h[64] = "T"
       h[87] = "T"
   }
   System.print("""
   === 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.
   """)

}

var drawGrid = Fn.new { |score, guesses|

   System.print("      0   1   2   3   4   5   6   7   8   9 ")
   System.print()
   System.print("        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗")
   Fmt.lprint("a     $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s",
       [b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9]])
   System.print("    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗")
   Fmt.lprint  ("b   ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║",
       [b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]])
   System.print("    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣")
   Fmt.lprint  ("c   ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║",
       [b[20], b[21], b[22], b[23], b[24], b[25], b[26], b[27], b[28], b[29]])
   System.print("    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣")
   Fmt.lprint  ("d   ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║",
       [b[30], b[31], b[32], b[33], b[34], b[35], b[36], b[37], b[38], b[39]])
   System.print("    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣")
   Fmt.lprint  ("e   ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║",
       [b[40], b[41], b[42], b[43], b[44], b[45], b[46], b[47], b[48], b[49]])
   System.print("    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣")
   Fmt.lprint  ("f   ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║",
       [b[50], b[51], b[52], b[53], b[54], b[55], b[56], b[57], b[58], b[59]])
   System.print("    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣")
   Fmt.lprint  ("g   ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║",
       [b[60], b[61], b[62], b[63], b[64], b[65], b[66], b[67], b[68], b[69]])
   System.print("    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣")
   Fmt.lprint  ("h   ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║",
       [b[70], b[71], b[72], b[73], b[74], b[75], b[76], b[77], b[78], b[79]])
   System.print("    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣")
   Fmt.lprint  ("i   ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║",
       [b[80], b[81], b[82], b[83], b[84], b[85], b[86], b[87], b[88], b[89]])
   System.print("    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝")
   Fmt.lprint  ("j     $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s ║ $s",
       [b[90], b[91], b[92], b[93], b[94], b[95], b[96], b[97], b[98], b[99]])
   System.print("        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝")
   var status = (guesses != 4) ? "In play" : "Game over!"
   System.print("\n        Score = %(score)\tGuesses = %(guesses)\t Status = %(status)\n")

}

var atCorner = Fn.new { |ix| ix == 0 || ix == 9 || ix == 90 || ix == 99 }

var inRange = Fn.new { |ix| ix >= 1 && ix <= 98 && ix != 9 && ix != 90 }

var atTop = Fn.new { |ix| ix >= 1 && ix <= 8 }

var atBottom = Fn.new { |ix| ix >= 91 && ix <= 98 }

var atLeft = Fn.new { |ix| inRange.call(ix) && ix%10 == 0 }

var atRight = Fn.new { |ix| inRange.call(ix) && ix%10 == 9 }

var inMiddle = Fn.new { |ix|

   return inRange.call(ix) && !atTop.call(ix) && !atBottom.call(ix) &&
          !atLeft.call(ix) && !atRight.call(ix)

}

var nextCell = Fn.new {

   var ix
   while (true) {
       var sq = Str.lower(Input.text("    Choose cell : ", 1))
       if (sq.count == 1 && sq[0] == "q") {
           Fiber.abort("program aborted")
       }
       if (sq.count != 2 || !"abcdefghij".contains(sq[0]) || !"0123456789".contains(sq[1])) {
           continue
       }
       ix = (sq[0].bytes[0] - 97) * 10 + sq[1].bytes[0] - 48
       if (atCorner.call(ix)) continue
       break
   }
   System.print()
   return ix

}

var finalScore = Fn.new { |score, guesses|

   for (i in 11..88) {
       var 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 = score + 5
       } else if (b[i] == " " && h[i] == "T") {
           b[i] = "A"
       }
   }
   drawGrid.call(score, guesses)

}

var play = Fn.new {

   var score = 0
   var guesses = 0
   var num = "0"
   while (true) {
       var outer = false
       drawGrid.call(score, guesses)
       var ix = nextCell.call()
       if (!inMiddle.call(ix) && b[ix] != " ") continue  // already processed
       var inc
       var def
       if (atTop.call(ix)) {
           inc = 10
           def = 1
       } else if (atBottom.call(ix)) {
           inc = -10
           def = 1
       } else if (atLeft.call(ix)) {
           inc = 1
           def = 10
       } else if (atRight.call(ix)) {
           inc = -1
           def = 10
       } else {
           if (b[ix] != "G") {
               b[ix] = "G"
               guesses = guesses + 1
               if (guesses == 4) break
           } else {
               b[ix] = " "
               guesses = guesses - 1
           }
           continue
       }
       var x = ix + inc
       var first = true
       while (inMiddle.call(x)) {
           if (h[x] == "T" ) {  // hit
               b[ix] = "H"
               score = score + 1
               first = false
               outer = true
               break
           }
           if (first && (inMiddle.call(x+def) && h[x+def] == "T") ||
               (inMiddle.call(x-def) && h[x-def] == "T")) {  // reflection
               b[ix] = "R"
               score = score + 1
               first = false
               outer = true
               break
           }
           first = false
           var y = x + inc - def
           if (inMiddle.call(y) && h[y] == "T") {  // deflection
               if (inc.abs == 1) {
                   inc = 10
                   def = 1
               } else if (inc.abs == 10) {
                   inc = 1
                   def = 10
               }
           }
           y = x + inc + def
           if (inMiddle.call(y) && h[y] == "T") {  // deflection or double deflection
               if (inc.abs == 1) {
                   inc = -10
                   def = 1
               } else if (inc.abs == 10) {
                   inc = -1
                   def = 10
               }
           }
           x = x + inc
       }
       if (outer) continue
       if (num != "9") {
           num = String.fromByte(num.bytes[0] + 1)
       } else {
           num = "a"
       }
       if (b[ix] == " ") score = score + 1
       b[ix] = num
       if (inRange.call(x)) {
           if (ix == x) {
               b[ix] = "R"
           } else {
               if (b[x] == " ") score = score + 1
               b[x] = num
           }
       }
   }
   drawGrid.call(score, guesses)
   finalScore.call(score, guesses)

}

while (true) {

   initialize.call()
   play.call()
   var yn = Str.lower(Input.option("    Play again y/n : ", "ynYN"))
   if (yn == "n") return

}</lang>

Output:

Sample game (wikiGame == true):

    === 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

      0   1   2   3   4   5   6   7   8   9 

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 4	Guesses = 0	 Status = In play

    Choose cell : d7

      0   1   2   3   4   5   6   7   8   9 

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║   ║   ║   ║   ║   ║ G ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 4	Guesses = 1	 Status = In play

    Choose cell : d4

      0   1   2   3   4   5   6   7   8   9 

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║   ║   ║ G ║   ║   ║ G ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 4	Guesses = 2	 Status = In play

    Choose cell : e3

      0   1   2   3   4   5   6   7   8   9 

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║   ║   ║ G ║   ║   ║ G ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║ G ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 4	Guesses = 3	 Status = In play

    Choose cell : h2

      0   1   2   3   4   5   6   7   8   9 

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║   ║   ║ G ║   ║   ║ G ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║ G ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║ G ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 4	Guesses = 4	 Status = Game over!

      0   1   2   3   4   5   6   7   8   9 

        ╔═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
a       ║ 2 ║   ║   ║   ║   ║   ║   ║   ║  
    ╔═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╗
b   ║ 1 ║   ║   ║   ║   ║   ║   ║   ║   ║ 1 ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
c   ║ 2 ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
d   ║   ║   ║ A ║   ║ N ║   ║   ║ Y ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
e   ║   ║   ║   ║ N ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
f   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
g   ║   ║   ║   ║   ║ A ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
h   ║   ║   ║ N ║   ║   ║   ║   ║   ║   ║   ║
    ╠═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
i   ║   ║   ║   ║   ║   ║   ║   ║ A ║   ║   ║
    ╚═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╬═══╝
j       ║   ║   ║   ║   ║   ║   ║   ║   ║  
        ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝

        Score = 19	Guesses = 4	 Status = Game over!

    Play again y/n : n

zkl

Translation of: Go

<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(
  1. <<<"
   === 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");
  1. <<<

}

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