474 lines
14 KiB
JavaScript
474 lines
14 KiB
JavaScript
'use strict';
|
|
var gun = Gun(['https://jessemcdonald.info/gun']);
|
|
|
|
let initialBoard = (function (){
|
|
var init = JSON.stringify({
|
|
'light': [
|
|
'rnbkqbnr',
|
|
'pppppppp',
|
|
' ',
|
|
' ',
|
|
' ',
|
|
' ',
|
|
' ',
|
|
' ',
|
|
],
|
|
'dark': [
|
|
' ',
|
|
' ',
|
|
' ',
|
|
' ',
|
|
' ',
|
|
' ',
|
|
'pppppppp',
|
|
'rnbkqbnr',
|
|
],
|
|
'player': 'light',
|
|
});
|
|
return function (){ return JSON.parse(init); }
|
|
})();
|
|
|
|
function cloneJSON(obj){
|
|
return JSON.parse(JSON.stringify(obj));
|
|
}
|
|
|
|
function normalizeSide(side){
|
|
return (side[0] === 'd') ? 'dark' : 'light';
|
|
}
|
|
|
|
function otherSide(side){
|
|
return (side[0] === 'd') ? 'light' : 'dark';
|
|
}
|
|
|
|
function boardGet(board, where, side){
|
|
side = normalizeSide(side);
|
|
if (where === 'phantom') {
|
|
if (!board['phantom'] || board['phantom']['type'][1] !== side[0]) {
|
|
return ' ';
|
|
} else {
|
|
return board['phantom']['type'][0];
|
|
}
|
|
} else {
|
|
var column = 'abcdefgh'.indexOf(where[0]);
|
|
var row = Number(where[1]) - 1;
|
|
return board[side][row][column];
|
|
}
|
|
}
|
|
|
|
function boardPut(board, where, side, piece){
|
|
side = (side[0] === 'd') ? 'dark' : 'light';
|
|
var column = 'abcdefgh'.indexOf(where[0]);
|
|
var row = Number(where[1]) - 1;
|
|
var data = board[side][row];
|
|
var prefix = data.substring(0, column);
|
|
var suffix = data.substring(column + 1, 8);
|
|
board[side][row] = prefix + piece + suffix;
|
|
}
|
|
|
|
function movePiece(priorBoard, side, from, to){
|
|
var other = otherSide(side);
|
|
var type = boardGet(priorBoard, from, side);
|
|
var took = boardGet(priorBoard, to, other);
|
|
var replaced = boardGet(priorBoard, to, side);
|
|
var alongside = (from === 'phantom') ? ' ' : boardGet(priorBoard, from, other);
|
|
|
|
var undoBoard = priorBoard;
|
|
if (undoBoard['subsequent']) {
|
|
undoBoard = cloneJSON(undoBoard);
|
|
delete undoBoard['subsequent'];
|
|
}
|
|
|
|
var board = cloneJSON(undoBoard);
|
|
board['prior'] = undoBoard;
|
|
|
|
board['move'] = {
|
|
'side': normalizeSide(side),
|
|
'type': type,
|
|
'from': from,
|
|
'to': to
|
|
};
|
|
if (took !== ' ') {
|
|
board['move']['took'] = took;
|
|
}
|
|
if (replaced !== ' ') {
|
|
board['move']['replaced'] = replaced;
|
|
}
|
|
if (alongside !== ' ') {
|
|
board['move']['alongside'] = alongside;
|
|
}
|
|
|
|
if (from === 'phantom') {
|
|
delete board['phantom'];
|
|
} else {
|
|
boardPut(board, from, side, ' ');
|
|
boardPut(board, from, other, ' ');
|
|
}
|
|
|
|
boardPut(board, to, side, type);
|
|
|
|
if (alongside !== ' ') {
|
|
boardPut(board, to, other, alongside);
|
|
}
|
|
|
|
if (replaced === ' ') {
|
|
board['player'] = otherSide(board['player']);
|
|
} else {
|
|
board['phantom'] = { 'from': to, 'type': replaced + side[0] };
|
|
}
|
|
|
|
return board;
|
|
}
|
|
|
|
function renderHistory(board) {
|
|
var list = [];
|
|
|
|
while (board && board['move']) {
|
|
list.push(board['move']);
|
|
board = board['prior'];
|
|
}
|
|
|
|
var result = '';
|
|
var n = 0;
|
|
|
|
while (list.length > 0) {
|
|
var move = list.pop();
|
|
if (move['from'] === 'phantom') {
|
|
var took = move['took'] ? 'x' : '';
|
|
result += '*' + move['type'].toUpperCase() + took + move['to'];
|
|
} else {
|
|
if (n > 0 || move['side'] === 'dark') {
|
|
result += ' ';
|
|
}
|
|
|
|
if (move['side'] === 'light') {
|
|
++n;
|
|
result += String(n) + '. ';
|
|
}
|
|
|
|
if (move['pass']) {
|
|
result += '...';
|
|
} else if (move['alongside']) {
|
|
if (move['side'] === 'light') {
|
|
result += move['type'].toUpperCase() + move['alongside'].toUpperCase() + move['from'] + move['to'];
|
|
} else {
|
|
result += move['alongside'].toUpperCase() + move['type'].toUpperCase() + move['from'] + move['to'];
|
|
}
|
|
} else {
|
|
var took = move['took'] ? 'x' : '';
|
|
result += move['type'].toUpperCase() + move['from'] + took + move['to'];
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function pieceStartDrag(ev, ui){
|
|
var board = $('#cb_board').data('board');
|
|
var dragged = $(this);
|
|
var type = dragged.data('type');
|
|
var from = dragged.data('location');
|
|
if (from === 'phantom' || boardGet(board, from, otherSide(type[1])) === ' ') {
|
|
var clss = (type[1] === 'd') ? '.cb-dk-piece' : '.cb-lt-piece';
|
|
var other = (type[1] === 'd') ? '.cb-lt-piece' : '.cb-dk-piece';
|
|
$('.cb-square').not(':has('+clss+')').droppable('enable');
|
|
$('.cb-square').filter(':has('+other+')').droppable('enable');
|
|
} else {
|
|
/* moving together, must go to an empty square */
|
|
$('.cb-square').not(':has(.cb-piece)').droppable('enable');
|
|
}
|
|
};
|
|
|
|
function placePiece(where, type, count){
|
|
var piece_id = 'cb_piece_' + type + '_' + count;
|
|
var piece = $($('#' + piece_id)[0] || $('#cb_piece_' + type + ' img').clone());
|
|
piece.attr('style', '');
|
|
piece.attr('id', piece_id);
|
|
piece.data({ 'type': type, 'location': where });
|
|
piece.appendTo('#cb_' + where);
|
|
piece.draggable({
|
|
disabled: true,
|
|
containment: '#cb_inner',
|
|
revert: 'invalid',
|
|
zIndex: 100,
|
|
start: pieceStartDrag,
|
|
});
|
|
return piece;
|
|
}
|
|
|
|
function renderBoard(board){
|
|
$('#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_phantom').appendTo('#cb_hidden');
|
|
|
|
for (const side of ['light', 'dark']) {
|
|
var counters = {};
|
|
for (var row = 0; row < 8; ++row) {
|
|
for (var column = 0; column < 8; ++column) {
|
|
var here = 'abcdefgh'[column] + String(row+1);
|
|
var type = board[side][row][column];
|
|
if (type !== ' ') {
|
|
if (!counters[type]) {
|
|
counters[type] = 0;
|
|
}
|
|
var count = ++counters[type];
|
|
placePiece(here, type + side[0], count);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var clss = board['player'] === 'light' ? '.cb-lt-piece' : '.cb-dk-piece';
|
|
|
|
if (board['phantom']) {
|
|
var where = board['phantom']['from'];
|
|
placePiece('phantom', board['phantom']['type'], 'ph');
|
|
$('#cb_phantom').appendTo('#cb_' + where);
|
|
$('#cb_board .ui-draggable').draggable('disable');
|
|
$('#cb_phantom .ui-draggable-disabled').filter(clss).draggable('enable');
|
|
} else {
|
|
$('#cb_board .ui-draggable-disabled').filter(clss).draggable('enable');
|
|
}
|
|
|
|
if (board['move']) {
|
|
if (board['move']['from'] === 'phantom') {
|
|
$('#cb_' + board['prior']['move']['to']).addClass('cb-start');
|
|
} else {
|
|
$('#cb_' + board['move']['from']).addClass('cb-start');
|
|
}
|
|
$('#cb_' + board['move']['to']).addClass('cb-end');
|
|
}
|
|
|
|
$('#cb_' + otherSide(board['player']) + '_move').hide();
|
|
$('#cb_' + normalizeSide(board['player']) + '_move').show();
|
|
|
|
$('#cb_history').text(renderHistory(board));
|
|
|
|
$('#cb_undo').attr('disabled', board['prior'] ? false : true);
|
|
$('#cb_redo').attr('disabled', board['subsequent'] ? false : true);
|
|
$('#cb_pass').attr('disabled', board['phantom'] ? true : false);
|
|
}
|
|
|
|
function randomId(){
|
|
var res = '';
|
|
for (var i = 0; i < 4; ++i) {
|
|
var part = Math.floor(Math.random() * 65536).toString(16);
|
|
res = res + ("0000".substring(part.length, 4) + part);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
var PacoSakoUUID = '7c38edd4-c931-49c8-9f1a-84de560815db';
|
|
|
|
function putState(board){
|
|
var boardElem = $('#cb_board');
|
|
var gameId = boardElem.data('gameId');
|
|
var game = gun.get(PacoSakoUUID).get('games').get(gameId);
|
|
game.put({ 'board': JSON.stringify(board) });
|
|
putMeta();
|
|
}
|
|
|
|
function putMeta(){
|
|
var gameId = $('#cb_board').data('gameId');
|
|
var lightName = $('#cb_light_name').val() || 'Light';
|
|
var darkName = $('#cb_dark_name').val() || 'Dark';
|
|
var meta = gun.get(PacoSakoUUID).get('meta').get(gameId);
|
|
meta.put({ 'gameId': gameId, 'lightName': lightName, 'darkName': darkName, 'timestamp': new Date().getTime() });
|
|
}
|
|
|
|
function switchGameId(newId){
|
|
var boardElem = $('#cb_board');
|
|
var gameId = boardElem.data('gameId');
|
|
|
|
if (newId == gameId) {
|
|
return;
|
|
}
|
|
|
|
if (gameId) {
|
|
//gun.get(PacoSakoUUID).get('games').get(gameId).off();
|
|
//gun.get(PacoSakoUUID).get('meta').get(gameId).off();
|
|
}
|
|
|
|
boardElem.data('gameId', newId);
|
|
location.hash = '#/' + newId;
|
|
|
|
/* this will be the starting state if no data is received from peers */
|
|
var newBoard = initialBoard();
|
|
boardElem.data('board', newBoard);
|
|
renderBoard(newBoard);
|
|
|
|
gun.get(PacoSakoUUID).get('games').get(newId).on(function(d){
|
|
if (d && d['board'] && $('#cb_board').data('gameId') === newId) {
|
|
var board = JSON.parse(d['board']);
|
|
$('#cb_board').data('board', board);
|
|
renderBoard(board);
|
|
}
|
|
});
|
|
|
|
$('#cb_light_name').val('');
|
|
$('#cb_dark_name').val('');
|
|
|
|
gun.get(PacoSakoUUID).get('meta').get(newId).on(function(d){
|
|
if (d && $('#cb_board').data('gameId') === newId) {
|
|
if (d['lightName']) {
|
|
$('#cb_light_name').val(d['lightName']);
|
|
}
|
|
if (d['darkName']) {
|
|
$('#cb_dark_name').val(d['darkName']);
|
|
}
|
|
}
|
|
});
|
|
|
|
var selOpt = $('#cb_game_' + newId);
|
|
if (selOpt.length === 1) {
|
|
$('#cb_select_game')[0].selectedIndex = selOpt.index();
|
|
} else {
|
|
$('#cb_select_game')[0].selectedIndex = -1;
|
|
}
|
|
}
|
|
|
|
$(function (){
|
|
$('.cb-square').droppable({
|
|
accept: '.cb-piece',
|
|
disabled: true,
|
|
deactivate: function(ev, ui){
|
|
$(this).droppable('disable');
|
|
},
|
|
drop: function(ev, ui) {
|
|
var dragged = ui.draggable;
|
|
var type = dragged.data('type');
|
|
var from = dragged.data('location');
|
|
var to = this.id.replace(/^cb_/, '');
|
|
dragged.appendTo('#cb_hidden');
|
|
|
|
var newBoard = movePiece($('#cb_board').data('board'), type[1], from, to);
|
|
putState(newBoard);
|
|
},
|
|
});
|
|
|
|
$('#cb_undo').on('click', function(){
|
|
var board = $('#cb_board').data('board');
|
|
if (board['prior']) {
|
|
var newBoard = cloneJSON(board['prior']);
|
|
var redoBoard = cloneJSON(board);
|
|
delete redoBoard['prior'];
|
|
newBoard['subsequent'] = redoBoard;
|
|
putState(newBoard);
|
|
}
|
|
});
|
|
|
|
$('#cb_redo').on('click', function(){
|
|
var board = $('#cb_board').data('board');
|
|
if (board['subsequent']) {
|
|
var newBoard = cloneJSON(board['subsequent']);
|
|
var undoBoard = cloneJSON(board);
|
|
delete undoBoard['subsequent'];
|
|
newBoard['prior'] = undoBoard;
|
|
putState(newBoard);
|
|
}
|
|
});
|
|
|
|
$('#cb_reset').on('click', function(){
|
|
putState(initialBoard());
|
|
});
|
|
|
|
$('#cb_pass').on('click', function(){
|
|
var board = $('#cb_board').data('board');
|
|
if (!board['phantom']) {
|
|
var newBoard = cloneJSON(board);
|
|
newBoard['prior'] = board;
|
|
newBoard['move'] = { 'side': board['player'], 'pass': true };
|
|
newBoard['player'] = otherSide(board['player']);
|
|
putState(newBoard);
|
|
}
|
|
});
|
|
|
|
$('#cb_select_game').on('change', function(){
|
|
var optIndex = $('#cb_select_game')[0].selectedIndex;
|
|
if (optIndex === 0) {
|
|
switchGameId(randomId());
|
|
} else if (optIndex >= 1) {
|
|
var opt = $('#cb_select_game option')[optIndex];
|
|
if (opt) {
|
|
switchGameId(opt.id.replace(/^cb_game_/, ''));
|
|
}
|
|
}
|
|
});
|
|
|
|
let updateMeta = function() { putMeta(); }
|
|
$('#cb_light_name').on('input', updateMeta);
|
|
$('#cb_dark_name').on('input', updateMeta);
|
|
|
|
gun.get(PacoSakoUUID).get('meta').map().on(function(d){
|
|
if (d && d['gameId'] && d['lightName'] && d['darkName']) {
|
|
function updateTitle(opt){
|
|
const then = opt.data('then');
|
|
const now = new Date().getTime();
|
|
var 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 (d['gameId'] !== $('#cb_board').data('gameId')) {
|
|
opt.remove();
|
|
return;
|
|
}
|
|
opt.text(opt.data('title') + age_str);
|
|
}
|
|
|
|
var opt = $('#cb_game_' + d['gameId']);
|
|
if (opt.length === 0) {
|
|
opt = $('<option></option>');
|
|
opt.attr('id', 'cb_game_' + d['gameId']);
|
|
|
|
function refreshTitle(){
|
|
updateTitle(opt);
|
|
window.setTimeout(refreshTitle, 15000);
|
|
};
|
|
|
|
window.setTimeout(refreshTitle, 0);
|
|
}
|
|
|
|
opt.data('title', d['lightName'] + ' vs. ' + d['darkName']);
|
|
opt.data('then', d['timestamp'] || new Date().getTime());
|
|
opt.addClass('cb-game-option');
|
|
opt.appendTo('#cb_select_game');
|
|
updateTitle(opt);
|
|
|
|
var select = $('#cb_select_game');
|
|
var 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);
|
|
}
|
|
|
|
var 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;
|
|
}
|
|
}
|
|
});
|
|
|
|
var gameId = location.hash.replace(/^#\//, '');
|
|
if (gameId.length !== 16) {
|
|
gameId = randomId();
|
|
}
|
|
|
|
switchGameId(gameId);
|
|
});
|
|
|
|
/* vim:set expandtab sw=3 ts=8: */
|