aima-checkers
Version:
Checkers game for aima.js.
130 lines (107 loc) • 4.66 kB
JavaScript
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'
: ' '
)
)