switch scanPath and legalMoves to return generators instead of arrays
This commit is contained in:
parent
5f9ce09181
commit
b228f1f626
|
|
@ -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;
|
||||||
141
js/pacosako.js
141
js/pacosako.js
|
|
@ -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,26 +250,41 @@ 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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,15 +450,29 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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: */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue