Skip to content
This repository has been archived by the owner on Dec 5, 2024. It is now read-only.

Commit

Permalink
Add puzzles mode (#18)
Browse files Browse the repository at this point in the history
* csv reader

* Update puzzles.js

* fixed to await

* fix

* grabs a selection of puzzles

* added return

* fix infinite loop

* syntax

* added board creator

* syntax

* added movement functionality

* Update puzzles.js

* syntax fix

* Update puzzles.js

* syntax fix

* Added initialisation and made puzzle move checker

* added menu element for puzzles

* syntax error fixed

* fixed interaction with fenfurnace

* puzzles fix

* added moves per colour

* fixed correct move issue

todo: add exception case handling for promotion

* error fix

* window -> global

* Update puzzles.js

* added button to move to next puzzle

* fixed await

* flips board to correct position

* cleared message on puzzle completion

* add difficulty slider

* move data file

* Update readme.md

* formatting

* Update index.html

* pass through data folder

* display title and next properly

* clear move log when done

* move next puzzle button

* Update puzzles.js

* added puzzles icon

* Update index.html

* Fix ingame status not being reset

* Add hint button

* highlight hinted square instead

* Filter with difficulty

* sort puzzles iteratively until list is usable

* fix merge artefact

* display failed attempts

* copyedit

* unhide failed move count

* copyedit

* reset puzzle attempts on puzzle change

* fix randomiser

* state promotion piece

* clear highlighting on puzzle change

* Add option to load specific puzzle

* expanded amount of puzzles

* NEW AND IMPROVED PUZZLES

* remove unnecessary data from puzzle list

Co-authored-by: Nixinova <[email protected]>
Co-authored-by: nimitoburrito <[email protected]>
  • Loading branch information
3 people authored Jun 22, 2021
1 parent 133535c commit 08b7fd3
Show file tree
Hide file tree
Showing 14 changed files with 1,553 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { execSync } = require('child_process');

module.exports = function (cfg) {
const passThruPaths = [
'images/', 'scripts/', 'styles/',
'images/', 'data/', 'scripts/', 'styles/',
'_redirects', 'favicon.ico',
];

Expand Down
1,338 changes: 1,338 additions & 0 deletions data/puzzles.csv

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions images/icons/puzzle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pages/home/bot.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ <h2>Bot intelligence</h2>
<div style="width: 100%;">
<label>
<span style="display:none;">Bot intelligence</span>
<input type="range" autocomplete="off" name="botIntelligence" id="bot-intelligence-level" value="0" min="0" max="3" oninput="updateBotIntelligence();">
<input type="range" autocomplete="off" name="botIntelligence" value="1" min="0" max="3" id="bot-intelligence-level" oninput="updateSlider('bot-intelligence');">
</label>
<div id="bot-intelligence-dots">
<span id="dot-highlighted"></span> • • •
Expand Down
5 changes: 5 additions & 0 deletions pages/home/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@ <h2>Select opponent</h2>
<span class="card-description">Play against someone anywhere in the world</span>
</a>

<a href="/menu/puzzles/" class="card big">
<img src="/images/icons/puzzle.svg" height="100px" alt="Online" class="card-image">
<span class="card-heading">Puzzles</span>
<span class="card-description">Find the best move in various levels</span>
</a>
</div>
38 changes: 38 additions & 0 deletions pages/home/puzzles.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
permalink: /menu/puzzles/
layout: menu.njk
---

<form action="/play" method="GET">

<h2>Puzzles</h2>
<div>
<label class="hide">
<strong>Puzzles</strong>
<input type="checkbox" checked name="puzzles">
</label>
</div>

<div>
<div style="width: 100%;">
<label>
<strong>Difficulty</strong><br>
<input type="range" autocomplete="off" name="difficulty" value="1700" min="1000" max="2400" id="puzzles-level">
</label>
<label>
<strong>Starting Puzzle</strong><br>
<input type="text" maxlength="5" name="puzzlename" placeholder="Random">
</label>
</div>
</div>

<div id="nav-buttons">
<a href="/" id="back" class="card wide" title="Return to menu">
Back
</a>
<button type="submit" id="play" class="card wide">
Play
</button>
</div>

</form>
15 changes: 13 additions & 2 deletions pages/play.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<script src="/scripts/play/online.js"></script>
<script src="/scripts/play/pagelog.js"></script>
<script src="/scripts/play/pieces.js"></script>
<script src="/scripts/play/puzzles.js"></script>
<script src="/scripts/ext/fenfurnace/main.js"></script>
</head>

Expand Down Expand Up @@ -54,11 +55,18 @@ <h1>

<aside id="right-sidebar">
<div id="game-data">
<strong>Session</strong>
<h3>Session</h3>
<dl id="game-data_content"></dl>
</div>
<strong>Moves</strong>
<h3>Moves</h3>
<div id="log" class="resettable"></div>
<div id="puzzle-attempts" class="hide">
<strong>Attempts:</strong>
<span id="puzzle-attempts-value">0</span>
</div>
<button id="next-puzzle" class="hide" onclick="nextPuzzle()" title="Next puzzle">
Next Puzzle &rarr;
</button>
</aside>

<aside id="left-sidebar">
Expand All @@ -76,6 +84,9 @@ <h1>
<button onclick="shareGame()" title="Copy to clipboard">
Share
</button>
<button id="puzzles-hint" class="hide" onclick="showPuzzleHint()" title="Get a hint">
Hint
</button>
</div>

<form action="javascript:sendChatMessage()" id="send-chat">
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ Try a game at https://carbonchess.com.
- Play with a friend (or yourself) on the same device.
- **Online**
- Play online with someone anywhere in the world.
- **Puzzles**
- Test your move-making skills on a variety of carefully-constructed puzzles.
4 changes: 2 additions & 2 deletions scripts/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ const debug = (...args) => console.log('DEBUG', ...args);
const indexToLetter = n => String.fromCharCode(n + 64);
const getClasses = elem => Array.from(elem?.classList || []);
const invertColour = colour => colour === 'white' ? 'black' : 'white';
const random = (min = 0, max = 99, len) => Math.floor(Math.random() * (max - min + 1) + min).toString().padStart(len || 0, '0');
const random = (min = 0, max = 1) => Math.floor(Math.random() * (max - min + 1) + min);
const randomID = (len = 5) => random(0, +'9'.repeat(len)).toString().padStart(len, '0');
const copy = text => navigator.clipboard.writeText(text);
const addGameData = (title, content) => $('#game-data_content').innerHTML += `<dt>${title}</dt><dd>${content}</dd>`;
const addGameData = (title, content, id) => $('#game-data_content').innerHTML += `<dt id="${id}">${title}</dt><dd>${content}</dd>`;

const SEP = { MSG: '\x1e', INFO: '\x1f' };

Expand Down
6 changes: 3 additions & 3 deletions scripts/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ function updateSelection(elem) {
elem.classList.add('selected');
}

function updateBotIntelligence() {
const level = $('#bot-intelligence-level').value;
function updateSlider(id) {
const level = $(`#${id}-level`).value;
$$(`[data-level]`).forEach(elem => elem.classList.add('hide'));
$(`[data-level="${level}"]`).classList.remove('hide');
$('#bot-intelligence-dots').innerHTML = ' • '.repeat(level) + ` <span id="dot-highlighted">•</span> ` + ' • '.repeat(3 - level);
$(`#${id}-dots`).innerHTML = ' • '.repeat(level - 1) + ` <span id="dot-highlighted">•</span> ` + ' • '.repeat(4 - level);
}
38 changes: 27 additions & 11 deletions scripts/play/game-cycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ function hasClicked(cell) {

// promotion
const canPromote = piece === 'pawn' && ['1', '8'].includes(endCell[1]);
if (canPromote) {
global.promotionPiece = getPieceID(window.promotionPiece).toLowerCase();
if (canPromote && !global.promotionPiece) {
global.promotionPiece = getPieceID(window.promotionPiece) || 'q';
}

// move the piece
Expand Down Expand Up @@ -77,6 +77,27 @@ function hasClicked(cell) {
// check game ending status
checkGameEnding();

// check if correct puzzle move has been made
if (window.gameOptions.puzzles && puzzleColour === global.currentTurn && movesToMake) {
if (startCell === movesToMake[0].slice(0, 2).toUpperCase() && endCell === movesToMake[0].slice(2, 4).toUpperCase()) {
movesToMake.shift();
if (movesToMake.length > 0) {
$.id('winner').innerHTML = 'Correct, now find the next move'
setTimeout(puzzleMove, 500);
}
else {
$.id('winner').innerHTML = 'Well done';
$.id('next-puzzle').classList.remove('hide');
}
}
else {
undoLastMove();
$.id('winner').innerHTML = 'Wrong, try again';
window.failedPuzzleAttempts++;
}
$.id('puzzle-attempts-value').innerText = window.failedPuzzleAttempts;
}

// send to server
if (window.autoPing) sendDB();

Expand All @@ -95,7 +116,7 @@ function hasClicked(cell) {
}

selectPiece(cell);
console.log('\n' + (totalMoves + 1));
console.log('\n' + (window.totalMoves + 1));
console.log('T', ...cellClasses);

$$('#promotion img').forEach(elem => {
Expand All @@ -114,7 +135,7 @@ function checkHighlight() {
if (isCheck('w')) $('.white.king').parentElement.classList.add('check');
if (isCheck('b')) $('.black.king').parentElement.classList.add('check');

let { start, end } = window.lastMove;
const { start, end } = window.lastMove;
$$('td').forEach(elem => elem.classList.remove('last-move'));
[start, end].forEach(cell => $.id(cell)?.classList.add('last-move'));
}
Expand All @@ -136,9 +157,7 @@ function undoLastMove() {
logPoints();
checkHighlight();
if (window.autoFlip) alignBoard();
$$(`[data-move="${totalMoves}"]`).forEach(elem => {
if (elem.parentNode) elem.parentNode.innerHTML = '';
});
$$(`[data-move="${totalMoves}"]`).forEach(elem => elem.parentNode.innerHTML = '');
$('#log').removeChild($('#log').lastChild);
$('#winner').innerText = '';
if (window.autoPing) sendDB();
Expand All @@ -148,9 +167,6 @@ function undoLastMove() {
function threefoldRepetition() {
const lastFen = movesList[movesList.length - 1].replace(/ . .$/, '');
let repetitions = 0;
for (let i in movesList) {
if (movesList[i].replace(/ . .$/, '') === lastFen)
repetitions++;
}
movesList.forEach(move => (move.replace(/ . .$/, '') === lastFen) && repetitions++);
return repetitions >= 3;
}
26 changes: 22 additions & 4 deletions scripts/play/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ function run() {
gamecode: params.get('gamecode'),
static: booleanParam('static'),
spectating: booleanParam('spectating'),
puzzles: booleanParam('puzzles'),
startingPuzzle: params.get('puzzlename'),
difficulty: +params.get('difficulty'),
}
window.firstLoad = false;

Expand All @@ -34,6 +37,7 @@ function run() {
window.lastEnpassantCell = enpassantCell;
window.fmrMoves = 0;
window.failedMoveCount = 0;
window.failedPuzzleAttempts = 0;
window.autoFlip = gameOptions.autoFlip;
window.autoPing = gameOptions.multiplayer;
window.hasRules = gameOptions.rules;
Expand All @@ -42,8 +46,8 @@ function run() {
window.chat = [];

$('#game-data_content').innerHTML = '';
addGameData('Opponent', gameOptions.bot ? 'Bot' : (gameOptions.multiplayer && !gameOptions.static) ? 'Online' : 'Local');
$('body').dataset.mode = gameOptions.multiplayer ? 'multiplayer' : 'singleplayer';
$('body').dataset.mode = gameOptions.multiplayer ? 'multiplayer' : gameOptions.puzzles ? 'singleplayer' : 'puzzles';
addGameData('Opponent', (gameOptions.bot || gameOptions.puzzles) ? 'Bot' : (gameOptions.multiplayer && !gameOptions.static) ? 'Online' : 'Local');
if (gameOptions.multiplayer) {
addGameData('Game ID', window.gameId);
$('#winner').innerText = 'Loading...';
Expand All @@ -59,14 +63,28 @@ function run() {
if (gameOptions.spectating) {
addGameData('Spectating', 'Yes');
}
if (gameOptions.puzzles) {
addGameData('Puzzles Mode', 'Yes');
addGameData('Difficulty', gameOptions.difficulty);
if (gameOptions.startingPuzzle) addGameData('Current Puzzle', '', 'current-puzzle-name');
$.id('winner').innerText = 'Find the best move';
$.id('puzzles-hint').classList.remove('hide');
$.id('puzzle-attempts').classList.remove('hide');
}
if (!gameOptions.autoFlip) {
$('body').dataset.noflip = true;
}

Object.assign(window, fenFuncs);

setupBoard();
newBoard(8, true);
alignBoard();
if (gameOptions.puzzles) {
getPuzzles(gameOptions.startingPuzzle).then(() => setBoard(0));
}
else {
newBoard(8, true);
alignBoard();
}

}

Expand Down
91 changes: 91 additions & 0 deletions scripts/play/puzzles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
let savedPuzzles;
let movesToMake;
let puzzleColour;
let puzzlePosition = 0;

function processData(allText) {
const allTextLines = allText.split(/\r\n|\n/);
const headers = allTextLines[0].split(',');
let lines = [];

for (let i = 1; i < allTextLines.length; i++) {
const data = allTextLines[i].split(',');
if (data.length === headers.length) {
let textArray = [];
for (let j = 0; j < headers.length; j++) {
textArray.push(headers[j] + ":" + data[j]);
}
lines.push(textArray);
}
}
return lines;
}

async function getPuzzles(start) {
const puzzleCache = 10;

// fetch puzzle data
let fileData = await fetch('/data/puzzles.csv').then(data => data.text());
let puzzleList = processData(fileData);
puzzleList = puzzleList.map(array => Object.fromEntries(array.map(item => item.split(':')))); // convert to array of objects

// attempt to filter to current difficulty
let sortedPuzzles = puzzleList;
for (let i = 200; i < 800; i += 200) {
sortedPuzzles = puzzleList.filter(obj => Math.abs(obj.ELO - gameOptions.difficulty) < i);
if (sortedPuzzles.length >= 10) break;
}
puzzleList = sortedPuzzles;

// cache list of puzzles
let selection = [];
for (let i = 1; i <= puzzleCache; i++) {
selection.push(puzzleList[random(0, puzzleList.length - 1)]);
}
savedPuzzles = selection;

// set first puzzle if explicitly specified
if (start) {
const customPuzzle = puzzleList.find(obj => obj.ID === start);
if (customPuzzle) savedPuzzles.unshift(customPuzzle);
}
}

function puzzleMove() {
const [, start, end, promotion] = movesToMake[0].toUpperCase().match(/^(..)(..)(.?)/);
if (promotion) global.promotionPiece = promotion;
hasClicked(start);
hasClicked(end);
movesToMake.shift();
}

function setBoard(item) {
$.id('current-puzzle-name').innerText = savedPuzzles[item].ID;
createBoardFromFen(savedPuzzles[item].FEN);
movesToMake = savedPuzzles[item].Moves.split(' ');
alignBoard();
flipBoard();
puzzleColour = global.currentTurn;
setTimeout(puzzleMove, 1000);
}

function nextPuzzle() {
window.ingame = true;
window.failedPuzzleAttempts = 0;
$.id('puzzle-attempts-value').innerText = window.failedPuzzleAttempts;
$$('td').forEach(elem => elem.setAttribute('class', ''));
if (puzzlePosition === 9) {
puzzlePosition = 0;
getPuzzles().then(setBoard(puzzlePosition));
} else {
puzzlePosition++;
setupBoard();
setBoard(puzzlePosition);
}
$.id('next-puzzle').classList.add('hide');
$.id('winner').innerHTML = 'Find the best move';
}

function showPuzzleHint() {
$.id(movesToMake[0].slice(0, 2).toUpperCase())?.classList.add('valid');
}
10 changes: 9 additions & 1 deletion styles/play.nvss
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ button.disabled {
grid-area: rsidebar;
overflow-y: auto;

strong {
h3 {
margin-bottom: 0.5em;
display: block;
text-align: center;
Expand All @@ -402,6 +402,14 @@ button.disabled {
overflow: auto;
}
}

#puzzle-attempts {
padding: 0.5em;
}

#next-puzzle {
margin: auto auto 2em;
}
}

/* Visibility */
Expand Down

0 comments on commit 08b7fd3

Please sign in to comment.