'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 = $(''); 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: */