-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathtick-oat-two.ts
More file actions
123 lines (102 loc) · 3.38 KB
/
tick-oat-two.ts
File metadata and controls
123 lines (102 loc) · 3.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// An AI for the TickoaTTwo game created by Oats Jenkins:
// https://www.youtube.com/watch?v=ePxrVU4M9uA
export const enum Cell {
None,
Horizontal,
Vertical,
Both,
}
export type Player = Cell.Horizontal | Cell.Vertical
export type Location = readonly [row: number, column: number]
export interface Game {
turn: Player
last: Location
readonly board: Board
}
export interface ReadonlyGame {
readonly turn: Player
readonly board: ReadonlyBoard
}
export type Board = Cell[][]
export type ReadonlyBoard = readonly (readonly Cell[])[]
export function isWin(board: ReadonlyBoard) {
return (
(board[0]![0] == Cell.Both
&& board[1]![0] == Cell.Both
&& board[2]![0] == Cell.Both)
|| (board[0]![1] == Cell.Both
&& board[1]![1] == Cell.Both
&& board[2]![1] == Cell.Both)
|| (board[0]![2] == Cell.Both
&& board[1]![2] == Cell.Both
&& board[2]![2] == Cell.Both)
|| (board[0]![0] == Cell.Both
&& board[0]![1] == Cell.Both
&& board[0]![2] == Cell.Both)
|| (board[1]![0] == Cell.Both
&& board[1]![1] == Cell.Both
&& board[1]![2] == Cell.Both)
|| (board[2]![0] == Cell.Both
&& board[2]![1] == Cell.Both
&& board[2]![2] == Cell.Both)
|| (board[0]![0] == Cell.Both
&& board[1]![1] == Cell.Both
&& board[2]![2] == Cell.Both)
|| (board[0]![2] == Cell.Both
&& board[1]![1] == Cell.Both
&& board[2]![0] == Cell.Both)
)
}
export function possibleMoves(
board: ReadonlyBoard,
forPlayer: Player,
[lastRow, lastCol]: Location = [-1, -1],
): readonly Location[] {
const output: Location[] = []
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (
(lastRow != i || lastCol != j)
&& !(board[i]![j]! & forPlayer)
) {
output.push([i, j])
}
}
}
return output
}
const characterMap = [".", "-", "|", "+"]
const toCharacter = (cell: Cell) => characterMap[cell]
const rowToCharacter = (row: readonly Cell[]) => row.map(toCharacter).join("")
export function ascii(board: ReadonlyBoard) {
return board.map(rowToCharacter).join("")
}
const LOG = false
/** Assumes that it is `forPlayer`'s turn. */
export function rate(
board: ReadonlyBoard,
forPlayer: Player,
last: Location | undefined,
): [bestMove: Location | undefined, rating: number] {
if (isWin(board)) {
if (LOG) console.log("win for", 3 - forPlayer) // TODO:
// If the other player won, return -1.
return [undefined, -1]
} else {
// Get all my moves.
const moves = possibleMoves(board, forPlayer, last)
if (LOG) console.log("moves for", forPlayer, moves) // TODO:
for (const move of moves) {
// Play each move and get the rating from the other player's POV.
const next: Board = board.map((row) => row.slice())
next[move[0]]![move[1]] |= forPlayer
const [, rating] = rate(next, 3 - forPlayer, move)
if (rating == -1) {
// The other player definitely loses; exit early.
return [move, 1]
}
}
// If the other player can win in all scenarios, we lose.
return [moves[0], -1]
}
}