add high score feature and update dependencies

This commit is contained in:
flo 2025-04-29 14:38:06 +02:00
parent d8dcb08a46
commit 2d34f975bc
11 changed files with 2018 additions and 37 deletions

BIN
datas/database.db Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

412
index.js
View File

@ -1,4 +1,36 @@
import express from 'express';
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import fs from 'fs';
// Déclaration de la variable db pour qu'elle soit accessible dans tout le script
let db;
// Fonction pour initialiser la base de données
async function initializeDatabase() {
// open the database - SQLite créera automatiquement le fichier s'il n'existe pas
db = await open({
filename: './datas/database.db',
driver: sqlite3.Database
});
// Créer les tables nécessaires si elles n'existent pas
await db.exec(`
CREATE TABLE IF NOT EXISTS scores (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_name TEXT,
score INTEGER,
pathHistory TEXT,
date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`);
console.log('Database initialized');
}
initializeDatabase().catch(err => {
console.error('Error initializing database:', err);
});
const app = express();
const port = 3000;
@ -6,6 +38,386 @@ const port = 3000;
app.use(express.static('public'));
app.use(express.json());
app.get('/', (req, res) => {
res.sendFile('index.html', { root: '.' });
});
const templatePiece = [
[ // ok
[3, 0],
[4, 0],
[5, 0],
[6, 0],
[4.5, 0.5]
],
[
[3, 1],
[4, 1],
[5, 1],
[5, 0],
[4, 1]
],
[ // ok
[3, 1],
[4, 1],
[5, 1],
[3, 0],
[4, 1]
],
[ // ok
[3, 0],
[4, 0],
[4, 1],
[5, 1],
[4, 1]
],
[
[3, 1],
[4, 1],
[4, 0],
[5, 0],
[4, 1]
],
[ // ok
[3, 0],
[4, 0],
[4, 1],
[5, 0],
[4, 0]
],
[ // ok
[4, 0],
[5, 0],
[4, 1],
[5, 1],
[4.5, 0.5]
]
];
const templateColor = [
[240, 87, 84, 255],
[254, 223, 92, 255],
[27, 148, 118, 255],
[36, 115, 155, 255],
[106, 190, 178, 255],
[164, 199, 218, 255],
[177, 225, 218, 255]
];
const MOVELEFT = 1;
const MOVERIGHT = 2;
const MOVEDOWN = 3;
const ROTATE = 4;
const MOVEDOWNFAST = 5;
const RESPAWN = 6;
const CHANGENEXTPIECE = 7;
function ifPiece(x, y, board) {
if (x < 0 || x >= 10 || y >= 20) {
return true;
}
if (y < 0) {
return false;
}
if (board[x][y][0] == 0 && board[x][y][1] == 0 && board[x][y][2] == 0) {
return false;
}
return true;
}
function canMoveTo(x, y, piece, board) {
for (let i = 0; i < piece.length - 1; i++) {
if (piece[i][0] + x < 0 || piece[i][0] + x >= 10 || piece[i][1] + y >= 20 || ifPiece(piece[i][0] + x, piece[i][1] + y, board)) {
return false;
}
}
return true;
}
function ifRotate(piece, board) {
for (let i = 0; i < piece.length - 1; i++) {
if (piece[i][0] < 0 || piece[i][0] >= 10 || piece[i][1] >= 20 || ifPiece(piece[i][0], piece[i][1], board)) {
return false;
}
}
return true;
}
function moveDown(piece, board) {
if (canMoveTo(0, 1, piece, board)) {
for (let i = 0; i < piece.length; i++) {
piece[i][1]++;
}
}
}
function moveLeft(piece, board) {
if (canMoveTo(-1, 0, piece, board)) {
for (let i = 0; i < piece.length; i++) {
piece[i][0]--;
}
}
}
function moveRight(piece, board) {
if (canMoveTo(1, 0, piece, board)) {
for (let i = 0; i < piece.length; i++) {
piece[i][0]++;
}
}
}
function rotate(piece, board) {
if (piece.length == 0) {
return;
}
let currentPieceCopy = [];
for (let i = 0; i < piece.length; i++) {
currentPieceCopy.push([piece[i][0], piece[i][1]]);
}
const center = currentPieceCopy[currentPieceCopy.length - 1];
for (let i = 0; i < piece.length - 1; i++) {
const x = currentPieceCopy[i][0] - center[0];
const y = currentPieceCopy[i][1] - center[1];
currentPieceCopy[i][0] = center[0] - y;
currentPieceCopy[i][1] = center[1] + x;
}
if (ifRotate(currentPieceCopy, board)) {
piece = currentPieceCopy;
// Don't modify history in verification function
}
else {
for (let x = -1; x <= 1; x++) {
for (let y = 0; y >= -1; y--) {
if (canMoveTo(x, y, currentPieceCopy, board)) {
moveToPiece(x, y, currentPieceCopy);
piece = currentPieceCopy;
x = 2;
y = -2;
}
}
}
}
}
function moveToPiece(x, y, piece) {
for (let i = 0; i < piece.length; i++) {
piece[i][0] += x;
piece[i][1] += y;
}
}
function checkLine(board, scoreObject) {
let lineFilled = 0;
let score = 0;
let lines = 0;
let level = 0;
for (let i = 0; i < 20; i++) {
let check = true;
for (let j = 0; j < 10; j++) {
if (board[j][i][0] == 0 && board[j][i][1] == 0 && board[j][i][2] == 0) {
check = false;
break;
}
}
if (check) {
for (let j = i; j > 0; j--) {
for (let k = 0; k < 10; k++) {
board[k][j] = board[k][j - 1];
}
}
for (let k = 0; k < 10; k++) {
board[k][0] = [0, 0, 0, 255];
}
lineFilled++;
}
}
if (lineFilled == 1) {
score += 40;
}
else if (lineFilled == 2) {
score += 100;
}
else if (lineFilled == 3) {
score += 300;
}
else if (lineFilled == 4) {
score += 1200;
}
lines += lineFilled;
level = Math.floor(lines / 10);
scoreObject.score += score;
scoreObject.lines += lineFilled;
scoreObject.level = Math.floor(scoreObject.lines / 10);
}
function verifieHystoryAndScore(score, history) {
let board = [];
for (let i = 0; i < 10; i++) {
board[i] = [];
for (let j = 0; j < 20; j++) {
board[i][j] = [0, 0, 0, 255];
}
}
let piece = [];
let nextPiece = [];
let nextIndex = 0;
let scoreObject = {
score: 0,
lines: 0,
level: 0
};
for (let i = 0; i < history.length; i++) {
if (history[i][0] === CHANGENEXTPIECE) {
// Only place non-fractional coordinates on the board
for (let j = 0; j < piece.length - 1; j++) {
if (Number.isInteger(piece[j][0]) && Number.isInteger(piece[j][1]) &&
piece[j][0] >= 0 && piece[j][0] < 10 &&
piece[j][1] >= 0 && piece[j][1] < 20) {
board[piece[j][0]][piece[j][1]] = [templateColor[nextIndex][0], templateColor[nextIndex][1], templateColor[nextIndex][2], templateColor[nextIndex][3]];
}
}
checkLine(board, scoreObject);
nextIndex = history[i][1];
// Reset piece and copy from template
piece = [];
for (let j = 0; j < templatePiece[nextIndex].length; j++) {
piece.push([templatePiece[nextIndex][j][0], templatePiece[nextIndex][j][1]]);
}
// Set up next piece
nextPiece = [];
let nextNextIndex = (i+1 < history.length && history[i+1][0] === CHANGENEXTPIECE) ? history[i+1][1] : 0;
for (let j = 0; j < templatePiece[nextNextIndex].length; j++) {
nextPiece.push([templatePiece[nextNextIndex][j][0], templatePiece[nextNextIndex][j][1]]);
}
}
else if (history[i][0] === MOVEDOWN) {
moveDown(piece, board);
}
else if (history[i][0] === MOVELEFT) {
moveLeft(piece, board);
}
else if (history[i][0] === MOVERIGHT) {
moveRight(piece, board);
}
else if (history[i][0] === ROTATE) {
rotate(piece, board);
}
}
console.log("Calculated score:", scoreObject.score, "Submitted score:", score);
return score === scoreObject.score;
}
app.put('/api/sendScore', (req, res) => {
let score = req.body.score;
let history = req.body.history;
let playerName = req.body.playerName;
// if (score === null || history === null || playerName === null) {
// return res.status(400).send('Missing required fields');
// }
// if (score < 0) {
// return res.status(400).send('Score cannot be negative');
// }
// if (history.length === 0) {
// return res.status(400).send('History cannot be empty');
// }
if (!verifieHystoryAndScore(score, history)) {
return res.status(400).send('Invalid history or score');
}
// Vérifier si le dossier des historiques existe, le créer si nécessaire
const historyDir = './datas/histories';
if (!fs.existsSync(historyDir)) {
fs.mkdirSync(historyDir, { recursive: true });
}
// Date du jour
const date = new Date();
const dateString = date.toISOString().split('T')[0]; // Format YYYY-MM-DD
// Vérifier si un fichier avec ce nom existe déjà et ajouter un chiffre si nécessaire
let finalPlayerName = playerName + dateString;
let counter = 0;
let historyFilePath = `${historyDir}/${finalPlayerName}.json`;
while (fs.existsSync(historyFilePath)) {
counter++;
finalPlayerName = `${playerName}${dateString}${counter}`;
historyFilePath = `${historyDir}/${finalPlayerName}.json`;
}
// Écrire l'historique dans le fichier
try {
fs.writeFileSync(historyFilePath, JSON.stringify(history));
console.log(`History written to ${historyFilePath}`);
} catch (err) {
console.error('Error writing history file:', err);
return res.status(500).send('Error writing history file');
}
//
db.run('INSERT INTO scores (player_name, score, pathHistory) VALUES (?, ?, ?)', [playerName, score, historyFilePath])
.then(() => {
console.log(`Score inserted for ${playerName}`);
})
.then(() => {
console.log('History path inserted in database');
res.status(200).json({
success: true,
playerName: playerName,
message: 'Score and history saved'
});
})
.catch(err => {
console.error('Database error:', err);
res.status(500).send('Error inserting data');
});
});
app.get('/api/getHighScores', async (req, res) => {
try {
const scores = await db.all('SELECT player_name, score FROM scores ORDER BY score DESC LIMIT 10');
res.json(scores);
} catch (err) {
console.error('Database error:', err);
res.status(500).send('Error retrieving high scores');
}
}
);
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});

1457
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,9 @@
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.21.2"
"express": "^4.21.2",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7"
},
"devDependencies": {
"nodemon": "^3.1.9"

View File

@ -10,6 +10,22 @@
</head>
<body>
<template id="high-score-template">
<div class="high-score">
<span class="high-score-name"></span>
<span class="high-score-score"></span>
</div>
</template>
<div class="frame scoreboard">
<p>High Scores</p>
<div id="high-scores-container">
<div id="high-scores-list" class="high-scores-list">
</div>
</div>
</div>
<div class="container">
<div id="left-part" class="first">
<p class="frame">
@ -54,6 +70,9 @@
</div>
</div>
<div class="options">
</div>
</body>
</html>

View File

@ -19,6 +19,10 @@ const previewContainer = document.getElementById('preview-container');
const buttonContainer = document.getElementById('pause-container');
const highScoreTemplate = document.getElementById('high-score-template');
const highScoresList = document.getElementById('high-scores-list');
restartButton.addEventListener('click', () =>
{
displayGameBoard();
@ -174,6 +178,7 @@ const MOVEDOWN = 3;
const ROTATE = 4;
const MOVEDOWNFAST = 5;
const RESPAWN = 6;
const CHANGENEXTPIECE = 7;
let history = [];
@ -185,6 +190,7 @@ function startGame() {
currentPiece = [];
color = [];
history = [];
keyPress = [];
for (let i = 0; i < 10; i++) {
boardData[i] = [];
@ -246,7 +252,7 @@ function loadTetris() {
checkLine();
spawnPiece();
checkEndDrop();
//checkEndDrop();
}
function currentPieceToBoard() {
@ -259,42 +265,100 @@ function currentPieceToBoard() {
}
function spawnPiece() {
const random = Math.floor(Math.random() * templatePiece.length);
let random = 0;
if (nextPiece.length == 0) {
random = Math.floor(Math.random() * templatePiece.length);
for (let i = 0; i < templatePiece[random].length; i++) {
nextPiece.push([templatePiece[random][i][0], templatePiece[random][i][1]]);
}
nextColor = templateColor[random];
history.push([CHANGENEXTPIECE, random]);
}
currentPiece = nextPiece;
if (gameOver) {
return;
}
for (let i = 0; i < currentPiece.length - 1; i++) {
if (boardData[currentPiece[i][0]][currentPiece[i][1]][0] != 0 &&
boardData[currentPiece[i][0]][currentPiece[i][1]][1] != 0 &&
boardData[currentPiece[i][0]][currentPiece[i][1]][2] != 0) {
gameOver = true;
clearDownInterval();
clearInterval(endDownInterval);
oppacity = 255;
displayGameOver();
return;
gameOver = true;
clearDownInterval();
clearInterval(endDownInterval);
oppacity = 255;
displayGameOver();
sendScore();
return;
}
}
random = Math.floor(Math.random() * templatePiece.length);
nextPiece = [];
for (let i = 0; i < templatePiece[random].length; i++) {
nextPiece.push([templatePiece[random][i][0], templatePiece[random][i][1]]);
}
history.push([CHANGENEXTPIECE, random]);
color = nextColor;
nextColor = templateColor[random];
history.push([RESPAWN, random]);
}
async function sendScore() {
const playerName = prompt('Enter your name:');
if (playerName) {
const data = {
score,
history,
playerName
};
console.log(data);
try {
// Send a PUT request to the server
const query = await fetch('/api/sendScore', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
// Check if the response is ok before parsing
if (!query.ok) {
throw new Error(`Server returned ${query.status}: ${query.statusText}`);
}
// Check the content type to ensure it's JSON
const contentType = query.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new Error('Server did not return JSON');
}
// Get the response body
const response = await query.json();
if (response.success) {
console.log('Score and history saved successfully');
}
else {
console.error('Error saving score and history:', response.error);
}
} catch (error) {
console.error('Failed to save score:', error.message);
alert('Could not save your score. Please try again later.');
}
}
}
function ifMoveTo(x, y) {
return canMoveTo(x, y, currentPiece);
@ -518,7 +582,7 @@ function moveRight() {
return true;
}
function fasteDrop() {
function fastDrop() {
while (ifMoveTo(0, 1)) {
moveDown();
}
@ -528,7 +592,6 @@ function fasteDrop() {
oppacity = 255;
loadTetris();
history.push([MOVEDOWNFAST, 0]);
}
document.addEventListener('keydown', (event) => {
@ -568,7 +631,7 @@ document.addEventListener('keydown', (event) => {
refresh();
}, 100);
} else if (event.key == ' ' && !keyPress.includes(' ')) {
fasteDrop();
fastDrop();
}
refresh();
@ -579,18 +642,14 @@ document.addEventListener('keydown', (event) => {
});
document.addEventListener('keyup', (event) => {
if (pause) {
return;
}
if (gameOver) {
return;
}
if (event.key == 'ArrowDown' || event.key == 's') {
initAndChangeSpeedDrop();
} else if (event.key == 'ArrowLeft' || event.key == 'a' || event.key == 'q') {
clearInterval(leftInterval);
} else if (event.key == 'ArrowRight' || event.key == 'd') {
clearInterval(rightInterval);
if (!pause && !gameOver) {
if (event.key == 'ArrowDown' || event.key == 's') {
initAndChangeSpeedDrop();
} else if (event.key == 'ArrowLeft' || event.key == 'a' || event.key == 'q') {
clearInterval(leftInterval);
} else if (event.key == 'ArrowRight' || event.key == 'd') {
clearInterval(rightInterval);
}
}
keyPress = keyPress.filter((key) => {

View File

@ -187,4 +187,32 @@ function displayGameOver() {
buttonContainer.classList.add('hidden');
gameOverMenu.classList.remove('hidden');
}
let intervalDisplayScoreBoard = null;
displayScoreBoard();
function displayScoreBoard() {
if (intervalDisplayScoreBoard) {
clearInterval(intervalDisplayScoreBoard);
intervalDisplayScoreBoard = null;
}
intervalDisplayScoreBoard = setInterval(() => {
fetch('/api/getHighScores')
.then(response => response.json())
.then(data => {
highScoresList.innerHTML = '';
data.forEach(score => {
// Clone template content properly
const scoreElement = document.importNode(highScoreTemplate.content, true).firstElementChild;
scoreElement.querySelector('.high-score-name').innerHTML = score.player_name;
scoreElement.querySelector('.high-score-score').innerHTML = score.score;
highScoresList.appendChild(scoreElement);
});
})
.catch(error => console.error('Error fetching high scores:', error));
}, 1000);
}

View File

@ -16,6 +16,8 @@ body {
justify-content: center;
flex-direction: row;
justify-content: space-evenly;
gap: 1rem;
}
@ -128,9 +130,6 @@ body {
left: 0;
right: 0;
bottom: 0;
}
.pauseContainer {
@ -174,4 +173,19 @@ body {
.hidden {
display: none;
}
.scoreboard {
width: 200px;
height: 600px;
display: flex;
flex-direction: column;
gap: 1rem;
align-items: center;
}
.options {
width: 200px;
height: 600px;
}