major rewrite; breaking change; game logic in separate file
This commit is contained in:
parent
0d91a2baca
commit
a96e8f1e4f
24
index.html
24
index.html
|
|
@ -245,26 +245,6 @@
|
||||||
<div id="cb_piece_pl"><img src="svg/Chess_plt45.svg" alt="pl" draggable="true" class="cb-piece cb-lt-piece cb-pawn"></div>
|
<div id="cb_piece_pl"><img src="svg/Chess_plt45.svg" alt="pl" draggable="true" class="cb-piece cb-lt-piece cb-pawn"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 1em; display: none">
|
|
||||||
<form id="sign" onsubmit="return false;">
|
|
||||||
<input id="alias" placeholder="username">
|
|
||||||
<input id="pass" type="password" placeholder="passphrase">
|
|
||||||
<input id="in" type="submit" value="sign in">
|
|
||||||
<input id="up" type="button" value="sign up">
|
|
||||||
<p id="message" style="display: none"></p>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div id="todo" style="display: none">
|
|
||||||
<ul id="todo_list"></ul>
|
|
||||||
|
|
||||||
<form id="said" onsubmit="return false;">
|
|
||||||
<input id="say">
|
|
||||||
<input id="speak" type="submit" value="speak">
|
|
||||||
<input id="sign_out" type="button" value="sign out">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="jquery-ui/external/jquery/jquery.js"></script>
|
<script src="jquery-ui/external/jquery/jquery.js"></script>
|
||||||
|
|
@ -283,8 +263,8 @@
|
||||||
console.log = console.real_log;
|
console.log = console.real_log;
|
||||||
</script>
|
</script>
|
||||||
<script src="node-deep-equal/index.js"></script>
|
<script src="node-deep-equal/index.js"></script>
|
||||||
<script src="js/chess.js"></script>
|
<script src="js/pacosako.js"></script>
|
||||||
<script src="js/todo.js"></script>
|
<script src="js/pacosako_ui.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
<!-- vim:set expandtab sw=3 ts=8: -->
|
<!-- vim:set expandtab sw=3 ts=8: -->
|
||||||
|
|
|
||||||
1171
js/chess.js
1171
js/chess.js
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,804 @@
|
||||||
|
'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(){
|
||||||
|
|
||||||
|
/* Game states */
|
||||||
|
const PLAYING = 'playing';
|
||||||
|
const ENDED = 'ended';
|
||||||
|
|
||||||
|
/* Sides */
|
||||||
|
const LIGHT = 'light';
|
||||||
|
const DARK = 'dark';
|
||||||
|
|
||||||
|
/* Pieces */
|
||||||
|
const KING = 'k';
|
||||||
|
const QUEEN = 'q';
|
||||||
|
const ROOK = 'r';
|
||||||
|
const KNIGHT = 'n';
|
||||||
|
const BISHOP = 'b';
|
||||||
|
const PAWN = 'p';
|
||||||
|
const EMPTY = ' ';
|
||||||
|
|
||||||
|
const ALL_PIECES = EMPTY + PAWN + BISHOP + KNIGHT + ROOK + QUEEN + KING;
|
||||||
|
|
||||||
|
/* Special squares */
|
||||||
|
const PHANTOM = 'phantom';
|
||||||
|
|
||||||
|
/* a1...h1 and a8...h8 */
|
||||||
|
const INITIAL_EDGE_ROW = ROOK + KNIGHT + BISHOP + QUEEN + KING + BISHOP + KNIGHT + ROOK;
|
||||||
|
|
||||||
|
const ROWS = '12345678';
|
||||||
|
const COLUMNS = 'abcdefgh';
|
||||||
|
|
||||||
|
function otherSide(side) {
|
||||||
|
if (side === LIGHT) {
|
||||||
|
return DARK;
|
||||||
|
} else if (side === DARK) {
|
||||||
|
return LIGHT;
|
||||||
|
} else {
|
||||||
|
throw { message: 'invalid side', side: side };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function squareIndex(square) {
|
||||||
|
if (typeof square !== 'string' || square.length !== 2) {
|
||||||
|
throw { message: 'invalid square', square: square };
|
||||||
|
}
|
||||||
|
|
||||||
|
const column = COLUMNS.indexOf(square[0].toLowerCase());
|
||||||
|
const row = ROWS.indexOf(square[1]);
|
||||||
|
|
||||||
|
if (column < 0 || row < 0) {
|
||||||
|
throw { message: 'invalid square', square: square };
|
||||||
|
}
|
||||||
|
|
||||||
|
return (row * 8) + column;
|
||||||
|
}
|
||||||
|
|
||||||
|
function offsetSquare(from, columnsRight, rowsUp) {
|
||||||
|
let index = squareIndex(from);
|
||||||
|
let column = (index & 0b111) + columnsRight;
|
||||||
|
let row = (index >>> 3) + rowsUp;
|
||||||
|
|
||||||
|
if (column >= 0 && column < 8 && row >= 0 && row < 8) {
|
||||||
|
return COLUMNS[column] + ROWS[row];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodePiece(side, type) {
|
||||||
|
let result = ALL_PIECES.indexOf(type);
|
||||||
|
|
||||||
|
if (result < 0) {
|
||||||
|
throw { message: 'invalid piece', piece: type };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (side === LIGHT) {
|
||||||
|
return (result << 4);
|
||||||
|
} else if (side === DARK) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw { message: 'invalid side', side: side };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodePiece(side, value) {
|
||||||
|
let sideShift = null;
|
||||||
|
|
||||||
|
if (side === LIGHT) {
|
||||||
|
sideShift = 4;
|
||||||
|
} else if (side === DARK) {
|
||||||
|
sideShift = 0;
|
||||||
|
} else {
|
||||||
|
throw { message: 'invalid side', side: side };
|
||||||
|
}
|
||||||
|
|
||||||
|
let pieceValue = (value >>> sideShift) & 0b1111;
|
||||||
|
|
||||||
|
if (pieceValue < ALL_PIECES.length) {
|
||||||
|
return ALL_PIECES[pieceValue];
|
||||||
|
} else {
|
||||||
|
throw { message: 'invalid encoded piece', value: pieceValue };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Board {
|
||||||
|
constructor(original) {
|
||||||
|
if (original !== null && original !== undefined) {
|
||||||
|
if (!(original instanceof this.constructor)) {
|
||||||
|
throw { message: 'can only clone from another Board instance' };
|
||||||
|
}
|
||||||
|
|
||||||
|
this._board = new Uint8Array(original._board);
|
||||||
|
this._phantom = JSON.parse(JSON.stringify(original._phantom));
|
||||||
|
} else {
|
||||||
|
this._board = new Uint8Array(64);
|
||||||
|
this._board.fill(0);
|
||||||
|
this._phantom = null;
|
||||||
|
|
||||||
|
for (let column = 0; column < 8; ++column) {
|
||||||
|
const letter = COLUMNS[column];
|
||||||
|
this.putPiece(DARK, letter + '8', INITIAL_EDGE_ROW[column]);
|
||||||
|
this.putPiece(DARK, letter + '7', PAWN);
|
||||||
|
this.putPiece(LIGHT, letter + '2', PAWN);
|
||||||
|
this.putPiece(LIGHT, letter + '1', INITIAL_EDGE_ROW[column]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get phantom() {
|
||||||
|
let phantom = this._phantom;
|
||||||
|
if (phantom) {
|
||||||
|
return { side: phantom.side, type: phantom.type, from: phantom.from };
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPiece(side, square) {
|
||||||
|
if (square === PHANTOM) {
|
||||||
|
if (this._phantom && this._phantom.side === side) {
|
||||||
|
return this._phantom.type;
|
||||||
|
} else {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodePiece(side, this._board[squareIndex(square)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty(square) {
|
||||||
|
if (square === PHANTOM) {
|
||||||
|
return this._phantom === null;
|
||||||
|
} else {
|
||||||
|
return this._board[squareIndex(square)] === 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
putPiece(side, square, piece) {
|
||||||
|
const other = otherSide(side);
|
||||||
|
const otherPiece = this.getPiece(other, square);
|
||||||
|
const together = encodePiece(side, piece) | encodePiece(other, otherPiece);
|
||||||
|
this._board[squareIndex(square)] = together;
|
||||||
|
}
|
||||||
|
|
||||||
|
makePhantom(side, square) {
|
||||||
|
const type = this.getPiece(side, square);
|
||||||
|
|
||||||
|
if (type !== EMPTY) {
|
||||||
|
if (this._phantom) {
|
||||||
|
throw { message: 'phantom square is already occupied' };
|
||||||
|
}
|
||||||
|
|
||||||
|
this.putPiece(side, square, EMPTY);
|
||||||
|
|
||||||
|
this._phantom = {
|
||||||
|
side: side,
|
||||||
|
type: type,
|
||||||
|
from: square,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
move(from, to) {
|
||||||
|
if (this._phantom && from !== PHANTOM) {
|
||||||
|
throw { message: 'must complete prior move before moving other pieces' };
|
||||||
|
}
|
||||||
|
|
||||||
|
let lightPiece = this.getPiece(LIGHT, from);
|
||||||
|
let darkPiece = this.getPiece(DARK, from);
|
||||||
|
|
||||||
|
if (lightPiece === EMPTY && darkPiece === EMPTY) {
|
||||||
|
throw { message: 'cannot move from empty square' };
|
||||||
|
} else if (lightPiece !== EMPTY && darkPiece !== EMPTY) {
|
||||||
|
if (!this.isEmpty(to)) {
|
||||||
|
throw { message: 'cannot capture with joined pieces' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromIndex = squareIndex(from);
|
||||||
|
const toIndex = squareIndex(to);
|
||||||
|
this._board[toIndex] = this._board[fromIndex];
|
||||||
|
this._board[fromIndex] = 0;
|
||||||
|
} else {
|
||||||
|
const moving = (lightPiece === EMPTY) ? DARK : LIGHT;
|
||||||
|
const movingPiece = (lightPiece === EMPTY) ? darkPiece : lightPiece;
|
||||||
|
const other = (lightPiece === EMPTY) ? LIGHT : DARK;
|
||||||
|
const displaced = this.getPiece(moving, to);
|
||||||
|
|
||||||
|
if (displaced !== EMPTY && this.getPiece(other, to) === EMPTY) {
|
||||||
|
throw { message: 'cannot join with piece of same color' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from === PHANTOM) {
|
||||||
|
this._phantom = null;
|
||||||
|
} else {
|
||||||
|
this.putPiece(moving, from, EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.makePhantom(moving, to);
|
||||||
|
this.putPiece(moving, to, movingPiece);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validDestination(side, where, canCapture) {
|
||||||
|
let ours = this.getPiece(side, where);
|
||||||
|
let theirs = this.getPiece(otherSide(side), where);
|
||||||
|
return ((theirs === EMPTY) ? (ours === EMPTY) : (canCapture ? true : false));
|
||||||
|
}
|
||||||
|
|
||||||
|
scanPath(accum, side, from, canCapture, columnsRight, rowsUp, remainder) {
|
||||||
|
while (true) {
|
||||||
|
let there = offsetSquare(from, columnsRight, rowsUp);
|
||||||
|
|
||||||
|
if (!there || !this.validDestination(side, there, canCapture)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
accum.push(there);
|
||||||
|
|
||||||
|
if (remainder < 1 || this.getPiece(otherSide(side), there) !== EMPTY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
from = there;
|
||||||
|
remainder -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pieces;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ORTHO = [[-1, 0], [1, 0], [0, -1], [0, 1]];
|
||||||
|
const DIAG = [[-1, -1], [-1, 1], [1, -1], [1, 1]];
|
||||||
|
const ANY_DIR = ORTHO.concat(DIAG);
|
||||||
|
|
||||||
|
const KNIGHT_DIR =
|
||||||
|
[[-1, 2], [ 1, 2],
|
||||||
|
[-2, 1], [ 2, 1],
|
||||||
|
[-2, -1], [ 2, -1],
|
||||||
|
[-1, -2], [ 1, -2]];
|
||||||
|
|
||||||
|
const NBSP = '\u00a0'; /* non-breaking space */
|
||||||
|
const SHY = '\u00ad' /* soft hyphen */
|
||||||
|
const ZWSP = '\u200b'; /* zero-width space */
|
||||||
|
|
||||||
|
class Game {
|
||||||
|
constructor(original) {
|
||||||
|
if (original !== undefined) {
|
||||||
|
if (!(original instanceof this.constructor)) {
|
||||||
|
throw { message: 'can only clone from another Game instance' };
|
||||||
|
}
|
||||||
|
|
||||||
|
this._board = new Board(original._board);
|
||||||
|
this._player = original._player;
|
||||||
|
this._status = original._status;
|
||||||
|
this._moves = JSON.parse(JSON.stringify(original._moves));
|
||||||
|
this._redo = JSON.parse(JSON.stringify(original._redo));
|
||||||
|
this._castling = JSON.parse(JSON.stringify(original._castling));
|
||||||
|
} else {
|
||||||
|
this._board = new Board();
|
||||||
|
this._player = LIGHT;
|
||||||
|
this._status = PLAYING;
|
||||||
|
this._moves = [];
|
||||||
|
this._redo = [];
|
||||||
|
|
||||||
|
/* set to false when the king or rook moves */
|
||||||
|
this._castling = {};
|
||||||
|
this._castling[LIGHT] = { king: true, queen: true };
|
||||||
|
this._castling[DARK] = { king: true, queen: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get board() {
|
||||||
|
return new Board(this._board);
|
||||||
|
}
|
||||||
|
|
||||||
|
get player() {
|
||||||
|
return this._player;
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return this._status;
|
||||||
|
}
|
||||||
|
|
||||||
|
get moves() {
|
||||||
|
return JSON.parse(JSON.stringify(this._moves));
|
||||||
|
}
|
||||||
|
|
||||||
|
get redoMoves() {
|
||||||
|
return JSON.parse(JSON.stringify(this._redo));
|
||||||
|
}
|
||||||
|
|
||||||
|
legalMoves(side, from, canCapture) {
|
||||||
|
const board = this._board;
|
||||||
|
const type = board.getPiece(side, from);
|
||||||
|
|
||||||
|
if (this._status !== PLAYING || type === EMPTY) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from === PHANTOM) {
|
||||||
|
/* this is valid because board.getPiece(side, PHANTOM) !== EMPTY */
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for castling conditions */
|
||||||
|
if (from === (side === DARK ? 'e8' : 'e1')) {
|
||||||
|
const row = from[1];
|
||||||
|
|
||||||
|
if (this._castling[side].king &&
|
||||||
|
board.isEmpty('f' + row) &&
|
||||||
|
board.isEmpty('g' + row)) {
|
||||||
|
legals.push('g' + row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._castling[side].queen &&
|
||||||
|
board.isEmpty('d' + row) &&
|
||||||
|
board.isEmpty('c' + row) &&
|
||||||
|
board.isEmpty('b' + row)) {
|
||||||
|
legals.push('c' + row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type === QUEEN) {
|
||||||
|
for (const dir of ANY_DIR) {
|
||||||
|
board.scanPath(legals, 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);
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type === ROOK) {
|
||||||
|
for (const dir of ORTHO) {
|
||||||
|
board.scanPath(legals, side, from, canCapture, dir[0], dir[1], 8);
|
||||||
|
}
|
||||||
|
} else if (type === PAWN) {
|
||||||
|
const dark = side === DARK;
|
||||||
|
const forward = offsetSquare(from, 0, dark ? -1 : 1);
|
||||||
|
const forward2 = offsetSquare(from, 0, dark ? -2 : 2);
|
||||||
|
const diagL = offsetSquare(from, -1, dark ? -1 : 1);
|
||||||
|
const diagR = offsetSquare(from, 1, dark ? -1 : 1);
|
||||||
|
|
||||||
|
if (forward && board.validDestination(side, forward, false)) {
|
||||||
|
legals.push(forward);
|
||||||
|
if (dark ? (from[1] >= '7') : (from[1] <= '2')) {
|
||||||
|
if (forward2 && board.validDestination(side, forward2, false)) {
|
||||||
|
legals.push(forward2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canCapture) {
|
||||||
|
for (const there of [diagL, diagR]) {
|
||||||
|
if (there) {
|
||||||
|
if (board.getPiece(otherSide(side), there) !== EMPTY) {
|
||||||
|
legals.push(there);
|
||||||
|
} else if (forward2) {
|
||||||
|
let lastMove = null;
|
||||||
|
for (let i = this._moves.length; i > 0; --i) {
|
||||||
|
const move = this._moves[i - 1];
|
||||||
|
if (move.side !== this._player) {
|
||||||
|
lastMove = move;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastMove && lastMove.type === PAWN &&
|
||||||
|
lastMove.from === there[0] + forward2[1] &&
|
||||||
|
lastMove.to === there[0] + from[1]) {
|
||||||
|
/* en passant */
|
||||||
|
legals.push(there);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return legals;
|
||||||
|
}
|
||||||
|
|
||||||
|
move(from, to, meta) {
|
||||||
|
if (this._status !== PLAYING) {
|
||||||
|
throw { message: "can't move, game is already over" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const side = this._player;
|
||||||
|
const other = otherSide(side);
|
||||||
|
const board = this._board;
|
||||||
|
const type = board.getPiece(side, from);
|
||||||
|
const took = board.getPiece(other, to);
|
||||||
|
const replaced = board.getPiece(side, to);
|
||||||
|
const alongside = board.getPiece(other, from);
|
||||||
|
|
||||||
|
if (from === PHANTOM && !board.phantom) {
|
||||||
|
throw { message: "attempted to continue a completed move" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromSquare = (from === PHANTOM) ? board.phantom.from : from;
|
||||||
|
|
||||||
|
const legals = this.legalMoves(side, from, alongside === EMPTY);
|
||||||
|
if (!legals.includes(to)) {
|
||||||
|
throw { message: "illegal move", side: side, from: fromSquare, to: to };
|
||||||
|
}
|
||||||
|
|
||||||
|
const move = {
|
||||||
|
side: side,
|
||||||
|
type: type,
|
||||||
|
from: fromSquare,
|
||||||
|
to: to,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (from === PHANTOM) {
|
||||||
|
move.phantom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (took !== EMPTY) {
|
||||||
|
move.took = took;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replaced !== EMPTY) {
|
||||||
|
move.replaced = replaced;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alongside !== EMPTY) {
|
||||||
|
move.alongside = alongside;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meta !== undefined) {
|
||||||
|
move.meta = JSON.parse(JSON.stringify(meta));
|
||||||
|
}
|
||||||
|
|
||||||
|
board.move(from, to);
|
||||||
|
|
||||||
|
if (type === KING) {
|
||||||
|
/* we already checked that this is a legal move, so it must be castling */
|
||||||
|
if (from[0] === 'e' && to[0] === 'g') {
|
||||||
|
/* move the rook & any paired piece */
|
||||||
|
board.move('h' + from[1], 'f' + from[1]);
|
||||||
|
move.castle = true;
|
||||||
|
}
|
||||||
|
else if (from[0] === 'e' && to[0] === 'c') {
|
||||||
|
/* move the rook & any paired piece */
|
||||||
|
board.move('a' + from[1], 'd' + from[1]);
|
||||||
|
move.queen_castle = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* can't castle after moving the king */
|
||||||
|
this._castling[side].king = false;
|
||||||
|
this._castling[side].queen = false;
|
||||||
|
} else if (type === ROOK) {
|
||||||
|
/* can't castle after moving the rook */
|
||||||
|
if (from[0] === 'h') {
|
||||||
|
this._castling[side].king = false;
|
||||||
|
} else if (from[0] === 'a') {
|
||||||
|
this._castling[side].queen = false;
|
||||||
|
}
|
||||||
|
} else if (type === PAWN) {
|
||||||
|
if (fromSquare[0] !== to[0] && took === EMPTY) {
|
||||||
|
/* legal diagonal move but nothing at destination; must be en passant */
|
||||||
|
move.en_passant = true;
|
||||||
|
move.took = PAWN;
|
||||||
|
|
||||||
|
/* the location of the captured pawn */
|
||||||
|
const where = to[0] + fromSquare[1];
|
||||||
|
|
||||||
|
/* move the opponent's pawn back one space to join our pawn */
|
||||||
|
board.putPiece(other, where, EMPTY);
|
||||||
|
board.putPiece(other, to, PAWN);
|
||||||
|
|
||||||
|
/* see if we replaced a piece from the other square */
|
||||||
|
board.makePhantom(side, where);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to[1] === (side === DARK ? '1' : '8')) {
|
||||||
|
move.promotion = QUEEN; /* TODO: allow other choices */
|
||||||
|
board.putPiece(side, to, move.promotion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alongside === PAWN && to[1] === (other === DARK ? '1' : '8')) {
|
||||||
|
move.promotion = QUEEN; /* TODO: allow other choices */
|
||||||
|
board.putPiece(other, to, move.promotion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (board.phantom === null) {
|
||||||
|
this._player = otherSide(this._player);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (took === KING) {
|
||||||
|
this._status = ENDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._moves.push(move);
|
||||||
|
this._redo = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
resign(meta) {
|
||||||
|
if (this._status !== PLAYING) {
|
||||||
|
throw { message: "can't resign, game is already over" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const move = {
|
||||||
|
side: this._player,
|
||||||
|
resign: true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (meta !== undefined) {
|
||||||
|
move.meta = meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._status = ENDED;
|
||||||
|
this._moves.push(move);
|
||||||
|
this._redo = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get lastMove() {
|
||||||
|
if (this._moves.length > 0) {
|
||||||
|
return JSON.parse(JSON.stringify(this._moves[this._moves.length - 1]));
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get winner() {
|
||||||
|
const move = this.lastMove;
|
||||||
|
if (move && move.resign) {
|
||||||
|
return otherSide(this.lastMove.side);
|
||||||
|
} else if (move && move.took === KING) {
|
||||||
|
return move.side;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replayMove(move) {
|
||||||
|
if (!move || move.side !== this._player) {
|
||||||
|
throw { message: "other player's move", move: move };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move.resign) {
|
||||||
|
this.resign();
|
||||||
|
} else if (move.phantom) {
|
||||||
|
this.move(PHANTOM, move.to, move.meta);
|
||||||
|
} else {
|
||||||
|
this.move(move.from, move.to, move.meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get canUndo() {
|
||||||
|
return this._moves.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get canRedo() {
|
||||||
|
return this._redo.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
undo() {
|
||||||
|
if (this.canUndo) {
|
||||||
|
/* preserve the last move and redo history */
|
||||||
|
const lastMove = this._moves[this._moves.length - 1];
|
||||||
|
const savedRedo = this._redo;
|
||||||
|
|
||||||
|
/* replay all moves except the last in a new game object */
|
||||||
|
const replay = new this.constructor();
|
||||||
|
for (let i = 0; i < this._moves.length - 1; ++i) {
|
||||||
|
replay.replayMove(this._moves[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* copy all the properties from the replayed game to this one */
|
||||||
|
for (const prop in replay) {
|
||||||
|
this[prop] = replay[prop];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* restore the original redo history and add the undone move */
|
||||||
|
this._redo = savedRedo;
|
||||||
|
this._redo.push(lastMove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redo() {
|
||||||
|
if (this.canRedo) {
|
||||||
|
const savedRedo = this._redo;
|
||||||
|
this.replayMove(savedRedo.pop());
|
||||||
|
this._redo = savedRedo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearRedo() {
|
||||||
|
this._redo = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
countTurns() {
|
||||||
|
let n = 0;
|
||||||
|
let player = null;
|
||||||
|
|
||||||
|
for (const move of this._moves) {
|
||||||
|
/* multiple consecutive moves by the same player are a single turn */
|
||||||
|
if (move.side !== player) {
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
|
||||||
|
player = move.side;
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHistory() {
|
||||||
|
let replay = new Game();
|
||||||
|
let result = '';
|
||||||
|
let n = 0;
|
||||||
|
|
||||||
|
for (const move of this._moves) {
|
||||||
|
if (move.phantom) {
|
||||||
|
result += SHY + '*';
|
||||||
|
} else {
|
||||||
|
if (n > 0 || move.side === DARK) {
|
||||||
|
result += ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move.side === LIGHT) {
|
||||||
|
++n;
|
||||||
|
result += String(n) + '.' + NBSP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move.castle) {
|
||||||
|
result += 'O-O';
|
||||||
|
} else if (move.queen_castle) {
|
||||||
|
result += 'O-O-O';
|
||||||
|
} else if (move.side && move.type && move.from && move.to) {
|
||||||
|
let piece = '';
|
||||||
|
|
||||||
|
if (move.alongside) {
|
||||||
|
if (move.side === LIGHT) {
|
||||||
|
piece = move.type.toUpperCase() + move.alongside.toUpperCase();
|
||||||
|
} else {
|
||||||
|
piece = move.alongside.toUpperCase() + move.type.toUpperCase();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
piece = move.type === PAWN ? '' : move.type.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the second condition below is for en passant of a joined piece */
|
||||||
|
if (!move.phantom || move.from !== replay.lastMove.to) {
|
||||||
|
const sameKind = replay.board.findPieces(move.side, move.type, move.alongside || EMPTY);
|
||||||
|
const legalFrom = [];
|
||||||
|
let sameFile = 0; /* column / letter */
|
||||||
|
let sameRank = 0; /* row / number */
|
||||||
|
|
||||||
|
for (const where of sameKind) {
|
||||||
|
if (replay.legalMoves(move.side, where, true).includes(move.to)) {
|
||||||
|
legalFrom.push(where);
|
||||||
|
|
||||||
|
if (where[0] === move.from[0]) {
|
||||||
|
sameFile += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (where[1] === move.from[1]) {
|
||||||
|
sameRank += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* always disambiguate captures by pawns (standard convention) */
|
||||||
|
if (legalFrom.length !== 1 || (move.type === PAWN && move.took)) {
|
||||||
|
/* append file, rank, or both to disambiguate */
|
||||||
|
if (sameFile === 1) {
|
||||||
|
piece += move.from[0];
|
||||||
|
} else if (sameRank === 1) {
|
||||||
|
piece += move.from[1];
|
||||||
|
} else {
|
||||||
|
piece += move.from;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const took = move.took ? 'x' : '';
|
||||||
|
result += piece + took + move.to;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move.en_passant) {
|
||||||
|
result += 'e.p.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move.promotion) {
|
||||||
|
result += '(' + move.promotion.toUpperCase() + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move.took === KING) {
|
||||||
|
result += '#';
|
||||||
|
}
|
||||||
|
|
||||||
|
replay.replayMove(move);
|
||||||
|
}
|
||||||
|
|
||||||
|
let winner = replay.winner;
|
||||||
|
|
||||||
|
if (winner === LIGHT) {
|
||||||
|
result += ' 1-0';
|
||||||
|
} else if (winner === DARK) {
|
||||||
|
result += ' 0-1';
|
||||||
|
} else if (replay.status !== PLAYING) {
|
||||||
|
result += ' \u00bd-\u00bd'; /* 1/2-1/2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
Board: Board,
|
||||||
|
Game: Game,
|
||||||
|
|
||||||
|
PLAYING: PLAYING,
|
||||||
|
ENDED: ENDED,
|
||||||
|
|
||||||
|
LIGHT: LIGHT,
|
||||||
|
DARK: DARK,
|
||||||
|
|
||||||
|
KING: KING,
|
||||||
|
QUEEN: QUEEN,
|
||||||
|
ROOK: ROOK,
|
||||||
|
KNIGHT: KNIGHT,
|
||||||
|
BISHOP: BISHOP,
|
||||||
|
PAWN: PAWN,
|
||||||
|
EMPTY: EMPTY,
|
||||||
|
|
||||||
|
ROWS: ROWS,
|
||||||
|
COLUMNS: COLUMNS,
|
||||||
|
PHANTOM: PHANTOM,
|
||||||
|
|
||||||
|
Util: {
|
||||||
|
otherSide: otherSide,
|
||||||
|
offsetSquare: offsetSquare,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/* vim:set expandtab sw=3 ts=8: */
|
||||||
|
|
@ -0,0 +1,757 @@
|
||||||
|
'use strict';
|
||||||
|
$(function (){
|
||||||
|
let gun = Gun({
|
||||||
|
peers: ['https://jessemcdonald.info/gun'],
|
||||||
|
/* workaround for persistent errors accumulating which break synchronization */
|
||||||
|
//localStorage: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const PS = window.PacoSako;
|
||||||
|
let currentGame = new PS.Game();
|
||||||
|
let visibleGame = new PS.Game(currentGame);
|
||||||
|
let cancelGameCallback = function() {};
|
||||||
|
let cancelMetaCallback = function() {};
|
||||||
|
|
||||||
|
/* for debug */
|
||||||
|
window.getCurrentGame = function() { return currentGame; }
|
||||||
|
window.getVisibleGame = function() { return visibleGame; }
|
||||||
|
function debug() { console.log.apply(console, arguments); }
|
||||||
|
|
||||||
|
function pieceTypeCode(type) {
|
||||||
|
if (type === PS.KING) {
|
||||||
|
return 'k';
|
||||||
|
} else if (type === PS.QUEEN) {
|
||||||
|
return 'q';
|
||||||
|
} else if (type === PS.ROOK) {
|
||||||
|
return 'r';
|
||||||
|
} else if (type === PS.KNIGHT) {
|
||||||
|
return 'n';
|
||||||
|
} else if (type === PS.BISHOP) {
|
||||||
|
return 'b';
|
||||||
|
} else if (type === PS.PAWN) {
|
||||||
|
return 'p';
|
||||||
|
} else {
|
||||||
|
throw { message: 'unknown piece type', type: type };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pieceSideCode(side) {
|
||||||
|
if (side === PS.LIGHT) {
|
||||||
|
return 'l';
|
||||||
|
} else if (side === PS.DARK) {
|
||||||
|
return 'd';
|
||||||
|
} else {
|
||||||
|
throw { message: 'unknown side', side: side };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pieceCode(side, type) {
|
||||||
|
return pieceTypeCode(type) + pieceSideCode(side);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cbSquare(where) {
|
||||||
|
if (where === PS.PHANTOM) {
|
||||||
|
return $('#cb_phantom').first()
|
||||||
|
} else {
|
||||||
|
return $('#cb_' + where).first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pieceStartDrag(ev, ui) {
|
||||||
|
const dragged = $(this);
|
||||||
|
const side = dragged.data('side');
|
||||||
|
const type = dragged.data('type');
|
||||||
|
const from = dragged.data('location');
|
||||||
|
const board = currentGame.board;
|
||||||
|
const where = (from === PS.PHANTOM) ? board.phantom.from : from;
|
||||||
|
const canCapture = (from === PS.PHANTOM) ||
|
||||||
|
board.getPiece(PS.Util.otherSide(side), where) === PS.EMPTY;
|
||||||
|
const legals = currentGame.legalMoves(side, from, canCapture);
|
||||||
|
for (const there of legals) {
|
||||||
|
cbSquare(there).addClass('cb-legal').droppable('enable');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pieceStopDrag(ev, ui) {
|
||||||
|
$('#cb_board .cb-legal').removeClass('cb-legal').droppable('disable');
|
||||||
|
}
|
||||||
|
|
||||||
|
function placePiece(where, side, type, suffix) {
|
||||||
|
const code = pieceCode(side, type);
|
||||||
|
const piece_id = 'cb_piece_' + code + '_' + suffix;
|
||||||
|
const piece = $($('#' + piece_id)[0] || $('#cb_piece_' + code + ' img').clone());
|
||||||
|
piece.attr('style', '');
|
||||||
|
piece.attr('id', piece_id);
|
||||||
|
piece.data({ side: side, type: type, location: where });
|
||||||
|
piece.appendTo(cbSquare(where));
|
||||||
|
piece.draggable({
|
||||||
|
disabled: true,
|
||||||
|
containment: '#cb_inner',
|
||||||
|
revert: 'invalid',
|
||||||
|
zIndex: 100,
|
||||||
|
start: pieceStartDrag,
|
||||||
|
stop: pieceStopDrag,
|
||||||
|
});
|
||||||
|
return piece;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBoard() {
|
||||||
|
$('#cb_board .cb-piece .ui-draggable').draggable('destroy');
|
||||||
|
$('#cb_board .cb-piece').attr('style', '').appendTo('#cb_hidden');
|
||||||
|
$('#cb_board .cb-start').removeClass('cb-start');
|
||||||
|
$('#cb_board .cb-end').removeClass('cb-end');
|
||||||
|
$('#cb_board .cb-legal').removeClass('cb-legal');
|
||||||
|
$('#cb_phantom').appendTo('#cb_hidden');
|
||||||
|
|
||||||
|
const game = visibleGame;
|
||||||
|
const board = game.board;
|
||||||
|
|
||||||
|
for (const side of [PS.LIGHT, PS.DARK]) {
|
||||||
|
const counters = {};
|
||||||
|
for (const row of PS.ROWS) {
|
||||||
|
for (const column of PS.COLUMNS) {
|
||||||
|
const here = column + row;
|
||||||
|
const type = board.getPiece(side, here);
|
||||||
|
if (type !== PS.EMPTY) {
|
||||||
|
if (!counters[type]) {
|
||||||
|
counters[type] = 0;
|
||||||
|
}
|
||||||
|
placePiece(here, side, type, String(++counters[type]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let clss = game.player === PS.LIGHT ? '.cb-lt-piece' : '.cb-dk-piece';
|
||||||
|
|
||||||
|
if (board.phantom) {
|
||||||
|
let where = board.phantom.from;
|
||||||
|
placePiece(PS.PHANTOM, board.phantom.side, board.phantom.type, 'ph');
|
||||||
|
cbSquare(PS.PHANTOM).appendTo(cbSquare(where));
|
||||||
|
$('#cb_phantom .ui-draggable-disabled').filter(clss).draggable('enable');
|
||||||
|
} else if (game.status === PS.PLAYING) {
|
||||||
|
$('#cb_board .ui-draggable-disabled').filter(clss).draggable('enable');
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastMove = game.lastMove;
|
||||||
|
if (lastMove) {
|
||||||
|
if (lastMove.from) {
|
||||||
|
cbSquare(lastMove.from).addClass('cb-start');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastMove.to) {
|
||||||
|
cbSquare(lastMove.to).addClass('cb-end');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = '';
|
||||||
|
let winner = game.winner;
|
||||||
|
if (winner) {
|
||||||
|
if (lastMove && lastMove.resign) {
|
||||||
|
msg += (lastMove.side === PS.LIGHT ? 'Light' : 'Dark') + ' player resigned. ';
|
||||||
|
}
|
||||||
|
msg += (winner === PS.LIGHT ? 'Light' : 'Dark') + ' player won!';
|
||||||
|
} else if (game.status === PS.PLAYING) {
|
||||||
|
msg += (game.player === PS.LIGHT ? 'Light' : 'Dark') + ' player\'s turn.';
|
||||||
|
} else {
|
||||||
|
msg += 'Game ended in a draw.';
|
||||||
|
}
|
||||||
|
$('#cb_message').text(msg);
|
||||||
|
|
||||||
|
$('#cb_history').text(game.renderHistory());
|
||||||
|
|
||||||
|
$('#cb_nav_first').attr('disabled', !game.canUndo);
|
||||||
|
$('#cb_nav_prev_turn').attr('disabled', !game.canUndo);
|
||||||
|
$('#cb_nav_prev_state').attr('disabled', !game.canUndo);
|
||||||
|
$('#cb_nav_next_state').attr('disabled', !game.canRedo);
|
||||||
|
$('#cb_nav_next_turn').attr('disabled', !game.canRedo);
|
||||||
|
$('#cb_nav_last').attr('disabled', !game.canRedo);
|
||||||
|
|
||||||
|
if (!game.canRedo) {
|
||||||
|
$('#cb_undo').attr('disabled', !currentGame.canUndo);
|
||||||
|
$('#cb_redo').attr('disabled', !currentGame.canRedo);
|
||||||
|
if (currentGame.status === PS.PLAYING) {
|
||||||
|
$('#cb_pass').attr('disabled', currentGame.board.phantom ? true : false);
|
||||||
|
} else {
|
||||||
|
$('#cb_pass').attr('disabled', true);
|
||||||
|
}
|
||||||
|
$('#cb_resign').attr('disabled', currentGame.status !== PS.PLAYING);
|
||||||
|
$('#cb_board').addClass('cb-live');
|
||||||
|
$('#cb_board').removeClass('cb-archive');
|
||||||
|
} else {
|
||||||
|
$('#cb_undo').attr('disabled', true);
|
||||||
|
$('#cb_redo').attr('disabled', true);
|
||||||
|
$('#cb_pass').attr('disabled', true);
|
||||||
|
$('#cb_resign').attr('disabled', true);
|
||||||
|
$('#cb_board .ui-draggable').draggable('disable');
|
||||||
|
$('#cb_board').removeClass('cb-live');
|
||||||
|
$('#cb_board').addClass('cb-archive');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCurrentBoard(game) {
|
||||||
|
currentGame = game;
|
||||||
|
|
||||||
|
/* navigation should not include the redo stack */
|
||||||
|
visibleGame = new PS.Game(game);
|
||||||
|
visibleGame.clearRedo();
|
||||||
|
renderBoard();
|
||||||
|
|
||||||
|
const moves = game.moves;
|
||||||
|
|
||||||
|
const cb_board = $('#cb_board').first();
|
||||||
|
if ($('#cb_notify')[0].checked) {
|
||||||
|
if (!deepEqual(moves, cb_board.data('last_state'))) {
|
||||||
|
/* ignore partial moves */
|
||||||
|
if (!game.board.phantom) {
|
||||||
|
const gameString = cb_board.data('lightName') + ' vs. ' + cb_board.data('darkName');
|
||||||
|
notify(gameString + '\n' + $('#cb_message').text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cb_board.data('last_state', moves);
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomId(){
|
||||||
|
let res = '';
|
||||||
|
for (let i = 0; i < 4; ++i) {
|
||||||
|
const part = Math.floor(Math.random() * 65536).toString(16);
|
||||||
|
res = res + ('0000'.substring(part.length, 4) + part);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
//const PacoSakoUUID = '7c38edd4-c931-49c8-9f1a-84de560815db'; /* V1 release */
|
||||||
|
const PacoSakoUUID = 'b425b812-6bdb-11ea-9414-6f946662bac3'; /* V2 release */
|
||||||
|
|
||||||
|
function putState() {
|
||||||
|
const boardElem = $('#cb_board');
|
||||||
|
const gameId = boardElem.data('gameId');
|
||||||
|
const moves = { past: currentGame.moves, future: currentGame.redoMoves };
|
||||||
|
boardElem.data('last_state', currentGame.moves);
|
||||||
|
gun.get(PacoSakoUUID).get('games').get(gameId).put({ board: JSON.stringify(moves) });
|
||||||
|
putMeta();
|
||||||
|
}
|
||||||
|
|
||||||
|
function putMeta(){
|
||||||
|
const gameId = $('#cb_board').data('gameId');
|
||||||
|
const lightName = $('#cb_light_name').val();
|
||||||
|
const darkName = $('#cb_dark_name').val();
|
||||||
|
const winner = currentGame.winner;
|
||||||
|
const lastMove = currentGame.lastMove || {};
|
||||||
|
const lastMeta = lastMove.meta || {};
|
||||||
|
const status = !winner ? null : (lastMove.took === PS.KING) ? 'mate' : 'ended';
|
||||||
|
gun.get(PacoSakoUUID).get('meta').get(gameId).put({
|
||||||
|
lightName: lightName,
|
||||||
|
darkName: darkName,
|
||||||
|
moves: currentGame.countTurns(),
|
||||||
|
timestamp: lastMeta.timestamp || new Date(Gun.state()).getTime(),
|
||||||
|
status: status,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchGameId(newId){
|
||||||
|
let boardElem = $('#cb_board');
|
||||||
|
let gameId = boardElem.data('gameId');
|
||||||
|
|
||||||
|
debug('switching from ' + gameId + ' to ' + newId);
|
||||||
|
if (newId === gameId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelGameCallback();
|
||||||
|
cancelMetaCallback();
|
||||||
|
|
||||||
|
boardElem.data('gameId', newId);
|
||||||
|
location.hash = '#/' + newId;
|
||||||
|
|
||||||
|
if (gameId) {
|
||||||
|
//gun.get(PacoSakoUUID).get('games').get(gameId).off();
|
||||||
|
//gun.get(PacoSakoUUID).get('meta').get(gameId).off();
|
||||||
|
|
||||||
|
location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifyAfter = new Date().getTime() + 2000;
|
||||||
|
$(window).data('notifyAfter', new Date().getTime() + 2000);
|
||||||
|
window.setTimeout(function() {
|
||||||
|
/* Delete the notification block in case the system time is changed backward */
|
||||||
|
if ($(window).data('notifyAfter') === notifyAfter) {
|
||||||
|
$(window).removeData('notifyAfter');
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
closeNotifications();
|
||||||
|
|
||||||
|
/* this will be the starting state if no data is received from peers */
|
||||||
|
setCurrentBoard(new PS.Game());
|
||||||
|
boardElem.data('last_state', currentGame.moves);
|
||||||
|
|
||||||
|
boardElem.data('lightName', 'Light');
|
||||||
|
boardElem.data('darkName', 'Dark');
|
||||||
|
$('#cb_light_name').val('');
|
||||||
|
$('#cb_dark_name').val('');
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
let callback = function(d) {
|
||||||
|
if (d && d.board) {
|
||||||
|
try {
|
||||||
|
const moves = JSON.parse(d.board);
|
||||||
|
debug('got board', moves);
|
||||||
|
|
||||||
|
const newGame = new PS.Game();
|
||||||
|
for (const move of moves.past) {
|
||||||
|
newGame.replayMove(move);
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = 0;
|
||||||
|
|
||||||
|
for (const move of moves.future.slice().reverse()) {
|
||||||
|
newGame.replayMove(move);
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < n; ++i) {
|
||||||
|
newGame.undo();
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentBoard(newGame);
|
||||||
|
} catch (err) {
|
||||||
|
debug('Error replaying board state', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelGameCallback = function() { callback = function() {}; };
|
||||||
|
|
||||||
|
gun.get(PacoSakoUUID).get('games').get(newId).on(function(d){
|
||||||
|
callback(d);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
let callback = function(d) {
|
||||||
|
d = d || {};
|
||||||
|
debug('got meta', d);
|
||||||
|
$('#cb_board').data('lightName', d.lightName || 'Light');
|
||||||
|
$('#cb_board').data('darkName', d.darkName || 'Dark');
|
||||||
|
$('#cb_light_name').val(d.lightName || '');
|
||||||
|
$('#cb_dark_name').val(d.darkName || '');
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelMetaCallback = function() { callback = function() {}; };
|
||||||
|
|
||||||
|
gun.get(PacoSakoUUID).get('meta').get(newId).on(function(d){
|
||||||
|
callback(d);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
let selOpt = $('#cb_game_' + newId);
|
||||||
|
if (selOpt.length === 1) {
|
||||||
|
$('#cb_select_game')[0].selectedIndex = selOpt.index();
|
||||||
|
} else {
|
||||||
|
$('#cb_select_game')[0].selectedIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableNotify(){
|
||||||
|
$('#cb_notify')[0].checked = false;
|
||||||
|
$('#cb_notify').attr('disabled', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestNotify(){
|
||||||
|
try {
|
||||||
|
Notification.requestPermission(function (permission){
|
||||||
|
if (permission === 'denied') {
|
||||||
|
disableNotify();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
disableNotify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function notify(body) {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
const then = $(window).data('notifyAfter');
|
||||||
|
if (!then || now >= then) {
|
||||||
|
try {
|
||||||
|
Notification.requestPermission(function(permission){
|
||||||
|
if (permission === 'granted') {
|
||||||
|
navigator.serviceWorker.ready.then(function(registration){
|
||||||
|
registration.showNotification('Paco Ŝako', {
|
||||||
|
body: body,
|
||||||
|
tag: 'notice',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (permission === 'denied') {
|
||||||
|
disableNotify();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
disableNotify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeNotifications(){
|
||||||
|
try {
|
||||||
|
navigator.serviceWorker.ready.then(function(registration){
|
||||||
|
registration.getNotifications({tag: 'notice'}).then(function(notifications){
|
||||||
|
for (const notification of notifications) {
|
||||||
|
notification.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Notification.permission === 'denied') {
|
||||||
|
disableNotify();
|
||||||
|
} else {
|
||||||
|
navigator.serviceWorker.register('sw.js').catch(disableNotify);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
disableNotify();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('localStorage' in window) {
|
||||||
|
function updateNotify(newValue){
|
||||||
|
const doNotify = newValue === 'on';
|
||||||
|
const cb_notify = $('#cb_notify')[0];
|
||||||
|
if (doNotify) {
|
||||||
|
if (!cb_notify.checked) {
|
||||||
|
cb_notify.checked = true;
|
||||||
|
requestNotify();
|
||||||
|
}
|
||||||
|
} else if (cb_notify.checked) {
|
||||||
|
cb_notify.checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(window).on('storage', function(event){
|
||||||
|
if (event.originalEvent.key === '/pacosako/notify') {
|
||||||
|
updateNotify(event.originalEvent.newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateNotify(window.localStorage.getItem('/pacosako/notify'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.cb-square').droppable({
|
||||||
|
accept: '.cb-piece',
|
||||||
|
disabled: true,
|
||||||
|
deactivate: function(ev, ui){
|
||||||
|
$(this).droppable('disable');
|
||||||
|
},
|
||||||
|
drop: function(ev, ui) {
|
||||||
|
let dragged = ui.draggable;
|
||||||
|
let from = dragged.data('location');
|
||||||
|
let to = this.id.replace(/^cb_/, '');
|
||||||
|
dragged.appendTo('#cb_hidden');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const meta = { timestamp: new Date(Gun.state()).getTime() };
|
||||||
|
currentGame.move(from, to, meta);
|
||||||
|
} catch (err) {
|
||||||
|
debug('unable to move', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#cb_board').data('last_state', currentGame.moves);
|
||||||
|
setCurrentBoard(currentGame);
|
||||||
|
putState();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#cb_undo').on('click', function(){
|
||||||
|
if (currentGame.canUndo) {
|
||||||
|
currentGame.undo();
|
||||||
|
$('#cb_board').data('last_state', currentGame.moves);
|
||||||
|
setCurrentBoard(currentGame);
|
||||||
|
putState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#cb_redo').on('click', function(){
|
||||||
|
if (currentGame.canRedo) {
|
||||||
|
currentGame.redo();
|
||||||
|
$('#cb_board').data('last_state', currentGame.moves);
|
||||||
|
setCurrentBoard(currentGame);
|
||||||
|
putState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#cb_resign').on('click', function(){
|
||||||
|
try {
|
||||||
|
const meta = { timestamp: new Date(Gun.state()).getTime() };
|
||||||
|
currentGame.resign(meta);
|
||||||
|
} catch (err) {
|
||||||
|
debug('unable to resign', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#cb_board').data('last_state', currentGame.moves);
|
||||||
|
setCurrentBoard(currentGame);
|
||||||
|
putState();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#cb_nav_first').on('click', function(){
|
||||||
|
while (visibleGame.canUndo) {
|
||||||
|
visibleGame.undo();
|
||||||
|
}
|
||||||
|
renderBoard();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#cb_nav_prev_turn').on('click', function(){
|
||||||
|
const player = visibleGame.player;
|
||||||
|
while (visibleGame.canUndo) {
|
||||||
|
visibleGame.undo();
|
||||||
|
if (visibleGame.player === player) {
|
||||||
|
visibleGame.redo();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderBoard();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#cb_nav_prev_state').on('click', function(){
|
||||||
|
if (visibleGame.canUndo) {
|
||||||
|
visibleGame.undo();
|
||||||
|
}
|
||||||
|
renderBoard();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#cb_nav_next_state').on('click', function(){
|
||||||
|
if (visibleGame.canRedo) {
|
||||||
|
visibleGame.redo();
|
||||||
|
}
|
||||||
|
renderBoard();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#cb_nav_next_turn').on('click', function(){
|
||||||
|
const player = visibleGame.player;
|
||||||
|
while (visibleGame.canRedo) {
|
||||||
|
visibleGame.redo();
|
||||||
|
if (visibleGame.player !== player) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderBoard();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#cb_nav_last').on('click', function(){
|
||||||
|
while (visibleGame.canRedo) {
|
||||||
|
visibleGame.redo();
|
||||||
|
}
|
||||||
|
renderBoard();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#cb_select_game').on('change', function(){
|
||||||
|
let optIndex = $('#cb_select_game')[0].selectedIndex;
|
||||||
|
if (optIndex === 0) {
|
||||||
|
switchGameId(randomId());
|
||||||
|
} else if (optIndex >= 1) {
|
||||||
|
let opt = $('#cb_select_game option')[optIndex];
|
||||||
|
if (opt) {
|
||||||
|
switchGameId(opt.id.replace(/^cb_game_/, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#cb_notify').on('change', function(){
|
||||||
|
if ('localStorage' in window) {
|
||||||
|
window.localStorage.setItem('/pacosako/notify', this.checked ? 'on' : 'off');
|
||||||
|
}
|
||||||
|
if (this.checked) {
|
||||||
|
requestNotify();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let updateMeta = function() { putMeta(); }
|
||||||
|
$('#cb_light_name').on('input', updateMeta);
|
||||||
|
$('#cb_dark_name').on('input', updateMeta);
|
||||||
|
|
||||||
|
function updateTitle(opt){
|
||||||
|
opt = $(opt);
|
||||||
|
|
||||||
|
const then = opt.data('then');
|
||||||
|
const now = new Date(Gun.state()).getTime();
|
||||||
|
let age_str = '';
|
||||||
|
if (then > now) {
|
||||||
|
age_str = ' (future)';
|
||||||
|
} else if ((now - then) < 60*60*1000) {
|
||||||
|
age_str = ' (' + Math.floor((now - then) / (60*1000)) + 'm)';
|
||||||
|
} else if ((now - then) < 24*60*60*1000) {
|
||||||
|
age_str = ' (' + Math.floor((now - then) / (60*60*1000)) + 'h)';
|
||||||
|
} else if ((now - then) < 14*24*60*60*1000) {
|
||||||
|
age_str = ' (' + Math.floor((now - then) / (24*60*60*1000)) + 'd)';
|
||||||
|
} else if (opt.data('gameId') !== $('#cb_board').data('gameId')) {
|
||||||
|
opt.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
opt.text(opt.data('title') + age_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
gun.get(PacoSakoUUID).get('meta').map().on(function(d,key){
|
||||||
|
if (key.match(/^[0-9a-f]{16}$/) && d) {
|
||||||
|
debug('got meta for key ' + key, d);
|
||||||
|
const lightName = d.lightName ? d.lightName : 'Light';
|
||||||
|
const darkName = d.darkName ? d.darkName : 'Dark';
|
||||||
|
const moves = !d.moves ? '' :
|
||||||
|
(', ' + d.moves + (d.moves === 1 ? ' move' : ' moves'));
|
||||||
|
|
||||||
|
let opt = $('#cb_game_' + key);
|
||||||
|
|
||||||
|
if (!(d.lightName || d.darkName) && !d.moves && key !== $('#cb_board').data('gameId')) {
|
||||||
|
if (opt.length >= 1) {
|
||||||
|
opt.remove();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (opt.length === 0) {
|
||||||
|
opt = $('<option></option>');
|
||||||
|
opt.attr('id', 'cb_game_' + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
let stat = '';
|
||||||
|
if (d.status) {
|
||||||
|
stat = ', ' + d.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
opt.data('gameId', key);
|
||||||
|
opt.data('title', lightName + ' vs. ' + darkName + moves + stat);
|
||||||
|
opt.data('then', d.timestamp || new Date(Gun.state()).getTime());
|
||||||
|
opt.addClass('cb-game-option');
|
||||||
|
opt.appendTo('#cb_select_game');
|
||||||
|
updateTitle(opt);
|
||||||
|
|
||||||
|
let select = $('#cb_select_game');
|
||||||
|
let list = select.children('.cb-game-option').get();
|
||||||
|
list.sort(function(a,b) {
|
||||||
|
const then_a = $(a).data('then');
|
||||||
|
const then_b = $(b).data('then');
|
||||||
|
return (then_a < then_b) ? 1 : (then_a === then_b) ? 0 : -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const e of list) {
|
||||||
|
$(e).appendTo(select);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let selOpt = $('#cb_game_' + $('#cb_board').data('gameId'));
|
||||||
|
if (selOpt.length === 1) {
|
||||||
|
$('#cb_select_game')[0].selectedIndex = selOpt.index();
|
||||||
|
} else {
|
||||||
|
$('#cb_select_game')[0].selectedIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.setInterval(function(){
|
||||||
|
$('#cb_select_game').first().children('.cb-game-option').each(function(idx,opt){
|
||||||
|
updateTitle(opt);
|
||||||
|
});
|
||||||
|
}, 15000);
|
||||||
|
|
||||||
|
window.setInterval(function(){
|
||||||
|
const peers = gun._.opt.peers;
|
||||||
|
let n_open = 0;
|
||||||
|
let n_total = 0;
|
||||||
|
for (const peerId in peers) {
|
||||||
|
++n_total;
|
||||||
|
try {
|
||||||
|
const peer = peers[peerId];
|
||||||
|
if (peer.constructor === RTCPeerConnection) {
|
||||||
|
if (peer.connectionState === 'connected') {
|
||||||
|
++n_open;
|
||||||
|
}
|
||||||
|
} else if (peer.wire && peer.wire.constructor === WebSocket) {
|
||||||
|
if (peer.wire.readyState === peer.wire.OPEN) {
|
||||||
|
++n_open;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
const newText = 'Synchronizing with ' + n_open + (n_open === 1 ? ' peer' : ' peers') + ' (' + n_total + ' total).';
|
||||||
|
if (newText !== $('#cb_peers').text()) {
|
||||||
|
$('#cb_peers').text(newText);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
window.onpopstate = function(event){
|
||||||
|
const foundId = location.hash.match(/^#\/([0-9a-f]{16}\b)/);
|
||||||
|
if (foundId) {
|
||||||
|
switchGameId(foundId[1]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* force page reload after four hours to keep client code up to date */
|
||||||
|
window.setTimeout(function(){ location.reload(); }, 4*3600*1000);
|
||||||
|
|
||||||
|
const foundId = location.hash.match(/^#\/([0-9a-f]{16}\b)/);
|
||||||
|
switchGameId(foundId ? foundId[1] : randomId());
|
||||||
|
|
||||||
|
/* Low-level commands to be run from the JS console */
|
||||||
|
window.Admin = {
|
||||||
|
convertFromV1: function() {
|
||||||
|
const PacoSakoUUIDv1 = '7c38edd4-c931-49c8-9f1a-84de560815db';
|
||||||
|
gun.get(PacoSakoUUIDv1).get('games').map().once(function(d,key){
|
||||||
|
if (d && d.board) {
|
||||||
|
debug('converting ' + key);
|
||||||
|
const game = new PS.Game();
|
||||||
|
let moves = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
let board = JSON.parse(d.board);
|
||||||
|
|
||||||
|
while (board.prior) {
|
||||||
|
moves.push(board.move);
|
||||||
|
board = board.prior;
|
||||||
|
}
|
||||||
|
moves.reverse();
|
||||||
|
|
||||||
|
for (const move of moves) {
|
||||||
|
if (move.to) {
|
||||||
|
game.move(move.from, move.to, move.timestamp && { timestamp: move.timestamp });
|
||||||
|
} else if (move.resign) {
|
||||||
|
game.resign();
|
||||||
|
} else {
|
||||||
|
throw { message: "unknown move", move: move };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gun.get(PacoSakoUUID).get('games').get(key).put({ board: JSON.stringify({
|
||||||
|
past: game.moves,
|
||||||
|
future: [],
|
||||||
|
})});
|
||||||
|
} catch (err) {
|
||||||
|
debug('conversion of ' + key + ' failed', err, game, moves);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gun.get(PacoSakoUUIDv1).get('meta').map().once(function(d,key){
|
||||||
|
if (d) {
|
||||||
|
debug('converting metadata for ' + key);
|
||||||
|
gun.get(PacoSakoUUID).get('meta').get(key).put({
|
||||||
|
lightName: d.lightName,
|
||||||
|
darkName: d.darkName,
|
||||||
|
moves: d.moves,
|
||||||
|
timestamp: d.timestamp,
|
||||||
|
status: d.status,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanupMissingGames: function() {
|
||||||
|
gun.get(PacoSakoUUID).get('games').once(function(games){
|
||||||
|
gun.get(PacoSakoUUID).get('meta').map().once(function(d,key){
|
||||||
|
if (!(key in games)) {
|
||||||
|
gun.get(PacoSakoUUID).get('meta').get(key).put(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/* vim:set expandtab sw=3 ts=8: */
|
||||||
56
js/todo.js
56
js/todo.js
|
|
@ -1,56 +0,0 @@
|
||||||
var user = gun.user();
|
|
||||||
|
|
||||||
function UI(say, id){
|
|
||||||
var li = $('#' + id).get(0) || $('<li>').attr('id', id).appendTo('ul');
|
|
||||||
$(li).text(say);
|
|
||||||
};
|
|
||||||
|
|
||||||
function auth(alias, pass){
|
|
||||||
$('#message').text('Looking up alias "' + alias + '"...').show();
|
|
||||||
gun.get('~@' + alias).once(function(){
|
|
||||||
$('#message').text('Signing in as "' + alias + '"...').show();
|
|
||||||
user.auth(alias, pass, function (ack){
|
|
||||||
if (ack.err) {
|
|
||||||
$('#message').text(ack.err).show();
|
|
||||||
} else {
|
|
||||||
$('#message').hide();
|
|
||||||
$('#pass').val('');
|
|
||||||
$('#sign').hide();
|
|
||||||
$('#todo').show();
|
|
||||||
user.get('said').map().once(UI);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('#up').on('click', function(e){
|
|
||||||
var alias = $('#alias').val();
|
|
||||||
var pass = $('#pass').val();
|
|
||||||
$('#message').text('Creating alias "' + alias + '"...').show();
|
|
||||||
user.create(alias, pass, function (ack){
|
|
||||||
if (ack.err) {
|
|
||||||
$('#message').text(ack.err).show();
|
|
||||||
} else {
|
|
||||||
auth(alias, pass);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#sign').on('submit', function(e){
|
|
||||||
e.preventDefault();
|
|
||||||
auth($('#alias').val(), $('#pass').val());
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#said').on('submit', function(e){
|
|
||||||
e.preventDefault();
|
|
||||||
if(!user.is){ return }
|
|
||||||
user.get('said').set($('#say').val());
|
|
||||||
$('#say').val('');
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#sign_out').on('click', function (){
|
|
||||||
$('ul').empty();
|
|
||||||
user.leave();
|
|
||||||
$('#sign').show();
|
|
||||||
$('#todo').hide();
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue