sourcegames/tic-tac-toe/TicTacToe.xtl
1⍝# Tic-tac-toe's rules, a library: tic-tac-toe.xtl (scripted), play.xtl
2⍝# (at the terminal) and the web page all use these. The board is a
3⍝# vector of 9 squares, numbered row by row: 0 empty, 1 X, -1 O. X moves
4⍝# first.
5
6ᵇ⁼u̲se< "Board"
7
8⍝ :: Num a => Unit -> a
9⍝# The eight lines (three rows, three columns, two diagonals) as an 8 by
10⍝# 3 table of square numbers.
11ˡ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 }
12⍝ :: Num a => a -> a
13⍝# Every line's sum at once: the table used as indices into the board
14⍝# (an 8 by 3 matrix of squares), summed along each row. 3 is a line of
15⍝# X, -3 a line of O.
16ˡs̲ums ← { b → '+ r̲/₂ (ˡl̲ines @) s̲elect b }
17⍝ :: (Num a, Num b, Truthy b) => a -> b
18⍝# 1 when X has a line, -1 when O has, else 0.
19ˡw̲inner ← { b →
20 s ← ˡs̲ums b
21 (3 m̲ember? s) − -3 m̲ember? s
22}
23⍝ :: (Num a, Num b, Truthy b) => a -> b
24⍝# Whose turn: X (1) when the board has as many X as O, else O (-1).
25ˡt̲urn ← { b → 1 − 2 × 0 ≠ '+ r̲/ b }
26⍝ :: (Any a, Num b) => a -> b
27⍝# A new board: nine empty squares.
28ˡn̲ew ← { x → 9 r̲eshape 0 }
29⍝ :: Num a => a -> Int
30⍝# The empty squares, by number: the legal moves.
31ˡl̲egal ← { b → w̲here b = 0 }
32⍝ :: (Num a, Num b) => a -> b
33⍝# 0 playing, 1 X won, 2 O won, 3 a draw: the first of the three that
34⍝# holds.
35ˡs̲tatus ← { b →
36 w ← ˡw̲inner b
37 f̲irst (((w = 1) c̲at (w = -1) c̲at 0 = t̲ally ˡl̲egal b) r̲eplicate 1 2 3) c̲at 0
38}
39⍝ :: (Num a, Truthy a) => a -> Int -> a
40⍝# The player to move takes square k.
41ˡm̲ove ← { b k → b + (ˡt̲urn b) × k = r̲ange 9 }
42
43⍝ :: Num a => a -> Int
44⍝# Every square's code at once: O 1, X 2, an empty square 2 plus its
45⍝# number (so the text shows the number to type).
46ˡc̲odes ← { b → 3 3 r̲eshape (b = -1) + (2 × b = 1) + (b = 0) × 2 + r̲ange 9 }
47⍝ :: Num a => a -> Char
48⍝# The board as text, spaced (lib/Board).
49ˡv̲iew ← { b → ᵇs̲paced (ˡc̲odes b) s̲elect "OX123456789" }
50⍝ :: Num a => a -> Char
51⍝# The board as a picture X_eTaL draws (SVG text); []S_HOW shows it.
52ˡp̲icture ← { b → ⎕G̲RID (ˡc̲odes b) s̲elect "OX123456789" }
53
54⍝## Players
55⍝ :: Truthy a => Unit -> a
56⍝# Which squares lie on which lines: a 9 by 8 table (square against line),
57⍝# from a 9 by 8 by 3 table of every square against every entry.
58ˡo̲n ← { @ → '∨ r̲/₃ (r̲ange 9) '= t̲able ˡl̲ines @ }
59⍝ :: (Num a, Num b, Truthy b) => a -> a -> b
60⍝# For every square, whether a line through it sums to t.
61ˡn̲ear ← { b t → '∨ r̲/₂ (ˡo̲n @) × 9 8 r̲eshape t = ˡs̲ums b }
62⍝ :: (Num a, Truthy a) => a -> Int
63⍝# A quick player that rates every empty square at once: four features
64⍝# per square (it completes a line of the mover's, it blocks one of the
65⍝# other's, the center, a corner), a 4 by 9 matrix, weighted 1000, 100,
66⍝# 10 and 5 by one inner product. A line through a square with sum 2 (or
67⍝# -2) has two marks and one empty square, which must be that one.
68ˡa̲i ← { b →
69 p ← ˡt̲urn b
70 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
71 r ← (b = 0) × 1 + 1000 100 10 5 '+ '× i̲nner 4 9 r̲eshape f
72 f̲irst w̲here r = 'm̲ax r̲/ r
73}
74
75⍝ :: (Num a, Truthy a, Num b, Truthy b) => a -> b
76⍝# A perfect player: minimax, written as negamax. A board's value with
77⍝# best play (1 X wins, -1 O wins, 0 a draw) is the winner if there is
78⍝# one, 0 when the board is full, else the mover's best: the mover times
79⍝# the largest of the mover times every move's value (every legal move
80⍝# tried, by recursion).
81ˡv̲alue ← { b →
82 w ← ˡw̲inner b
83 w ≠ 0 ? w
84 e ← ˡl̲egal b
85 0 = t̲ally e ? 0
86 p ← ˡt̲urn b
87 p × 'm̲ax r̲/ p × '{ ˡv̲alue b ˡm̲ove ⍵ } e̲ach e
88}
89⍝ :: (Num a, Truthy a) => a -> Int
90⍝# Its move: the first legal square with the best value for the mover.
91ˡb̲est ← { b →
92 e ← ˡl̲egal b
93 v ← (ˡt̲urn b) × '{ ˡv̲alue b ˡm̲ove ⍵ } e̲ach e
94 f̲irst (v = 'm̲ax r̲/ v) r̲eplicate e
95}