Implement a version of the Black Box game beginners configuration: 4 Atoms in an 8 x 8 grid.

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.

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