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';
(function(factory) {
if (typeof define !== 'undefined' && define.amd) {
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(){
import {Iterator} from './iterator.js';
/* Game states */
const PLAYING = 'playing';
@ -136,6 +127,10 @@ class Board {
}
}
toString() {
return new Buffer(this._board).toString('hex')
}
get phantom() {
let phantom = this._phantom;
if (phantom) {
@ -236,7 +231,7 @@ class Board {
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) {
let there = offsetSquare(from, columnsRight, rowsUp);
@ -244,7 +239,7 @@ class Board {
return;
}
accum.push(there);
yield there;
if (remainder < 1 || this.getPiece(otherSide(side), there) !== EMPTY) {
return;
@ -255,27 +250,42 @@ class Board {
}
}
findPieces(side, type, alongside) {
*findPieces(side, type, alongside) {
const other = otherSide(side);
let pieces = [];
if (this._phantom && this._phantom.side === side && this._phantom.type === type) {
pieces.push(this._phantom.from);
function testFunction(getPiece, match) {
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 column of COLUMNS) {
const here = column + row;
if (this.getPiece(side, here) === type) {
if (!alongside || this.getPiece(other, here) === alongside) {
pieces.push(here);
if (test(here)) {
yield here;
}
}
}
}
return pieces;
}
}
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 type = board.getPiece(side, from);
if (this._status !== PLAYING || type === EMPTY) {
return [];
return;
}
if (canCapture === undefined) {
@ -364,11 +374,9 @@ class Game {
from = board.phantom.from;
}
let legals = [];
if (type === KING) {
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 */
@ -378,34 +386,34 @@ class Game {
if (this._castling[side].king &&
board.isEmpty('f' + row) &&
board.isEmpty('g' + row)) {
legals.push('g' + row);
yield 'g' + row;
}
if (this._castling[side].queen &&
board.isEmpty('d' + row) &&
board.isEmpty('c' + row) &&
board.isEmpty('b' + row)) {
legals.push('c' + row);
yield 'c' + row;
}
}
} else if (type === QUEEN) {
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) {
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) {
for (const dir of KNIGHT_DIR) {
const there = offsetSquare(from, dir[0], dir[1]);
if (there && board.validDestination(side, there, canCapture)) {
legals.push(there);
yield there;
}
}
} else if (type === ROOK) {
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) {
const dark = side === DARK;
@ -415,10 +423,10 @@ class Game {
const diagR = offsetSquare(from, 1, dark ? -1 : 1);
if (forward && board.validDestination(side, forward, false)) {
legals.push(forward);
yield forward;
if (dark ? (from[1] >= '7') : (from[1] <= '2')) {
if (forward2 && board.validDestination(side, forward2, false)) {
legals.push(forward2);
yield forward2;
}
}
}
@ -427,7 +435,7 @@ class Game {
for (const there of [diagL, diagR]) {
if (there) {
if (board.getPiece(otherSide(side), there) !== EMPTY) {
legals.push(there);
yield there;
} else if (forward2) {
let lastMove = null;
for (let i = this._moves.length; i > 0; --i) {
@ -442,7 +450,8 @@ class Game {
lastMove.from === there[0] + forward2[1] &&
lastMove.to === there[0] + from[1]) {
/* 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) {
@ -472,8 +494,7 @@ class Game {
const fromSquare = (from === PHANTOM) ? board.phantom.from : from;
const legals = this.legalMoves(side, from, alongside === EMPTY);
if (!legals.includes(to)) {
if (!this.isLegalMove(side, from, to, alongside === EMPTY)) {
throw { message: "illegal move", side: side, from: fromSquare, to: to };
}
@ -724,7 +745,7 @@ class Game {
let sameRank = 0; /* row / number */
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);
if (where[0] === move.from[0]) {
@ -783,34 +804,24 @@ class Game {
}
}
return {
Board: Board,
Game: Game,
export default {
/* Classes */
Board, Game,
PLAYING: PLAYING,
ENDED: ENDED,
/* Game States */
PLAYING, ENDED,
LIGHT: LIGHT,
DARK: DARK,
/* Sides */
LIGHT, DARK,
KING: KING,
QUEEN: QUEEN,
ROOK: ROOK,
KNIGHT: KNIGHT,
BISHOP: BISHOP,
PAWN: PAWN,
EMPTY: EMPTY,
/* Pieces */
KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN, EMPTY,
ROWS: ROWS,
COLUMNS: COLUMNS,
PHANTOM: PHANTOM,
/* Coordinates */
ROWS, COLUMNS, PHANTOM,
Util: {
otherSide: otherSide,
offsetSquare: offsetSquare,
},
/* Miscellaneous */
Util: { otherSide, offsetSquare },
};
});
/* vim:set expandtab sw=3 ts=8: */