diff --git a/js/iterator.js b/js/iterator.js new file mode 100644 index 0000000..db0c086 --- /dev/null +++ b/js/iterator.js @@ -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; diff --git a/js/pacosako.js b/js/pacosako.js index 5e563c7..633d1f9 100644 --- a/js/pacosako.js +++ b/js/pacosako.js @@ -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,26 +250,41 @@ 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; } } @@ -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,15 +450,29 @@ class Game { lastMove.from === there[0] + forward2[1] && lastMove.to === there[0] + from[1]) { /* 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) { @@ -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: */