switch scanPath and legalMoves to return generators instead of arrays

This commit is contained in:
Jesse D. McDonald 2020-04-04 03:10:42 -05:00
parent 5f9ce09181
commit b228f1f626
2 changed files with 212 additions and 65 deletions

136
js/iterator.js Normal file
View File

@ -0,0 +1,136 @@
'use strict';
export const IteratorMixin = {
map: function* map(f) {
let y;
while (!(y = this.next()).done) {
yield f(y.value);
}
return y.value;
},
filter: function* filter(f) {
let y;
while (!(y = this.next()).done) {
if (f(y.value)) {
yield y.value;
}
}
return y.value;
},
take: function* take(limit) {
let remaining = Number(limit) & -1;
let y;
while (remaining > 0 && !(y = this.next()).done) {
yield y.value;
remaining -= 1;
}
},
drop: function* drop(limit) {
let remaining = Number(limit) & -1;
let y;
while (!(y = this.next()).done) {
if (remaining <= 0) {
yield y.value;
} else {
remaining -= 1;
}
}
},
asIndexedPairs: function* asIndexedPairs() {
let index = 0;
let y;
while (!(y = this.next()).done) {
yield [index++, x];
}
return y.value;
},
flatMap: function* flatMap(f) {
let y;
while (!(y = this.next()).done) {
yield* f(y.value);
}
return y.value;
},
reduce: function reduce(f, state) {
if (typeof state === 'undefined') {
const first = this.next();
if (first.done) {
throw new TypeError('reduce: empty iterator');
}
state = first.value;
}
let y;
while (!(y = this.next()).done) {
state = f(state, y.value);
}
return state;
},
toArray: function toArray() {
return [...this];
},
forEach: function* forEach(f) {
let y;
while (!(y = this.next()).done) {
f(y.value);
}
},
some: function some(f) {
/* extension: if f is undefined, assume identity function */
let iter = (typeof f === 'undefined') ? this : IteratorMixin.map.call(this, f);
let y;
while (!(y = iter.next()).done) {
if (y.value) {
return true;
}
}
return false;
},
every: function every(f) {
/* extension: if f is undefined, assume identity function */
let iter = (typeof f === 'undefined') ? this : IteratorMixin.map.call(this, f);
let y;
while (!(y = iter.next()).done) {
if (!y.value) {
return false;
}
}
return true;
},
find: function find(f) {
if (typeof f === undefined) {
/* extension */
f = function identity(x) { return x; };
}
let y;
while (!(y = this.next()).done) {
if (f(y.value)) {
return y.value;
}
}
},
/* extension */
includes: function includes(x) {
return IteratorMixin.some.call(this, function equalsX(y) { return y === x; });
},
};
export const Iterator = {};
for (const fn in IteratorMixin) {
Iterator[fn] = function() {
return IteratorMixin[fn].call(...arguments);
};
}
export default Iterator;

View File

@ -1,15 +1,6 @@
'use strict'; 'use strict';
(function(factory) {
if (typeof define !== 'undefined' && define.amd) { import {Iterator} from './iterator.js';
define([], factory);
} else if (typeof define !== 'undefined' && define.petal) {
define(['paco-sako'], [], factory);
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = factory();
} else {
window.PacoSako = factory();
}
})(function(){
/* Game states */ /* Game states */
const PLAYING = 'playing'; const PLAYING = 'playing';
@ -136,6 +127,10 @@ class Board {
} }
} }
toString() {
return new Buffer(this._board).toString('hex')
}
get phantom() { get phantom() {
let phantom = this._phantom; let phantom = this._phantom;
if (phantom) { if (phantom) {
@ -236,7 +231,7 @@ class Board {
return ((theirs === EMPTY) ? (ours === EMPTY) : (canCapture ? true : false)); return ((theirs === EMPTY) ? (ours === EMPTY) : (canCapture ? true : false));
} }
scanPath(accum, side, from, canCapture, columnsRight, rowsUp, remainder) { *scanPath(side, from, canCapture, columnsRight, rowsUp, remainder) {
while (true) { while (true) {
let there = offsetSquare(from, columnsRight, rowsUp); let there = offsetSquare(from, columnsRight, rowsUp);
@ -244,7 +239,7 @@ class Board {
return; return;
} }
accum.push(there); yield there;
if (remainder < 1 || this.getPiece(otherSide(side), there) !== EMPTY) { if (remainder < 1 || this.getPiece(otherSide(side), there) !== EMPTY) {
return; return;
@ -255,27 +250,42 @@ class Board {
} }
} }
findPieces(side, type, alongside) { *findPieces(side, type, alongside) {
const other = otherSide(side); const other = otherSide(side);
let pieces = [];
if (this._phantom && this._phantom.side === side && this._phantom.type === type) { function testFunction(getPiece, match) {
pieces.push(this._phantom.from); if (match === undefined) {
return function(here) { return true; }
} else if (match === false) {
return function(here) { return getPiece(here) === EMPTY; };
} else if (match === true) {
return function(here) { return getPiece(here) !== EMPTY; };
} else {
return function(here) { return getPiece(here) === match; };
}
}
const testSide = testFunction(this.getPiece.bind(this, side), type);
const testOther = testFunction(this.getPiece.bind(this, other), alongside);
const test = function(here) { return testSide(here) && testOther(here); };
if (this._phantom && this._phantom.side === side) {
const getPhantom = (here) => this._phantom.type;
const testPhantom = testFunction(getPhantom, type);
if (testPhantom(PHANTOM)) {
yield this._phantom.from;
}
} }
for (const row of ROWS) { for (const row of ROWS) {
for (const column of COLUMNS) { for (const column of COLUMNS) {
const here = column + row; const here = column + row;
if (this.getPiece(side, here) === type) { if (test(here)) {
if (!alongside || this.getPiece(other, here) === alongside) { yield here;
pieces.push(here);
} }
} }
} }
} }
return pieces;
}
} }
const ORTHO = [[-1, 0], [1, 0], [0, -1], [0, 1]]; const ORTHO = [[-1, 0], [1, 0], [0, -1], [0, 1]];
@ -347,12 +357,12 @@ class Game {
} }
} }
legalMoves(side, from, canCapture) { *legalMoves(side, from, canCapture) {
const board = this._board; const board = this._board;
const type = board.getPiece(side, from); const type = board.getPiece(side, from);
if (this._status !== PLAYING || type === EMPTY) { if (this._status !== PLAYING || type === EMPTY) {
return []; return;
} }
if (canCapture === undefined) { if (canCapture === undefined) {
@ -364,11 +374,9 @@ class Game {
from = board.phantom.from; from = board.phantom.from;
} }
let legals = [];
if (type === KING) { if (type === KING) {
for (const dir of ANY_DIR) { for (const dir of ANY_DIR) {
board.scanPath(legals, side, from, false, dir[0], dir[1], 0); yield* board.scanPath(side, from, false, dir[0], dir[1], 0);
} }
/* check for castling conditions */ /* check for castling conditions */
@ -378,34 +386,34 @@ class Game {
if (this._castling[side].king && if (this._castling[side].king &&
board.isEmpty('f' + row) && board.isEmpty('f' + row) &&
board.isEmpty('g' + row)) { board.isEmpty('g' + row)) {
legals.push('g' + row); yield 'g' + row;
} }
if (this._castling[side].queen && if (this._castling[side].queen &&
board.isEmpty('d' + row) && board.isEmpty('d' + row) &&
board.isEmpty('c' + row) && board.isEmpty('c' + row) &&
board.isEmpty('b' + row)) { board.isEmpty('b' + row)) {
legals.push('c' + row); yield 'c' + row;
} }
} }
} else if (type === QUEEN) { } else if (type === QUEEN) {
for (const dir of ANY_DIR) { for (const dir of ANY_DIR) {
board.scanPath(legals, side, from, canCapture, dir[0], dir[1], 8); yield* board.scanPath(side, from, canCapture, dir[0], dir[1], 8);
} }
} else if (type === BISHOP) { } else if (type === BISHOP) {
for (const dir of DIAG) { for (const dir of DIAG) {
board.scanPath(legals, side, from, canCapture, dir[0], dir[1], 8); yield* board.scanPath(side, from, canCapture, dir[0], dir[1], 8);
} }
} else if (type === KNIGHT) { } else if (type === KNIGHT) {
for (const dir of KNIGHT_DIR) { for (const dir of KNIGHT_DIR) {
const there = offsetSquare(from, dir[0], dir[1]); const there = offsetSquare(from, dir[0], dir[1]);
if (there && board.validDestination(side, there, canCapture)) { if (there && board.validDestination(side, there, canCapture)) {
legals.push(there); yield there;
} }
} }
} else if (type === ROOK) { } else if (type === ROOK) {
for (const dir of ORTHO) { for (const dir of ORTHO) {
board.scanPath(legals, side, from, canCapture, dir[0], dir[1], 8); yield* board.scanPath(side, from, canCapture, dir[0], dir[1], 8);
} }
} else if (type === PAWN) { } else if (type === PAWN) {
const dark = side === DARK; const dark = side === DARK;
@ -415,10 +423,10 @@ class Game {
const diagR = offsetSquare(from, 1, dark ? -1 : 1); const diagR = offsetSquare(from, 1, dark ? -1 : 1);
if (forward && board.validDestination(side, forward, false)) { if (forward && board.validDestination(side, forward, false)) {
legals.push(forward); yield forward;
if (dark ? (from[1] >= '7') : (from[1] <= '2')) { if (dark ? (from[1] >= '7') : (from[1] <= '2')) {
if (forward2 && board.validDestination(side, forward2, false)) { if (forward2 && board.validDestination(side, forward2, false)) {
legals.push(forward2); yield forward2;
} }
} }
} }
@ -427,7 +435,7 @@ class Game {
for (const there of [diagL, diagR]) { for (const there of [diagL, diagR]) {
if (there) { if (there) {
if (board.getPiece(otherSide(side), there) !== EMPTY) { if (board.getPiece(otherSide(side), there) !== EMPTY) {
legals.push(there); yield there;
} else if (forward2) { } else if (forward2) {
let lastMove = null; let lastMove = null;
for (let i = this._moves.length; i > 0; --i) { for (let i = this._moves.length; i > 0; --i) {
@ -442,7 +450,8 @@ class Game {
lastMove.from === there[0] + forward2[1] && lastMove.from === there[0] + forward2[1] &&
lastMove.to === there[0] + from[1]) { lastMove.to === there[0] + from[1]) {
/* en passant */ /* en passant */
legals.push(there); yield there;
}
} }
} }
} }
@ -450,7 +459,20 @@ class Game {
} }
} }
return legals; isLegalMove(side, from, to, canCapture) {
const legals = this.legalMoves(side, from, canCapture);
return Iterator.some(legals, (where) => where === to);
}
isInCheck(_side) {
const side = _side || this._player;
const kings = [...this._board.findPieces(side, KING)];
if (kings.length !== 1) {
throw { message: "there should be exactly one king" };
}
const king = kings[0];
} }
move(from, to, meta) { move(from, to, meta) {
@ -472,8 +494,7 @@ class Game {
const fromSquare = (from === PHANTOM) ? board.phantom.from : from; const fromSquare = (from === PHANTOM) ? board.phantom.from : from;
const legals = this.legalMoves(side, from, alongside === EMPTY); if (!this.isLegalMove(side, from, to, alongside === EMPTY)) {
if (!legals.includes(to)) {
throw { message: "illegal move", side: side, from: fromSquare, to: to }; throw { message: "illegal move", side: side, from: fromSquare, to: to };
} }
@ -724,7 +745,7 @@ class Game {
let sameRank = 0; /* row / number */ let sameRank = 0; /* row / number */
for (const where of sameKind) { for (const where of sameKind) {
if (replay.legalMoves(move.side, where, true).includes(move.to)) { if (replay.isLegalMove(move.side, where, move.to, true)) {
legalFrom.push(where); legalFrom.push(where);
if (where[0] === move.from[0]) { if (where[0] === move.from[0]) {
@ -783,34 +804,24 @@ class Game {
} }
} }
return { export default {
Board: Board, /* Classes */
Game: Game, Board, Game,
PLAYING: PLAYING, /* Game States */
ENDED: ENDED, PLAYING, ENDED,
LIGHT: LIGHT, /* Sides */
DARK: DARK, LIGHT, DARK,
KING: KING, /* Pieces */
QUEEN: QUEEN, KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN, EMPTY,
ROOK: ROOK,
KNIGHT: KNIGHT,
BISHOP: BISHOP,
PAWN: PAWN,
EMPTY: EMPTY,
ROWS: ROWS, /* Coordinates */
COLUMNS: COLUMNS, ROWS, COLUMNS, PHANTOM,
PHANTOM: PHANTOM,
Util: { /* Miscellaneous */
otherSide: otherSide, Util: { otherSide, offsetSquare },
offsetSquare: offsetSquare,
},
}; };
});
/* vim:set expandtab sw=3 ts=8: */ /* vim:set expandtab sw=3 ts=8: */