ajout d'historique
This commit is contained in:
@@ -56,6 +56,17 @@ async function runMigrations()
|
|||||||
END IF;
|
END IF;
|
||||||
END $$;
|
END $$;
|
||||||
`);
|
`);
|
||||||
|
// Create tetris_game_history table if not exists
|
||||||
|
await pool.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS tetris_game_history (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id INT REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
score INT NOT NULL DEFAULT 0,
|
||||||
|
game_type VARCHAR(10) NOT NULL DEFAULT 'solo',
|
||||||
|
result VARCHAR(10) DEFAULT NULL,
|
||||||
|
played_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
`);
|
||||||
console.log('Migrations completed!');
|
console.log('Migrations completed!');
|
||||||
}
|
}
|
||||||
catch (err)
|
catch (err)
|
||||||
@@ -147,6 +158,15 @@ async function createTables()
|
|||||||
started_at TIMESTAMP DEFAULT NOW(),
|
started_at TIMESTAMP DEFAULT NOW(),
|
||||||
ended_at TIMESTAMP
|
ended_at TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tetris_game_history (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id INT REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
score INT NOT NULL DEFAULT 0,
|
||||||
|
game_type VARCHAR(10) NOT NULL DEFAULT 'solo',
|
||||||
|
result VARCHAR(10) DEFAULT NULL,
|
||||||
|
played_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
`);
|
`);
|
||||||
console.log('Tables created!');
|
console.log('Tables created!');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ router.get('/leaderboard', authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save tetris score (solo or duel) — updates best score if higher
|
// Save tetris score (solo) — updates best score if higher + saves to history
|
||||||
router.post('/tetris/score', authenticateToken, async (req, res) => {
|
router.post('/tetris/score', authenticateToken, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { score } = req.body;
|
const { score } = req.body;
|
||||||
@@ -52,6 +52,7 @@ router.post('/tetris/score', authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
const bestScore = await playerStatsService.updateTetrisBestScore(req.user.userId, score);
|
const bestScore = await playerStatsService.updateTetrisBestScore(req.user.userId, score);
|
||||||
await playerStatsService.incrementTetrisGamesPlayed(req.user.userId);
|
await playerStatsService.incrementTetrisGamesPlayed(req.user.userId);
|
||||||
|
await playerStatsService.addTetrisGameHistory(req.user.userId, score, 'solo', null);
|
||||||
res.json({ bestScore });
|
res.json({ bestScore });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error saving tetris score:', err);
|
console.error('Error saving tetris score:', err);
|
||||||
@@ -94,6 +95,17 @@ router.get('/tetris/rank/score', authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get current user's tetris game history (last 15)
|
||||||
|
router.get('/tetris/history', authenticateToken, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const history = await playerStatsService.getTetrisGameHistory(req.user.userId);
|
||||||
|
res.json(history);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error getting tetris history:', err);
|
||||||
|
res.status(500).json({ error: 'Server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Current user's rank by tetris duel wins
|
// Current user's rank by tetris duel wins
|
||||||
router.get('/tetris/rank/wins', authenticateToken, async (req, res) => {
|
router.get('/tetris/rank/wins', authenticateToken, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -129,6 +129,38 @@ async function getTetrisDuelWinsLeaderboard(limit = 10) {
|
|||||||
return result.rows;
|
return result.rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a game to tetris history (keep max 15 per user)
|
||||||
|
async function addTetrisGameHistory(userId, score, gameType = 'solo', result = null) {
|
||||||
|
await query(
|
||||||
|
`INSERT INTO tetris_game_history (user_id, score, game_type, result) VALUES ($1, $2, $3, $4)`,
|
||||||
|
[userId, score, gameType, result]
|
||||||
|
);
|
||||||
|
// Keep only the 15 most recent entries
|
||||||
|
await query(
|
||||||
|
`DELETE FROM tetris_game_history
|
||||||
|
WHERE id IN (
|
||||||
|
SELECT id FROM tetris_game_history
|
||||||
|
WHERE user_id = $1
|
||||||
|
ORDER BY played_at DESC
|
||||||
|
OFFSET 15
|
||||||
|
)`,
|
||||||
|
[userId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the last 15 games for a user
|
||||||
|
async function getTetrisGameHistory(userId) {
|
||||||
|
const result = await query(
|
||||||
|
`SELECT id, score, game_type, result, played_at
|
||||||
|
FROM tetris_game_history
|
||||||
|
WHERE user_id = $1
|
||||||
|
ORDER BY played_at DESC
|
||||||
|
LIMIT 15`,
|
||||||
|
[userId]
|
||||||
|
);
|
||||||
|
return result.rows;
|
||||||
|
}
|
||||||
|
|
||||||
// Rank of a user by tetris best score (1 = best)
|
// Rank of a user by tetris best score (1 = best)
|
||||||
async function getTetrisScoreRank(userId) {
|
async function getTetrisScoreRank(userId) {
|
||||||
const result = await query(
|
const result = await query(
|
||||||
@@ -166,5 +198,7 @@ export default {
|
|||||||
getTetrisBestScoreLeaderboard,
|
getTetrisBestScoreLeaderboard,
|
||||||
getTetrisDuelWinsLeaderboard,
|
getTetrisDuelWinsLeaderboard,
|
||||||
getTetrisScoreRank,
|
getTetrisScoreRank,
|
||||||
getTetrisDuelWinsRank
|
getTetrisDuelWinsRank,
|
||||||
|
addTetrisGameHistory,
|
||||||
|
getTetrisGameHistory
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -721,6 +721,7 @@ function setupSocketIO(io)
|
|||||||
|
|
||||||
// Relay pur : grid-update → adversaire uniquement
|
// Relay pur : grid-update → adversaire uniquement
|
||||||
socket.on('tetris:grid-update', (data) => {
|
socket.on('tetris:grid-update', (data) => {
|
||||||
|
if (data.score !== undefined) socket.tetrisLastScore = data.score;
|
||||||
_tetrisRelayToOpponent(socket, 'tetris:grid-update', data);
|
_tetrisRelayToOpponent(socket, 'tetris:grid-update', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -779,6 +780,7 @@ function setupSocketIO(io)
|
|||||||
try {
|
try {
|
||||||
await playerStatsService.updateTetrisBestScore(loserId, data.score || 0);
|
await playerStatsService.updateTetrisBestScore(loserId, data.score || 0);
|
||||||
await playerStatsService.incrementTetrisGamesPlayed(loserId);
|
await playerStatsService.incrementTetrisGamesPlayed(loserId);
|
||||||
|
await playerStatsService.addTetrisGameHistory(loserId, data.score || 0, 'duel', 'loss');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error saving tetris loser stats:', err);
|
console.error('Error saving tetris loser stats:', err);
|
||||||
}
|
}
|
||||||
@@ -793,6 +795,8 @@ function setupSocketIO(io)
|
|||||||
try {
|
try {
|
||||||
await playerStatsService.incrementTetrisWins(s.user.userId);
|
await playerStatsService.incrementTetrisWins(s.user.userId);
|
||||||
await playerStatsService.incrementTetrisGamesPlayed(s.user.userId);
|
await playerStatsService.incrementTetrisGamesPlayed(s.user.userId);
|
||||||
|
const winnerScore = s.tetrisLastScore || 0;
|
||||||
|
await playerStatsService.addTetrisGameHistory(s.user.userId, winnerScore, 'duel', 'win');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error saving tetris winner stats:', err);
|
console.error('Error saving tetris winner stats:', err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -524,4 +524,13 @@ button:disabled { opacity: 0.3; cursor: not-allowed; }
|
|||||||
width: 30px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hist-win {
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hist-loss {
|
||||||
|
color: var(--accent2);
|
||||||
|
}
|
||||||
|
|
||||||
body { overflow-y: auto; }
|
body { overflow-y: auto; }
|
||||||
|
|||||||
@@ -127,6 +127,7 @@
|
|||||||
<div class="leaderboard-tabs">
|
<div class="leaderboard-tabs">
|
||||||
<button class="lb-tab lb-tab--active" data-tab="scores">Meilleurs scores</button>
|
<button class="lb-tab lb-tab--active" data-tab="scores">Meilleurs scores</button>
|
||||||
<button class="lb-tab" data-tab="wins">Duels gagnés</button>
|
<button class="lb-tab" data-tab="wins">Duels gagnés</button>
|
||||||
|
<button class="lb-tab" data-tab="history">Mes parties</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="lb-scores" class="lb-content lb-content--active">
|
<div id="lb-scores" class="lb-content lb-content--active">
|
||||||
@@ -150,6 +151,17 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="lb-history" class="lb-content">
|
||||||
|
<table class="lb-table">
|
||||||
|
<thead>
|
||||||
|
<tr><th>#</th><th>Date</th><th>Type</th><th>Score</th><th>Résultat</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="lb-history-body">
|
||||||
|
<tr><td colspan="5">Chargement…</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ const game = new Tetris(
|
|||||||
updateButtons();
|
updateButtons();
|
||||||
showOverlay('GAME OVER', score);
|
showOverlay('GAME OVER', score);
|
||||||
loadLeaderboards();
|
loadLeaderboards();
|
||||||
|
loadGameHistory();
|
||||||
},
|
},
|
||||||
// onBlockPlaced — relay duel
|
// onBlockPlaced — relay duel
|
||||||
(grid) => {
|
(grid) => {
|
||||||
@@ -267,6 +268,53 @@ if (btnRestart) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// GAME HISTORY
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function loadGameHistory() {
|
||||||
|
const token = localStorage.getItem('auth_token');
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/stats/tetris/history', {
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
if (!res.ok) return;
|
||||||
|
const history = await res.json();
|
||||||
|
renderGameHistory(history);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Erreur chargement historique:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGameHistory(history) {
|
||||||
|
const tbody = document.getElementById('lb-history-body');
|
||||||
|
if (!tbody) return;
|
||||||
|
if (!history.length) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="5">Aucune partie jouée</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody.innerHTML = history.map((entry, i) => {
|
||||||
|
const date = new Date(entry.played_at).toLocaleDateString('fr-FR', {
|
||||||
|
day: '2-digit', month: '2-digit', year: '2-digit',
|
||||||
|
hour: '2-digit', minute: '2-digit'
|
||||||
|
});
|
||||||
|
const type = entry.game_type === 'duel' ? 'Duel' : 'Solo';
|
||||||
|
let resultHtml = '—';
|
||||||
|
if (entry.result === 'win') resultHtml = '<span class="hist-win">Victoire</span>';
|
||||||
|
if (entry.result === 'loss') resultHtml = '<span class="hist-loss">Défaite</span>';
|
||||||
|
return `<tr>
|
||||||
|
<td>${i + 1}</td>
|
||||||
|
<td>${date}</td>
|
||||||
|
<td>${type}</td>
|
||||||
|
<td>${entry.score}</td>
|
||||||
|
<td>${resultHtml}</td>
|
||||||
|
</tr>`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
// LEADERBOARDS
|
// LEADERBOARDS
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
@@ -349,8 +397,10 @@ document.querySelectorAll('.lb-tab').forEach(tab => {
|
|||||||
document.querySelectorAll('.lb-content').forEach(c => c.classList.remove('lb-content--active'));
|
document.querySelectorAll('.lb-content').forEach(c => c.classList.remove('lb-content--active'));
|
||||||
tab.classList.add('lb-tab--active');
|
tab.classList.add('lb-tab--active');
|
||||||
document.getElementById(`lb-${tab.dataset.tab}`).classList.add('lb-content--active');
|
document.getElementById(`lb-${tab.dataset.tab}`).classList.add('lb-content--active');
|
||||||
|
if (tab.dataset.tab === 'history') loadGameHistory();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Chargement initial des leaderboards
|
// Chargement initial des leaderboards
|
||||||
loadLeaderboards();
|
loadLeaderboards();
|
||||||
|
loadGameHistory();
|
||||||
|
|||||||
Reference in New Issue
Block a user