This commit is contained in:
nbillard 2023-01-06 16:07:10 +01:00
commit 9c00d29ffa
7 changed files with 122 additions and 19 deletions

View file

@ -1,9 +1,10 @@
<!doctype html> <!doctype html>
<html id="html" lang=""> <html id="html" lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge"> <meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Sokoban</title> <title>Sokoban</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@ -26,6 +27,7 @@
</ol> </ol>
</nav> </nav>
<div class="container"> <div class="container">
<!-- Scoreboard -->
<aside class="left-sidebar"> <aside class="left-sidebar">
<h2>Score</h2> <h2>Score</h2>
<div class="input-field"> <div class="input-field">
@ -43,6 +45,8 @@
</tbody> </tbody>
</table> </table>
</aside> </aside>
<!-- Game -->
<main> <main>
<article class="not-in-win-animation"> <article class="not-in-win-animation">
<canvas id="canvas" width="800" height="400"></canvas> <canvas id="canvas" width="800" height="400"></canvas>
@ -58,6 +62,8 @@
</article> </article>
</main> </main>
<!-- Controls -->
<aside class="controls not-in-win-animation right-sidebar"> <aside class="controls not-in-win-animation right-sidebar">
<div id="timer">Time</div> <div id="timer">Time</div>
<button id="pause-1">Pause</button> <button id="pause-1">Pause</button>

View file

@ -1,13 +1,15 @@
/**
* @fileoverview This file contains the functions to select a level
*/
import { levelsBlueprint } from '/modules/levels.mjs' import { levelsBlueprint } from '/modules/levels.mjs'
import { generatePlayground } from '/modules/playground.mjs' import { generatePlayground } from '/modules/playground.mjs'
import { Timer } from '/modules/timer.mjs' import { Timer } from '/modules/timer.mjs'
const prionicSequence = [0, 2, 6, 12, 20, 30, 42]; const prionicSequence = [0, 2, 6, 12, 20, 30, 42];
// This funcion is called when the player selects a level and starts the game
export const selectLevel = (ctx, gameState, id) => { export const selectLevel = (ctx, gameState, id) => {
gameState.playground = generatePlayground(levelsBlueprint[id].structure, gameState.width, gameState.height); gameState.playground = generatePlayground(levelsBlueprint[id].structure, gameState.width, gameState.height);
// TODO transfer expireFunction without a fail
// const expireFunc = () => {gameState.timer.expireFunction();};
gameState.timer.setTime(levelsBlueprint[id].time); gameState.timer.setTime(levelsBlueprint[id].time);
gameState.playable = true; gameState.playable = true;
gameState.tutorial.hide(); gameState.tutorial.hide();
@ -15,6 +17,7 @@ export const selectLevel = (ctx, gameState, id) => {
gameState.playground.draw(ctx, gameState.width, gameState.height); gameState.playground.draw(ctx, gameState.width, gameState.height);
} }
// This function fills the level selection menu with the levels
export const fillLevelsSelection = (gameState, ctx) => { export const fillLevelsSelection = (gameState, ctx) => {
let levelList = document.getElementById('level-list'); let levelList = document.getElementById('level-list');
for (let i = 0; i < levelsBlueprint.length; ++i) { for (let i = 0; i < levelsBlueprint.length; ++i) {
@ -30,6 +33,7 @@ export const fillLevelsSelection = (gameState, ctx) => {
} }
} }
// This class manages the levels and their completion
export class LevelManager { export class LevelManager {
constructor(winFunction, StartingLevelId = 0) { constructor(winFunction, StartingLevelId = 0) {
self.CurrentLevelId = StartingLevelId; self.CurrentLevelId = StartingLevelId;
@ -45,16 +49,13 @@ export class LevelManager {
}; };
} }
// getFirstUncompleted() {
// self.getFirstUncompleted();
// }
// This function is called when the player completes a level // This function is called when the player completes a level
// It checks if all levels are completed and calls the winFunction // It checks if all levels are completed and calls the winFunction
// If not, it selects the next level // If not, it selects the next level
next(ctx, gameState) { next(ctx, gameState) {
let score = gameState.timer.getTime(); let score = gameState.timer.getTime();
gameState.scoreboard.updateScoreCurrentGamer(score); gameState.scoreboard.updateScoreCurrentGamer(score);
self.Completed[self.CurrentLevelId] = true; self.Completed[self.CurrentLevelId] = true;
let allLevelsFinished = self.Completed.reduce((a, b) => { let allLevelsFinished = self.Completed.reduce((a, b) => {
return a && b; return a && b;
@ -62,8 +63,9 @@ export class LevelManager {
if (allLevelsFinished) { if (allLevelsFinished) {
self.winFunction(); self.winFunction();
} }
self.CurrentLevelId = self.getFirstUncompleted(); self.CurrentLevelId = self.getFirstUncompleted();
console.log(self.CurrentLevelId); // console.log(self.CurrentLevelId);
selectLevel(ctx, gameState, self.CurrentLevelId); selectLevel(ctx, gameState, self.CurrentLevelId);
} }
} }

View file

@ -1,7 +1,9 @@
/**
* @fileoverview This file contains the blueprints for the levels.
*/
import { Square } from '/modules/enums.mjs'; import { Square } from '/modules/enums.mjs';
// Blueprint for the first level // Blueprint for the first level
//
const level1Blueprint = { const level1Blueprint = {
structure:[[ Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall ], structure:[[ Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall ],
[ Square.Wall, Square.Destination, Square.Box, Square.Floor, Square.Player, Square.Wall ], [ Square.Wall, Square.Destination, Square.Box, Square.Floor, Square.Player, Square.Wall ],
@ -25,6 +27,7 @@ const level2Blueprint = {
time: 23000, time: 23000,
}; };
// Blueprint for the third level
const level3Blueprint = { const level3Blueprint = {
structure: [ structure: [
[Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall,], [Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall,],
@ -40,6 +43,7 @@ const level3Blueprint = {
time: 8000, time: 8000,
} }
// Blueprint for the fourth level
const level4Blueprint = { const level4Blueprint = {
structure: [ structure: [
[Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall,], [Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall,],
@ -56,6 +60,7 @@ const level4Blueprint = {
time: 3000, time: 3000,
} }
// Blueprint for the fifth level
const level5Blueprint = { const level5Blueprint = {
structure: [ structure: [
[Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, ], [Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, ],
@ -72,6 +77,7 @@ const level5Blueprint = {
time: 40000 time: 40000
} }
// Blueprint for the sixth level
const level6Blueprint = { const level6Blueprint = {
structure: [ structure: [
[Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall], [Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall],
@ -95,6 +101,7 @@ const level6Blueprint = {
time: 50000 time: 50000
} }
// Blueprint for the seventh level
const level7Blueprint = { const level7Blueprint = {
structure: [ structure: [
[Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Floor, Square.Floor, Square.Floor, Square.Floor, Square.Floor, Square.Floor, Square.Wall, Square.Wall, Square.Wall, Square.Wall], [Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Wall, Square.Floor, Square.Floor, Square.Floor, Square.Floor, Square.Floor, Square.Floor, Square.Wall, Square.Wall, Square.Wall, Square.Wall],
@ -115,6 +122,7 @@ const level7Blueprint = {
} }
// Array of blueprints for all levels
export const levelsBlueprint = [ export const levelsBlueprint = [
level1Blueprint, level1Blueprint,
level2Blueprint, level2Blueprint,

View file

@ -1,3 +1,7 @@
/**
* @fileoverview This file contains the Playground class.
*/
import { Square, CanMove, MoveDirection } from '/modules/enums.mjs' import { Square, CanMove, MoveDirection } from '/modules/enums.mjs'
import { Position, copyPosition } from '/modules/position.mjs' import { Position, copyPosition } from '/modules/position.mjs'
import { ForegroundTile, BackgroundTile } from '/modules/tiles.mjs' import { ForegroundTile, BackgroundTile } from '/modules/tiles.mjs'
@ -18,6 +22,7 @@ export const generatePlayground = (levelBlueprint, canvasWidth, canvasHeight) =>
x: NaN, x: NaN,
y: NaN, y: NaN,
}; };
levelBlueprint.forEach((levelRow, indexRow) => { levelBlueprint.forEach((levelRow, indexRow) => {
background.push([]); background.push([]);
foreground.push([]); foreground.push([]);

View file

@ -7,7 +7,7 @@
/** /**
* @class Scoreboard * @class Scoreboard
* @classdesc This class is responsible for the scoreboard. * @classdesc This class is responsible to display the scoreboard and manage scores of the gamers.
*/ */
export class Scoreboard { export class Scoreboard {
@ -24,9 +24,9 @@ export class Scoreboard {
/** /**
* @description * After name changed in the input field, this method is called.
* If the name is different from the current gamer, a new gamer is created.
* @param {String} name - The name of the gamer. * @param {String} name - The name of the gamer.
*
*/ */
updatedName(name) { updatedName(name) {
let previousGamer = this.currentGamer; let previousGamer = this.currentGamer;
@ -37,23 +37,33 @@ export class Scoreboard {
if (previousGamer != this.currentGamer && previousGamer.score == 0) { if (previousGamer != this.currentGamer && previousGamer.score == 0) {
this.removeGamer(previousGamer); this.removeGamer(previousGamer);
console.log(this.gamers);
} }
this.renderArray(); this.renderArray();
} }
/**
* Add a gamer to the list of gamers.
* @param {Gamer} gamer
*/
addGamer(gamer) { addGamer(gamer) {
this.gamers.push(gamer); this.gamers.push(gamer);
} }
/**
* Remove a gamer from the list of gamers.
* @param {Gamer} gamer
*/
removeGamer(gamer) { removeGamer(gamer) {
this.gamers.splice(this.gamers.indexOf(gamer), 1); this.gamers.splice(this.gamers.indexOf(gamer), 1);
} }
/**
* Render the list of gamers in the table.
*/
renderArray() { renderArray() {
let table = document.getElementById("scoreTable"); let table = document.getElementById("scoreTable");
table.innerHTML = ""; table.innerHTML = ""; // Clear the table
this.gamers.forEach((gamer, index) => { this.gamers.forEach((gamer, index) => {
let row = table.insertRow(); let row = table.insertRow();
let cell1 = row.insertCell(); let cell1 = row.insertCell();
@ -64,17 +74,30 @@ export class Scoreboard {
} }
/**
* Update the current gamer who is playing.
* @param {Gamer} gamer
*/
updateCurrentGamer(gamer) { updateCurrentGamer(gamer) {
this.currentGamer = gamer; this.currentGamer = gamer;
} }
/**
* Get the current gamer who is playing.
* @returns {Gamer} The current gamer.
*/
getCurrentGamer() { getCurrentGamer() {
return this.currentGamer; return this.currentGamer;
} }
/**
* Update the score of the current gamer in the scoreboard.
* @param {Number} score
*/
updateScoreCurrentGamer(score) { updateScoreCurrentGamer(score) {
let newScore = this.currentGamer.score + score; let newScore = this.currentGamer.score + score;
this.currentGamer.updateScore(newScore); this.currentGamer.updateScore(newScore);
// Update score of the gamer in the table gamers // Update score of the gamer in the table gamers
this.gamers.forEach((gamer, index) => { this.gamers.forEach((gamer, index) => {
if (gamer.name == this.currentGamer.name) { if (gamer.name == this.currentGamer.name) {
@ -93,23 +116,44 @@ export class Scoreboard {
* @classdesc This class is responsible for the gamer. * @classdesc This class is responsible for the gamer.
*/ */
export class Gamer { export class Gamer {
/**
* @constructor
* @param {String} name
* @param {Number} score
*/
constructor(name, score) { constructor(name, score) {
this.name = name; this.name = name;
this.score = score; this.score = score;
} }
/**
* Set the score of the gamer.
* @param {Number} score
*/
updateScore(score) { updateScore(score) {
this.score = score; this.score = score;
} }
/**
* Set the name of the gamer.
* @param {String} name
*/
updateName(name) { updateName(name) {
this.name = name; this.name = name;
} }
/**
* Get the score of the gamer.
* @returns {Number} The score of the gamer.
*/
getScore() { getScore() {
return this.score; return this.score;
} }
/**
* Get the name of the gamer.
* @returns {String} The name of the gamer.
*/
getName() { getName() {
return this.name; return this.name;
} }

View file

@ -1,4 +1,14 @@
/**
* @fileoverview Timer class
*/
/**
* @class Timer
* @classdesc This class is responsible for the timer.
*/
export class Timer { export class Timer {
// Set time to solve the level by using the difficulty slider
readDifficulty(slider) { readDifficulty(slider) {
self.difficulty = 1.5 - slider.value * 0.01; self.difficulty = 1.5 - slider.value * 0.01;
self.time = self.originalTime * self.difficulty; self.time = self.originalTime * self.difficulty;
@ -8,13 +18,17 @@ export class Timer {
self.originalTime = time; self.originalTime = time;
self.difficulty = 1.0; self.difficulty = 1.0;
self.time = time; self.time = time;
let difficultySlider = document.getElementById('difficulty-slider'); let difficultySlider = document.getElementById('difficulty-slider');
this.readDifficulty(difficultySlider); this.readDifficulty(difficultySlider);
difficultySlider.addEventListener("change", () => { difficultySlider.addEventListener("change", () => {
this.readDifficulty(difficultySlider); this.readDifficulty(difficultySlider);
}) })
self.expireFunction = expireFunction; self.expireFunction = expireFunction;
self.timerElement = document.getElementById('timer'); self.timerElement = document.getElementById('timer');
// Feature : Move the pause button when the mouse is over it
self.pauseButton1 = document.getElementById('pause-1'); self.pauseButton1 = document.getElementById('pause-1');
self.pauseButton2 = document.getElementById('pause-2'); self.pauseButton2 = document.getElementById('pause-2');
self.pauseButton2.style.visibility = "hidden"; self.pauseButton2.style.visibility = "hidden";
@ -40,6 +54,8 @@ export class Timer {
self.pauseButton1.addEventListener("click", () => { self.pauseButton1.addEventListener("click", () => {
alert("All this effort for nothing"); alert("All this effort for nothing");
}); });
self.timeRunning = false; self.timeRunning = false;
self.intervalController = setInterval(() => { self.intervalController = setInterval(() => {
// self.timerElement.innerHTML = "Time : " + String(self.time).padStart(5, ' ').InsertAt('.',3); // self.timerElement.innerHTML = "Time : " + String(self.time).padStart(5, ' ').InsertAt('.',3);
@ -56,18 +72,31 @@ export class Timer {
}, 10); }, 10);
} }
/**
* Set the time of the timer.
* @param {Number} time
*/
setTime(time) { setTime(time) {
self.time = time; self.time = time;
} }
/**
* @returns {Number} The time of the timer.
*/
getTime() { getTime() {
return self.time; return self.time;
} }
/**
* Start the timer.
*/
start() { start() {
self.timeRunning = true; self.timeRunning = true;
} }
/**
* Stop the timer.
*/
stop() { stop() {
self.timeRunning = false; self.timeRunning = false;
} }

View file

@ -6,6 +6,8 @@ import { Timer } from '/modules/timer.mjs'
import { TutorialControler } from '/modules/tutorialControler.mjs' import { TutorialControler } from '/modules/tutorialControler.mjs'
import { Scoreboard, Gamer } from '/modules/scoreboard.mjs' import { Scoreboard, Gamer } from '/modules/scoreboard.mjs'
// Get the canvas and the context
let canvas = document.getElementById('canvas'); let canvas = document.getElementById('canvas');
let difficultySlider = document.getElementById('difficulty-slider'); let difficultySlider = document.getElementById('difficulty-slider');
let mouseOnCanvas = false; let mouseOnCanvas = false;
@ -18,6 +20,9 @@ canvas.addEventListener("mouseleave",() => {
difficultySlider.disabled = false; difficultySlider.disabled = false;
}); });
let ctx = canvas.getContext('2d'); let ctx = canvas.getContext('2d');
// Create the game state
// The game state is a global variable that contains all the information about the game
let gameState = { let gameState = {
playground: generatePlayground(levelsBlueprint[0].structure, canvas.width, canvas.height), playground: generatePlayground(levelsBlueprint[0].structure, canvas.width, canvas.height),
width: canvas.width, width: canvas.width,
@ -35,11 +40,14 @@ let gameState = {
tutorial: new TutorialControler(), tutorial: new TutorialControler(),
}; };
fillLevelsSelection(gameState, ctx); fillLevelsSelection(gameState, ctx);
window.ctx = ctx window.ctx = ctx
window.addEventListener("keydown", (event) => { window.addEventListener("keydown", (event) => {
if (!event.defaultPrevented && mouseOnCanvas) { if (!event.defaultPrevented && mouseOnCanvas) {
if (gameState.playable) { if (gameState.playable) {
// If the game is playable, the keyboard is used to move the player
switch (event.key) { switch (event.key) {
case "ArrowDown": case "ArrowDown":
gameState.playground.move(MoveDirection.Down); gameState.playground.move(MoveDirection.Down);
@ -58,10 +66,13 @@ window.addEventListener("keydown", (event) => {
break; break;
} }
gameState.playground.draw(ctx, canvas.width, canvas.height); gameState.playground.draw(ctx, canvas.width, canvas.height);
// If the game is solved, the next level is loaded
if (gameState.playground.isSolved()) { if (gameState.playground.isSolved()) {
gameState.levelManager.next(ctx, gameState); gameState.levelManager.next(ctx, gameState);
} }
} else { } else {
// If the game is not playable, the tutorial is displayed
switch (event.key) { switch (event.key) {
case "ArrowRight": case "ArrowRight":
case " ": case " ":
@ -79,22 +90,20 @@ window.addEventListener("keydown", (event) => {
} }
}); });
//let table = document.getElementById("scoreTable"); // Add event listener to the input field to update the name
// let joueur = new Gamer("user", 0);
// joueur.Scoreboard();
let inputName = document.getElementById("name"); let inputName = document.getElementById("name");
inputName.addEventListener("input", (event) => { inputName.addEventListener("input", (event) => {
console.log(event.target.value);
inputName.setAttribute('value', event.target.value); inputName.setAttribute('value', event.target.value);
}); });
// Add event listener to the submit button to update the name
let submitButton = document.getElementById("submit-name"); let submitButton = document.getElementById("submit-name");
submitButton.addEventListener("click", () => { submitButton.addEventListener("click", () => {
let value = inputName.value; let value = inputName.value;
gamestate.scoreboard.updatedName(value); gamestate.scoreboard.updatedName(value);
}); });
// Draw the playground
window.gamestate = gameState; window.gamestate = gameState;
gameState.playground.draw(ctx); gameState.playground.draw(ctx);