librarygames/tic-tac-toe/TicTacToe.xtl
Tic-tac-toe's rules, a library: tic-tac-toe.xtl (scripted), play.xtl (at the terminal) and the web page all use these. The board is a vector of 9 squares, numbered row by row: 0 empty, 1 X, -1 O. X moves first.
ˡl̲ines : Num a => Unit -> a
The eight lines (three rows, three columns, two diagonals) as an 8 by 3 table of square numbers.
ˡl̲ines ← { @ → 8 3 r̲eshape 1 2 3 4 5 6 7 8 9 1 4 7 2 5 8 3 6 9 1 5 9 3 5 7 }
ˡs̲ums : Num a => a -> a
Every line's sum at once: the table used as indices into the board (an 8 by 3 matrix of squares), summed along each row. 3 is a line of X, -3 a line of O.
ˡs̲ums ← { b → '+ r̲/₂ (ˡl̲ines @) s̲elect b }
ˡw̲inner : (Num a, Num b, Truthy b) => a -> b
1 when X has a line, -1 when O has, else 0.
ˡw̲inner ← { b → s ← ˡs̲ums b (3 m̲ember? s) − -3 m̲ember? s }
ˡt̲urn : (Num a, Num b, Truthy b) => a -> b
Whose turn: X (1) when the board has as many X as O, else O (-1).
ˡt̲urn ← { b → 1 − 2 × 0 ≠ '+ r̲/ b }
ˡn̲ew : (Any a, Num b) => a -> b
A new board: nine empty squares.
ˡn̲ew ← { x → 9 r̲eshape 0 }
ˡl̲egal : Num a => a -> Int
The empty squares, by number: the legal moves.
ˡl̲egal ← { b → w̲here b = 0 }
ˡs̲tatus : (Num a, Num b) => a -> b
0 playing, 1 X won, 2 O won, 3 a draw: the first of the three that holds.
ˡs̲tatus ← { b → w ← ˡw̲inner b f̲irst (((w = 1) c̲at (w = -1) c̲at 0 = t̲ally ˡl̲egal b) r̲eplicate 1 2 3) c̲at 0 }
ˡm̲ove : (Num a, Truthy a) => a -> Int -> a
The player to move takes square k.
ˡm̲ove ← { b k → b + (ˡt̲urn b) × k = r̲ange 9 }
ˡc̲odes : Num a => a -> Int
Every square's code at once: O 1, X 2, an empty square 2 plus its number (so the text shows the number to type).
ˡc̲odes ← { b → 3 3 r̲eshape (b = -1) + (2 × b = 1) + (b = 0) × 2 + r̲ange 9 }
ˡv̲iew : Num a => a -> Char
The board as text, spaced (lib/Board).
ˡv̲iew ← { b → ᵇs̲paced (ˡc̲odes b) s̲elect "OX123456789" }
ˡp̲icture : Num a => a -> Char
The board as a picture X_eTaL draws (SVG text); []S_HOW shows it.
ˡp̲icture ← { b → ⎕G̲RID (ˡc̲odes b) s̲elect "OX123456789" }
Players
ˡo̲n : Truthy a => Unit -> a
Which squares lie on which lines: a 9 by 8 table (square against line), from a 9 by 8 by 3 table of every square against every entry.
ˡo̲n ← { @ → '∨ r̲/₃ (r̲ange 9) '= t̲able ˡl̲ines @ }
ˡn̲ear : (Num a, Num b, Truthy b) => a -> a -> b
For every square, whether a line through it sums to t.
ˡn̲ear ← { b t → '∨ r̲/₂ (ˡo̲n @) × 9 8 r̲eshape t = ˡs̲ums b }
ˡa̲i : (Num a, Truthy a) => a -> Int
A quick player that rates every empty square at once: four features per square (it completes a line of the mover's, it blocks one of the other's, the center, a corner), a 4 by 9 matrix, weighted 1000, 100, 10 and 5 by one inner product. A line through a square with sum 2 (or -2) has two marks and one empty square, which must be that one.
ˡa̲i ← { b → p ← ˡt̲urn b f ← (b ˡn̲ear 2 × p) c̲at (b ˡn̲ear -2 × p) c̲at (5 = r̲ange 9) c̲at (r̲ange 9) m̲ember? 1 3 7 9 r ← (b = 0) × 1 + 1000 100 10 5 '+ '× i̲nner 4 9 r̲eshape f f̲irst w̲here r = 'm̲ax r̲/ r }
ˡv̲alue : (Num a, Truthy a, Num b, Truthy b) => a -> b
A perfect player: minimax, written as negamax. A board's value with best play (1 X wins, -1 O wins, 0 a draw) is the winner if there is one, 0 when the board is full, else the mover's best: the mover times the largest of the mover times every move's value (every legal move tried, by recursion).
ˡv̲alue ← { b → w ← ˡw̲inner b w ≠ 0 ? w e ← ˡl̲egal b 0 = t̲ally e ? 0 p ← ˡt̲urn b p × 'm̲ax r̲/ p × '{ ˡv̲alue b ˡm̲ove ⍵ } e̲ach e }