851 lines
24 KiB
HTML
851 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<!-- Takhta is released under CC0 -->
|
|
<!-- Developed by nortti 2020-2021 and uplime 2021 -->
|
|
<!-- Name by CrazyEttin 2021 -->
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Takhta</title>
|
|
<style>
|
|
tr :nth-child(1) {
|
|
height: 1em;
|
|
width: 1em;
|
|
}
|
|
td {
|
|
font-size: 300%;
|
|
}
|
|
td div {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
line-height: 1em;
|
|
}
|
|
#board tbody :nth-child(odd) :nth-child(even) {
|
|
background-color: #fff;
|
|
}
|
|
#board tbody :nth-child(even) :nth-child(odd) {
|
|
background-color: #fff;
|
|
}
|
|
#board tbody :nth-child(odd) :nth-child(odd) {
|
|
background-color: #999;
|
|
}
|
|
#board tbody :nth-child(even) :nth-child(even) {
|
|
background-color: #999;
|
|
}
|
|
#board tbody :last-child :nth-child(n) {
|
|
background-color: unset;
|
|
}
|
|
#board tbody :nth-child(n) :first-child {
|
|
background-color: unset;
|
|
}
|
|
#options {
|
|
display: table;
|
|
}
|
|
.option {
|
|
display: table-row;
|
|
}
|
|
.option :nth-child(1) {
|
|
display: table-cell;
|
|
}
|
|
.optionvalues :nth-child(1) {
|
|
display: inline;
|
|
}
|
|
body {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
color: #000;
|
|
}
|
|
section {
|
|
margin-right: 2em;
|
|
}
|
|
#movelist {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
padding: 0;
|
|
}
|
|
#movelist :nth-child(odd) {
|
|
background-color: #fff;
|
|
}
|
|
#movelist :nth-child(even) {
|
|
margin-left: 1em;
|
|
background-color: #999;
|
|
}
|
|
li {
|
|
display: block;
|
|
padding-left: 0.2em;
|
|
padding-right: 0.2em;
|
|
}
|
|
.undone {
|
|
text-decoration: line-through;
|
|
}
|
|
.numentry {
|
|
white-space: pre-wrap;
|
|
}
|
|
#moveupload {
|
|
visibility: hidden;
|
|
display: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<section>
|
|
<table id="board">
|
|
<tbody>
|
|
<tr id="row8">
|
|
<th class="coords">8</th>
|
|
<td><div id="a8">♜</div></td>
|
|
<td><div id="b8">♞</div></td>
|
|
<td><div id="c8">♝</div></td>
|
|
<td><div id="d8">♚</div></td>
|
|
<td><div id="e8">♛</div></td>
|
|
<td><div id="f8">♝</div></td>
|
|
<td><div id="g8">♞</div></td>
|
|
<td><div id="h8">♜</div></td>
|
|
</tr>
|
|
|
|
<tr id="row7">
|
|
<th class="coords">7</th>
|
|
<td><div id="a7">♟</div></td>
|
|
<td><div id="b7">♟</div></td>
|
|
<td><div id="c7">♟</div></td>
|
|
<td><div id="d7">♟</div></td>
|
|
<td><div id="e7">♟</div></td>
|
|
<td><div id="f7">♟</div></td>
|
|
<td><div id="g7">♟</div></td>
|
|
<td><div id="h7">♟</div></td>
|
|
</tr>
|
|
|
|
<tr id="row6">
|
|
<th class="coords">6</th>
|
|
<td><div id="a6"></div></td>
|
|
<td><div id="b6"></div></td>
|
|
<td><div id="c6"></div></td>
|
|
<td><div id="d6"></div></td>
|
|
<td><div id="e6"></div></td>
|
|
<td><div id="f6"></div></td>
|
|
<td><div id="g6"></div></td>
|
|
<td><div id="h6"></div></td>
|
|
</tr>
|
|
|
|
<tr id="row5">
|
|
<th class="coords">5</th>
|
|
<td><div id="a5"></div></td>
|
|
<td><div id="b5"></div></td>
|
|
<td><div id="c5"></div></td>
|
|
<td><div id="d5"></div></td>
|
|
<td><div id="e5"></div></td>
|
|
<td><div id="f5"></div></td>
|
|
<td><div id="g5"></div></td>
|
|
<td><div id="h5"></div></td>
|
|
</tr>
|
|
|
|
<tr id="row4">
|
|
<th class="coords">4</th>
|
|
<td><div id="a4"></div></td>
|
|
<td><div id="b4"></div></td>
|
|
<td><div id="c4"></div></td>
|
|
<td><div id="d4"></div></td>
|
|
<td><div id="e4"></div></td>
|
|
<td><div id="f4"></div></td>
|
|
<td><div id="g4"></div></td>
|
|
<td><div id="h4"></div></td>
|
|
</tr>
|
|
|
|
<tr id="row3">
|
|
<th class="coords">3</th>
|
|
<td><div id="a3"></div></td>
|
|
<td><div id="b3"></div></td>
|
|
<td><div id="c3"></div></td>
|
|
<td><div id="d3"></div></td>
|
|
<td><div id="e3"></div></td>
|
|
<td><div id="f3"></div></td>
|
|
<td><div id="g3"></div></td>
|
|
<td><div id="h3"></div></td>
|
|
</tr>
|
|
|
|
<tr id="row2">
|
|
<th class="coords">2</th>
|
|
<td><div id="a2">♙</div></td>
|
|
<td><div id="b2">♙</div></td>
|
|
<td><div id="c2">♙</div></td>
|
|
<td><div id="d2">♙</div></td>
|
|
<td><div id="e2">♙</div></td>
|
|
<td><div id="f2">♙</div></td>
|
|
<td><div id="g2">♙</div></td>
|
|
<td><div id="h2">♙</div></td>
|
|
</tr>
|
|
|
|
<tr id="row1">
|
|
<th class="coords">1</th>
|
|
<td><div id="a1">♖</div></td>
|
|
<td><div id="b1">♘</div></td>
|
|
<td><div id="c1">♗</div></td>
|
|
<td><div id="d1">♔</div></td>
|
|
<td><div id="e1">♕</div></td>
|
|
<td><div id="f1">♗</div></td>
|
|
<td><div id="g1">♘</div></td>
|
|
<td><div id="h1">♖</div></td>
|
|
</tr>
|
|
|
|
<tr class="coords">
|
|
<th></th>
|
|
<th>A</th>
|
|
<th>B</th>
|
|
<th>C</th>
|
|
<th>D</th>
|
|
<th>E</th>
|
|
<th>F</th>
|
|
<th>G</th>
|
|
<th>H</th>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<p>
|
|
<form id="input">
|
|
<label><span id="tomove">White</span>'s move
|
|
<input id="move" type="text" autocomplete="off">
|
|
</label>
|
|
<input type="submit" value="Move">
|
|
</form>
|
|
</p>
|
|
<p>
|
|
<form id="actions">
|
|
<input type="button" value="Undo" onclick="undoMove()">
|
|
<input type="button" value="Redo" onclick="redoMove()">
|
|
<input type="button" value="Flip board" onclick="flipBoard()">
|
|
</form>
|
|
</p>
|
|
<p>
|
|
<form id="rewind">
|
|
<input type="button" value="Undo all" onclick="undoAll()">
|
|
<input type="button" value="Redo all" onclick="redoAll()">
|
|
</form>
|
|
</p>
|
|
<p>
|
|
<form id="shipping">
|
|
<input type="button" value="Download moves" onclick="download()">
|
|
<input type="file" id="moveupload" name="moveupload">
|
|
<button type="button"><label for="moveupload">Upload moves</label></button>
|
|
</form>
|
|
</p>
|
|
<p>
|
|
<form id="options">
|
|
<div class="option">
|
|
<div class="optiontext">White king on</div>
|
|
<div class="optionvalues">
|
|
<label>
|
|
<input type="radio" id="whited1" name="whiteking" value="d1" checked onchange="setupKings()">
|
|
d1
|
|
</label>
|
|
<label>
|
|
<input type="radio" id="whitee1" name="whiteking" value="e1" onchange="setupKings()">
|
|
e1
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="option">
|
|
<div class="optiontext">Black king on</div>
|
|
<div class="optionvalues">
|
|
<label>
|
|
<input type="radio" id="blackd8" name="blackking" value="d8" checked onchange="setupKings()">
|
|
d8
|
|
</label>
|
|
<label>
|
|
<input type="radio" id="blacke8" name="blackking" value="e8" onchange="setupKings()">
|
|
e8
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</p>
|
|
</section>
|
|
<section id="moves">
|
|
<p>Moves thus far</p>
|
|
<ul id="movelist">
|
|
</ul>
|
|
</section>
|
|
|
|
<script>
|
|
'use strict';
|
|
let form = document.getElementById('input');
|
|
form.onsubmit = moveEvent;
|
|
|
|
document.getElementById('whited1').checked = true;
|
|
document.getElementById('blackd8').checked = true;
|
|
|
|
let moveHistory = [];
|
|
let moveFuture = [];
|
|
|
|
function moveEvent(event) {
|
|
event.preventDefault();
|
|
|
|
let move = document.getElementById('move').value;
|
|
if (move === '') {
|
|
return;
|
|
}
|
|
|
|
let ret = doMove(move);
|
|
if ('error' in ret) {
|
|
alert(ret.error);
|
|
return;
|
|
}
|
|
|
|
// Destroy the future
|
|
moveFuture = [];
|
|
let movelist = document.getElementById('movelist')
|
|
let undones = Array.from(document.getElementsByClassName('undone')); // Array.from makes this not change from under us
|
|
for (let element of undones) {
|
|
movelist.removeChild(element);
|
|
}
|
|
|
|
// List the move
|
|
updateMoveTable(move);
|
|
|
|
// Empty input field
|
|
document.getElementById('move').value = '';
|
|
}
|
|
|
|
function updateMoveTable(move) {
|
|
let listElement = document.createElement('li');
|
|
|
|
if(moveHistory.length % 2 === 1) {
|
|
let padding = Math.floor(Math.log10((moveHistory.length / 2) + 1));
|
|
let moveList = document.getElementsByClassName('numentry');
|
|
|
|
for(let next = 0; next < moveList.length; next += 1) {
|
|
let digits = Math.floor(Math.log10(next + 1));
|
|
|
|
if(digits < padding) {
|
|
moveList[next].innerText = `${next + 1}. `.padStart(padding + digits + 3, " ");
|
|
}
|
|
}
|
|
|
|
let numEntry = document.createElement('span');
|
|
|
|
numEntry.classList.add('numentry');
|
|
numEntry.innerText = `${parseInt(moveHistory.length / 2) + 1}. `;
|
|
listElement.appendChild(numEntry);
|
|
}
|
|
|
|
listElement.appendChild(document.createTextNode(move));
|
|
movelist.appendChild(listElement);
|
|
}
|
|
|
|
function doMove(move) {
|
|
// Piece
|
|
let piece = move.slice(0, 1);
|
|
let index = 0;
|
|
switch(piece) {
|
|
case 'K':
|
|
case 'Q':
|
|
case 'B':
|
|
case 'N':
|
|
case 'R':
|
|
case 'P':
|
|
index++;
|
|
break;
|
|
case 'a':
|
|
case 'b':
|
|
case 'c':
|
|
case 'd':
|
|
case 'e':
|
|
case 'f':
|
|
case 'g':
|
|
case 'h':
|
|
piece = 'P';
|
|
break;
|
|
case 'O':
|
|
case '0':
|
|
// Castling, handled in its own function
|
|
return doCastle(move);
|
|
default:
|
|
return {error: 'Not a valid piece: ' + piece};
|
|
}
|
|
|
|
// Start square
|
|
let startLetter = move.slice(index, index + 1);
|
|
let startNumber = move.slice(index + 1, index + 2);
|
|
index += 2;
|
|
if (
|
|
startLetter === '' || !'abcdefgh'.includes(startLetter) ||
|
|
startNumber === '' || !'12345678'.includes(startNumber)
|
|
) {
|
|
return {error: 'Not a valid coordinate: ' + startLetter + startNumber};
|
|
}
|
|
|
|
// Capturing
|
|
let captures = false;
|
|
if (move.slice(index, index + 1) === 'x') {
|
|
captures = true;
|
|
index++;
|
|
}
|
|
|
|
// End square
|
|
let endLetter = move.slice(index, index + 1);
|
|
let endNumber = move.slice(index + 1, index + 2);
|
|
index += 2;
|
|
if (
|
|
endLetter === '' || !'abcdefgh'.includes(endLetter) ||
|
|
endNumber === '' || !'12345678'.includes(endNumber)
|
|
) {
|
|
return {error: 'Not a valid coordinate: ' + endLetter + endNumber};
|
|
}
|
|
|
|
// Promotion
|
|
let promotion = null;
|
|
if (move.slice(index, index + 1) === '=') {
|
|
index++;
|
|
switch (move.slice(index, index + 1)) {
|
|
case 'K':
|
|
case 'Q':
|
|
case 'B':
|
|
case 'N':
|
|
case 'R':
|
|
case 'P':
|
|
promotion = move.slice(index, index + 1);
|
|
break;
|
|
default:
|
|
return {error: 'Unrecognized piece: ' + move.slice(index, index + 1)};
|
|
}
|
|
index++;
|
|
} else if (['K', 'Q', 'B', 'N', 'R', 'P'].includes(move.slice(index, index + 1))) {
|
|
promotion = move.slice(index, index + 1);
|
|
index++;
|
|
}
|
|
|
|
// Check(mate)
|
|
switch(move.slice(index)) {
|
|
case '':
|
|
// nothing
|
|
break;
|
|
case '+':
|
|
// TODO: Check
|
|
break;
|
|
case '++':
|
|
case '#':
|
|
// TODO: Checkmate'
|
|
break;
|
|
default:
|
|
return {error: 'Unexpected contents at the end of the move: ' + move.slice(index)};
|
|
}
|
|
|
|
// End of parsing, start of move construction
|
|
let whitePieces = {'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙'};
|
|
let blackPieces = {'K': '♚', 'Q': '♛', 'R': '♜', 'B': '♝', 'N': '♞', 'P': '♟'};
|
|
|
|
let whiteMove = document.getElementById('tomove').childNodes[0].data === 'White';
|
|
|
|
let renderedPiece = whiteMove ? whitePieces[piece] : blackPieces[piece];
|
|
let renderedPromotion = null;
|
|
if (promotion) {
|
|
renderedPromotion = whiteMove ? whitePieces[promotion] : blackPieces[promotion];
|
|
}
|
|
|
|
// Are we moving the piece from where it is located?
|
|
let startSquare = document.getElementById(startLetter + startNumber);
|
|
if (startSquare.childNodes.length === 0) {
|
|
return {error: 'No piece on square ' + startLetter + startNumber};
|
|
}
|
|
if (startSquare.childNodes[0].data !== renderedPiece) {
|
|
let pieceNames = {'K': 'king', 'Q': 'vizier', 'R': 'rook', 'B': 'elephant', 'N': 'horse', 'P': 'pawn'};
|
|
return {error: (whiteMove ? 'White' : 'Black') + ' ' + pieceNames[piece] + ' not on square ' + startLetter + startNumber};
|
|
}
|
|
|
|
// TODO: Check piece is allowed to move like that
|
|
|
|
// Can the piece move to this location?
|
|
let endSquare = document.getElementById(endLetter + endNumber);
|
|
if (endSquare.childNodes.length !== 0 && !captures && startSquare !== endSquare) {
|
|
return {error: 'Square ' + endLetter + endNumber + ' is occupied, did you mean to capture?'};
|
|
}
|
|
if (endSquare.childNodes.length === 0 && captures) {
|
|
return {error: 'Square ' + endLetter + endNumber + ' empty even though capture was specified'};
|
|
}
|
|
|
|
// Apply move
|
|
let removedPieces = {}
|
|
let placedPieces = {}
|
|
if (captures) {
|
|
removedPieces[endLetter + endNumber] = endSquare.childNodes[0].data;
|
|
endSquare.removeChild(endSquare.childNodes[0]);
|
|
}
|
|
if (startSquare !== endSquare || !captures) {
|
|
removedPieces[startLetter + startNumber] = renderedPiece;
|
|
startSquare.removeChild(startSquare.childNodes[0]);
|
|
placedPieces[endLetter + endNumber] = promotion ? renderedPromotion : renderedPiece;
|
|
endSquare.appendChild(document.createTextNode(promotion ? renderedPromotion : renderedPiece))
|
|
}
|
|
|
|
// Flip whose turn it is
|
|
document.getElementById('tomove').childNodes[0].data = whiteMove ? 'Black' : 'White';
|
|
|
|
// Record our alterations to the table
|
|
let moveRecord = {
|
|
removedPieces: removedPieces,
|
|
placedPieces: placedPieces,
|
|
moveText: move
|
|
};
|
|
moveHistory.push(moveRecord);
|
|
|
|
return {}
|
|
}
|
|
|
|
function doCastle(move) {
|
|
let ballChar = move[0];
|
|
|
|
// Figure out distance between king and rook
|
|
let emptySquares = 1;
|
|
let index = 1;
|
|
while (index < move.length) {
|
|
if (move.slice(index, index + 2) === '-' + ballChar) {
|
|
emptySquares += 1;
|
|
index += 2;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check(mate)
|
|
if (!['', '+', '++', '#'].includes(move.slice(index))) {
|
|
// TODO: Implement handling
|
|
return {error: 'Unexpected contents at the end of the move: ' + move.slice(index)};
|
|
}
|
|
|
|
// Find the king(s)
|
|
let whiteMove = document.getElementById('tomove').childNodes[0].data === 'White';
|
|
let renderedKing = whiteMove ? '♔' : '♚';
|
|
let renderedRook = whiteMove ? '♖' : '♜';
|
|
|
|
let kings = [];
|
|
for (let kingLetter of 'abcdefgh') {
|
|
for (let kingNumber of '12345678') {
|
|
let square = document.getElementById(kingLetter + kingNumber);
|
|
if (square.childNodes.length !== 0 && square.childNodes[0].data === renderedKing) {
|
|
kings.push([kingLetter, kingNumber]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find the rook(s)
|
|
function squareDiff(squareLetter, squareNumber, dLetter, dNumber) {
|
|
let x = 'abcdefgh'.indexOf(squareLetter) + dLetter;
|
|
let y = '12345678'.indexOf(squareNumber) + dNumber;
|
|
if (x < 0 || x > 7 || y < 0 || y > 7) {
|
|
// Would be outside of the board
|
|
return null;
|
|
}
|
|
return ['abcdefgh'[x], '12345678'[y]]
|
|
}
|
|
|
|
let distance = emptySquares + 1;
|
|
let possiblePairs = [];
|
|
for (let [kingLetter, kingNumber] of kings) {
|
|
for (let [dLetter, dNumber] of [[-distance, 0], [distance, 0], [0, -distance], [0, distance]]) {
|
|
let rookCoords = squareDiff(kingLetter, kingNumber, dLetter, dNumber);
|
|
if (rookCoords === null) {
|
|
continue;
|
|
}
|
|
let [rookLetter, rookNumber] = rookCoords;
|
|
|
|
let square = document.getElementById(rookLetter + rookNumber);
|
|
if (square.childNodes.length !== 0 && square.childNodes[0].data === renderedRook) {
|
|
possiblePairs.push([kingLetter, kingNumber, rookLetter, rookNumber, dLetter, dNumber]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (possiblePairs.length === 0) {
|
|
return {error: 'Could not find king and rook matching castling criteria'};
|
|
} else if (possiblePairs.length > 1) {
|
|
return {error: 'Found ' + String(possiblePairs.length) + ' possible pairs of king and rook to castle'};
|
|
}
|
|
|
|
let [kingLetter, kingNumber, rookLetter, rookNumber, dLetter, dNumber] = possiblePairs[0];
|
|
let kingStartSquare = document.getElementById(kingLetter + kingNumber);
|
|
let rookStartSquare = document.getElementById(rookLetter + rookNumber);
|
|
|
|
// Make sure there there are no pieces between the king and the rook
|
|
let signLetter = Math.sign(dLetter);
|
|
let signNumber = Math.sign(dNumber);
|
|
let [squareLetter, squareNumber] = squareDiff(kingLetter, kingNumber, signLetter, signNumber);
|
|
while (squareLetter !== rookLetter || squareNumber !== rookNumber) {
|
|
if (document.getElementById(squareLetter + squareNumber).childNodes.length !== 0) {
|
|
return {error: squareLetter + squareNumber + ' is occupied'};
|
|
}
|
|
[squareLetter, squareNumber] = squareDiff(squareLetter, squareNumber, signLetter, signNumber);
|
|
}
|
|
|
|
// King moves two squares towards rook
|
|
let [kingEndLetter, kingEndNumber] = squareDiff(kingLetter, kingNumber, 2 * signLetter, 2 * signNumber);
|
|
let kingEndSquare = document.getElementById(kingEndLetter + kingEndNumber);
|
|
|
|
// Rook moves to the square king crossed
|
|
let [rookEndLetter, rookEndNumber] = squareDiff(kingLetter, kingNumber, signLetter, signNumber);
|
|
let rookEndSquare = document.getElementById(rookEndLetter + rookEndNumber);
|
|
|
|
// Move pieces
|
|
kingStartSquare.removeChild(kingStartSquare.childNodes[0]);
|
|
rookStartSquare.removeChild(rookStartSquare.childNodes[0]);
|
|
kingEndSquare.appendChild(document.createTextNode(renderedKing));
|
|
rookEndSquare.appendChild(document.createTextNode(renderedRook));
|
|
|
|
// Flip whose turn it is
|
|
document.getElementById('tomove').childNodes[0].data = whiteMove ? 'Black' : 'White';
|
|
|
|
// Record our alterations to the table
|
|
let removedPieces = {};
|
|
let placedPieces = {};
|
|
removedPieces[kingLetter + kingNumber] = renderedKing;
|
|
removedPieces[rookLetter + rookNumber] = renderedRook;
|
|
placedPieces[kingEndLetter + kingEndNumber] = renderedKing;
|
|
placedPieces[rookEndLetter + rookEndNumber] = renderedRook;
|
|
let moveRecord = {
|
|
removedPieces: removedPieces,
|
|
placedPieces: placedPieces,
|
|
moveText: move
|
|
};
|
|
moveHistory.push(moveRecord);
|
|
|
|
return {}
|
|
}
|
|
|
|
function undoMove() {
|
|
if (moveHistory.length === 0) {
|
|
alert('No moves to undo');
|
|
return;
|
|
}
|
|
|
|
// Undo move on the board
|
|
let {removedPieces, placedPieces, moveText} = moveHistory.pop();
|
|
for (let coords in placedPieces) {
|
|
let square = document.getElementById(coords);
|
|
square.removeChild(square.childNodes[0]);
|
|
}
|
|
for (let coords in removedPieces) {
|
|
let square = document.getElementById(coords);
|
|
square.appendChild(document.createTextNode(removedPieces[coords]));
|
|
}
|
|
|
|
// Flip whose turn it is
|
|
let whiteMove = document.getElementById('tomove').childNodes[0].data === 'White';
|
|
document.getElementById('tomove').childNodes[0].data = whiteMove ? 'Black' : 'White';
|
|
|
|
// Add to list of redoable moves
|
|
moveFuture.push(moveText);
|
|
|
|
// Mark as undone on the list of moves
|
|
document.getElementById('movelist').children[moveHistory.length].className = 'undone';
|
|
}
|
|
|
|
function redoMove() {
|
|
if (moveFuture.length === 0) {
|
|
alert('No moves to redo');
|
|
return;
|
|
}
|
|
|
|
let index = moveHistory.length;
|
|
let move = moveFuture.pop();
|
|
|
|
// Execute move
|
|
let ret = doMove(move);
|
|
if ('error' in ret) {
|
|
alert(ret.error);
|
|
// Put back in list of undone moves
|
|
moveFuture.push(move);
|
|
return false;
|
|
}
|
|
|
|
// Mark it as un-undone in the list of moves
|
|
document.getElementById('movelist').children[index].className = '';
|
|
|
|
return true;
|
|
}
|
|
|
|
function undoAll() {
|
|
while (moveHistory.length > 0) {
|
|
undoMove();
|
|
}
|
|
}
|
|
|
|
function redoAll() {
|
|
while (moveFuture.length > 0) {
|
|
if (!redoMove()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function flipBoard() {
|
|
let oldBoard = document.getElementById('board').firstElementChild;
|
|
|
|
let newBoard = document.createElement('tbody');
|
|
|
|
// Create a set of new rows that are in the same order as in the old board
|
|
// but have their columns reversed
|
|
let rows = [];
|
|
for (let oldRow of oldBoard.children) {
|
|
let newRow = document.createElement('tr');
|
|
newRow.id = oldRow.id;
|
|
|
|
let columns = Array.from(oldRow.children);
|
|
|
|
// Treat the first column specially, as it is the label
|
|
newRow.appendChild(columns[0]);
|
|
|
|
// Add rest of columns in reverse order
|
|
for (let i = 8; i >= 1; i--) {
|
|
newRow.appendChild(columns[i]);
|
|
}
|
|
|
|
rows.push(newRow);
|
|
}
|
|
|
|
// Reverse the order of the first 8 rows
|
|
// This combined with the flipping of the columns creates a rotation
|
|
// The proof of this is left as homework for the linear algebra student reading this
|
|
for (let i = 7; i >= 0; i--) {
|
|
newBoard.appendChild(rows[i]);
|
|
}
|
|
|
|
// Treat the last row specifically, as it is a row of labels
|
|
// We did however reverse its column order, as all the columns have been reversed
|
|
// and it's important that the labels reflect that
|
|
newBoard.appendChild(rows[8]);
|
|
|
|
// Replace the old board with the new one
|
|
document.getElementById('board').replaceChild(newBoard, oldBoard);
|
|
}
|
|
|
|
function download() {
|
|
let actions = document.getElementById("actions");
|
|
let moves = document.getElementById("movelist");
|
|
let link = document.createElement("a");
|
|
|
|
link.setAttribute("download", "moves.text");
|
|
link.style.display = "none";
|
|
actions.appendChild(link);
|
|
|
|
var pos = 0;
|
|
var game = [];
|
|
var longest = 0;
|
|
|
|
for(let move = 0; move < moves.children.length; move += 1) {
|
|
let record = moves.children[move];
|
|
if (record.className === 'undone') {
|
|
// Don't include undone moves
|
|
continue;
|
|
}
|
|
|
|
if(move % 2 === 0) {
|
|
if(record.childNodes[1].nodeValue.length > longest) {
|
|
longest = record.childNodes[1].nodeValue.length;
|
|
}
|
|
|
|
game[pos] = [record.childNodes[1].nodeValue];
|
|
} else {
|
|
game[pos].push(record.innerHTML);
|
|
pos += 1;
|
|
}
|
|
}
|
|
|
|
let file = [];
|
|
let padding = Math.floor(Math.log10(game.length));
|
|
|
|
for(let entry = 0; entry < game.length; entry += 1) {
|
|
var line = `${entry + 1}`.padStart(padding + 1, " ") + ". ";
|
|
|
|
if(game[entry][1] !== undefined) {
|
|
line += `${game[entry][0].padEnd(longest, " ")} ${game[entry][1]}`;
|
|
} else {
|
|
line += game[entry][0];
|
|
}
|
|
|
|
file.push(line);
|
|
}
|
|
|
|
// Include final \n since we're doing unix-style line endings (instead of line separators)
|
|
let contents = encodeURIComponent(file.join("\n") + "\n");
|
|
|
|
link.setAttribute("href", "data:text/plain;charset=utf-8," + contents);
|
|
link.click();
|
|
actions.removeChild(link);
|
|
}
|
|
|
|
function setupKings() {
|
|
let options = new FormData(document.getElementById('options'));
|
|
let whiteKing = options.get('whiteking');
|
|
let whiteQueen = whiteKing === 'd1' ? 'e1' : 'd1';
|
|
let blackKing = options.get('blackking');
|
|
let blackQueen = blackKing === 'd8' ? 'e8' : 'd8';
|
|
|
|
// Undo all moves
|
|
let historylength = moveHistory.length;
|
|
while (moveHistory.length > 0) {
|
|
undoMove();
|
|
}
|
|
|
|
// Place the kings and queens
|
|
document.getElementById(whiteKing).firstChild.data = '♔';
|
|
document.getElementById(whiteQueen).firstChild.data = '♕';
|
|
document.getElementById(blackKing).firstChild.data = '♚';
|
|
document.getElementById(blackQueen).firstChild.data = '♛';
|
|
|
|
// Redo until we can or until the point where we were before, whichever is first
|
|
while (moveHistory.length < historylength) {
|
|
if (!redoMove()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
let moveUpload = document.getElementById("moveupload");
|
|
|
|
moveUpload.addEventListener("change", (event) => {
|
|
let file = event.target.files[0];
|
|
let reader = new FileReader();
|
|
let moveLine = /^\s*\d+\. /;
|
|
|
|
reader.addEventListener("load", (event) => {
|
|
let moveFile = event.target;
|
|
|
|
if(moveFile.error === null) {
|
|
// reset the board
|
|
undoAll();
|
|
moveFuture = [];
|
|
|
|
let moveList = document.getElementById("movelist");
|
|
|
|
while(moveList.firstChild) {
|
|
moveList.removeChild(moveList.firstChild);
|
|
}
|
|
|
|
// parse the file
|
|
let rows = moveFile.result.split("\n");
|
|
|
|
for(let row of rows) {
|
|
if(moveLine.test(row)) {
|
|
let entries = row.replace(moveLine, "").split(/\s+/);
|
|
|
|
for(let entry of entries) {
|
|
let ret = doMove(entry);
|
|
|
|
if("error" in ret) {
|
|
alert(ret.error);
|
|
return;
|
|
}
|
|
updateMoveTable(entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
reader.readAsText(file);
|
|
|
|
// clear the upload button
|
|
let hiddenLink = document.getElementById("moveupload");
|
|
hiddenLink.value = "";
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|