-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.mjs
129 lines (107 loc) · 4.55 KB
/
index.mjs
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
124
125
126
127
128
129
import { Game } from 'aima'
export const checkers = new Game({
initialState: {
p: [
[0, 0, false], [0, 2, false], [0, 4, false], [0, 6, false],
[1, 1, false], [1, 3, false], [1, 5, false], [1, 7, false],
[2, 0, false], [2, 2, false], [2, 4, false], [2, 6, false]
],
q: [
[5, 1, false], [5, 3, false], [5, 5, false], [5, 7, false],
[6, 0, false], [6, 2, false], [6, 4, false], [6, 6, false],
[7, 1, false], [7, 3, false], [7, 5, false], [7, 7, false]
],
player: 'p',
opponent: 'q'
},
player: state => state.player,
actions: state => enforceJumping(state[state.player].flatMap(pos => [
...movePaths(state, pos),
...jumpPaths(state, pos)
])),
result: (state, action) => recursiveResult(state, action, true),
terminalTest: state =>
state.p.length === 0 ||
state.q.length === 0 ||
checkers.actions(state).length === 0,
utility: state => state.p.length - state.q.length,
heuristic: state => state.p.length - state.q.length
})
export const movePaths = (state, [y, x, royal]) =>
directions(royal)
.map(direction => [[y, x, royal], end(state, [y, x, royal], direction)])
.filter(([start, end]) => onBoard(end) && !occupied(state, end))
export const directions = royal => [[+1, -1], [+1, +1], ...royal ? [[-1, -1], [-1, +1]] : []]
export const end = (state, [y, x, royal], [forward, sideward], steps = 1) => [
y + forward * direction(state.player) * steps,
x + sideward * steps,
...typeof royal === 'undefined'
? []
: [crowned(state, [y, x, royal], end(state, [y, x], [forward, sideward], steps))]
]
export const crowned = (state, [y1, x1, royal], end) =>
royal ||
crownRow(state, end) ||
regicide(state, [y1, x1, royal], end)
export const crownRow = (state, [y, x]) => y === 3.5 + 3.5 * direction(state.player)
export const regicide = (state, start, end) => (dist(start, end) / 2 === 2 &&
state[state.opponent].find(pos => eq(pos, intermediate(start, end)))[2])
export const eq = ([y1, x1], [y2, x2]) => y1 === y2 && x1 === x2
export const direction = player => player === 'p' ? +1 : -1
export const dist = ([y1, x1], [y2, x2]) => Math.abs(y2 - y1) + Math.abs(x2 - x1)
export const intermediate = ([y1, x1], [y2, x2]) => [(y1 + y2) / 2, (x1 + x2) / 2]
export const onBoard = ([y, x]) => y >= 0 && y <= 7 && x >= 0 && x <= 7
export const occupied = (state, pos) => ['p', 'q'].some(p => occupiedBy(state, pos, p))
export const occupiedBy = (state, posA, player) => state[player].some(posB => eq(posA, posB))
export const jumpPaths = (state, start) =>
jumps(state, start)
.flatMap(([start, end]) => [
[start, end],
...!crownRow(state, end) || start[2] /* royal */ || regicide(state, start, end)
? [...jumpPaths(/* state = */ stepResult(state, [start, end], false), /* start = */ end)
.map(jumpPath => [start, ...jumpPath])]
: []
])
export const jumps = (state, [y, x, royal]) =>
directions(royal)
.map(direction => [[y, x, royal], end(state, [y, x], direction, 2)])
.filter(([start, end]) =>
onBoard(end) &&
!occupied(state, end) &&
occupiedBy(state, intermediate(start, end), state.opponent))
.map(([start, end]) => [start, [...end, crowned(state, start, end)]])
export const stepResult = (state, [start, end], nextPlayer) => ({
[state.player]: [...state[state.player].filter(pos => !eq(pos, start)), end],
[state.opponent]: state[state.opponent].filter(pos => !casualty(pos, [start, end])),
player: nextPlayer ? state.opponent : state.player,
opponent: nextPlayer ? state.player : state.opponent
})
export const casualty = (pos, action) =>
dist(...action) / 2 === 2 && eq(pos, intermediate(...action))
export const enforceJumping = actions => actions.filter(([start, end]) =>
!actions.some(([start, end]) => dist(start, end) / 2 === 2) ||
dist(start, end) / 2 === 2
)
export const recursiveResult = (state, action, nextPlayer = false) =>
action.length >= 2
? stepResult(
recursiveResult(state, action.slice(0, action.length - 1)),
[action[action.length - 2], action[action.length - 1]],
nextPlayer
)
: state
export const boardString = state => board(state).map(row => row.join('')).reverse().join('\n')
export const board = state =>
[0, 1, 2, 3, 4, 5, 6, 7].map(y =>
[0, 1, 2, 3, 4, 5, 6, 7].map(x =>
state.p.some(pos => eq(pos, [y, x]))
? state.p.find(pos => eq(pos, [y, x]))[2]
? 'X'
: 'x'
: state.q.some(pos => eq(pos, [y, x]))
? state.q.find(pos => eq(pos, [y, x]))[2]
? 'O'
: 'o'
: ' '
)
)