mirror of
https://github.com/cassoule/flopobot_v2.git
synced 2026-01-18 16:37:40 +01:00
solitaire moves hist and undo and hard mode
This commit is contained in:
@@ -212,10 +212,25 @@ export function moveCard(gameState, moveData) {
|
||||
// Add the stack to the destination pile.
|
||||
destPile.push(...cardsToMove);
|
||||
|
||||
const histMove = {
|
||||
move: 'move',
|
||||
sourcePileType: sourcePileType,
|
||||
sourcePileIndex: sourcePileIndex,
|
||||
sourceCardIndex: sourceCardIndex,
|
||||
destPileType: destPileType,
|
||||
destPileIndex: destPileIndex,
|
||||
cardsMoved: cardsToMove,
|
||||
cardWasFlipped: false,
|
||||
points: destPileType === 'foundationPiles' ? 11 : 1 // Points for moving to foundation
|
||||
}
|
||||
|
||||
// If the source was a tableau pile and there are cards left, flip the new top card.
|
||||
if (sourcePileType === 'tableauPiles' && sourcePile.length > 0) {
|
||||
sourcePile[sourcePile.length - 1].faceUp = true;
|
||||
histMove.cardWasFlipped = true;
|
||||
}
|
||||
|
||||
gameState.hist.push(histMove)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,14 +242,50 @@ export function drawCard(gameState) {
|
||||
const card = gameState.stockPile.pop();
|
||||
card.faceUp = true;
|
||||
gameState.wastePile.push(card);
|
||||
gameState.hist.push({
|
||||
move: 'draw',
|
||||
card: card
|
||||
})
|
||||
} else if (gameState.wastePile.length > 0) {
|
||||
// When stock is empty, move the entire waste pile back to stock, face down.
|
||||
gameState.stockPile = gameState.wastePile.reverse();
|
||||
gameState.stockPile.forEach(card => (card.faceUp = false));
|
||||
gameState.wastePile = [];
|
||||
gameState.hist.push({
|
||||
move: 'draw-reset',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function draw3Cards(gameState) {
|
||||
if (gameState.stockPile.length > 0) {
|
||||
let cards = []
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (gameState.stockPile.length > 0) {
|
||||
const card = gameState.stockPile.pop();
|
||||
card.faceUp = true;
|
||||
gameState.wastePile.push(card);
|
||||
cards.push(card);
|
||||
} else {
|
||||
break; // Stop if stock runs out
|
||||
}
|
||||
}
|
||||
gameState.hist.push({
|
||||
move: 'draw-3',
|
||||
cards: cards,
|
||||
})
|
||||
} else if (gameState.wastePile.length > 0) {
|
||||
// When stock is empty, move the entire waste pile back to stock, face down.
|
||||
gameState.stockPile = gameState.wastePile.reverse();
|
||||
gameState.stockPile.forEach(card => (card.faceUp = false));
|
||||
gameState.wastePile = [];
|
||||
gameState.hist.push({
|
||||
move: 'draw-reset',
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the game has been won (all 52 cards are in the foundation piles).
|
||||
* @param {Object} gameState - The current state of the game.
|
||||
@@ -243,4 +294,106 @@ export function drawCard(gameState) {
|
||||
export function checkWinCondition(gameState) {
|
||||
const foundationCardCount = gameState.foundationPiles.reduce((acc, pile) => acc + pile.length, 0);
|
||||
return foundationCardCount === 52;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the game state to its previous state based on the last move in the history.
|
||||
* This function mutates the gameState object directly.
|
||||
* @param {Object} gameState - The current game state, which includes a `hist` array.
|
||||
*/
|
||||
export function undoMove(gameState) {
|
||||
if (!gameState.hist || gameState.hist.length === 0) {
|
||||
console.log("No moves to undo.");
|
||||
return; // Nothing to undo
|
||||
}
|
||||
|
||||
const lastMove = gameState.hist.pop(); // Get and remove the last move from history
|
||||
gameState.moves++; // Undoing a move counts as a new move
|
||||
gameState.score -= lastMove.points || 1; // Revert score based on points from the last move
|
||||
|
||||
switch (lastMove.move) {
|
||||
case 'move':
|
||||
undoCardMove(gameState, lastMove);
|
||||
break;
|
||||
case 'draw':
|
||||
undoDraw(gameState, lastMove);
|
||||
break;
|
||||
case 'draw-3':
|
||||
undoDraw3(gameState, lastMove);
|
||||
break;
|
||||
case 'draw-reset':
|
||||
undoDrawReset(gameState, lastMove);
|
||||
break;
|
||||
default:
|
||||
// If an unknown move type is found, push it back to avoid corrupting the history
|
||||
gameState.hist.push(lastMove);
|
||||
gameState.moves--; // Revert the move count increment
|
||||
gameState.score += lastMove.points || 1; // Revert the score decrement
|
||||
console.error("Unknown move type in history:", lastMove);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helper functions for undoing specific moves ---
|
||||
|
||||
function undoCardMove(gameState, moveData) {
|
||||
const { sourcePileType, sourcePileIndex, sourceCardIndex, destPileType, destPileIndex, cardsMoved, cardWasFlipped } = moveData;
|
||||
|
||||
// 1. Find the destination pile (where the cards are NOW)
|
||||
let currentPile;
|
||||
if (destPileType === 'tableauPiles') currentPile = gameState.tableauPiles[destPileIndex];
|
||||
else if (destPileType === 'foundationPiles') currentPile = gameState.foundationPiles[destPileIndex];
|
||||
|
||||
// 2. Remove the moved cards from their current pile
|
||||
// Using splice with a negative index removes from the end of the array
|
||||
currentPile.splice(-cardsMoved.length);
|
||||
|
||||
// 3. Find the original source pile
|
||||
let originalPile;
|
||||
if (sourcePileType === 'tableauPiles') originalPile = gameState.tableauPiles[sourcePileIndex];
|
||||
else if (sourcePileType === 'wastePile') originalPile = gameState.wastePile;
|
||||
else if (sourcePileType === 'foundationPiles') originalPile = gameState.foundationPiles[sourcePileIndex];
|
||||
|
||||
// 4. Put the cards back where they came from
|
||||
// Using splice to insert the cards back at their original index
|
||||
originalPile.splice(sourceCardIndex, 0, ...cardsMoved);
|
||||
|
||||
// 5. If a card was flipped during the move, flip it back to face-down
|
||||
if (cardWasFlipped) {
|
||||
const cardToUnflip = originalPile[sourceCardIndex - 1];
|
||||
if (cardToUnflip) {
|
||||
cardToUnflip.faceUp = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function undoDraw(gameState, moveData) {
|
||||
// A 'draw' move means a card went from stock to waste.
|
||||
// To undo, move it from waste back to stock and flip it face-down.
|
||||
const cardToReturn = gameState.wastePile.pop();
|
||||
if (cardToReturn) {
|
||||
cardToReturn.faceUp = false;
|
||||
gameState.stockPile.push(cardToReturn);
|
||||
}
|
||||
}
|
||||
|
||||
function undoDraw3(gameState, moveData) {
|
||||
// A 'draw-3' move means up to 3 cards went from stock to
|
||||
// waste. To undo, move them back to stock and flip them face-down.
|
||||
const cardsToReturn = moveData.cards || [];
|
||||
for (let i = 0; i < cardsToReturn.length; i++) {
|
||||
const card = gameState.wastePile.pop();
|
||||
if (card) {
|
||||
card.faceUp = false;
|
||||
gameState.stockPile.push(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function undoDrawReset(gameState, moveData) {
|
||||
// A 'draw-reset' means the waste pile was moved to the stock pile.
|
||||
// To undo, move the stock pile back to the waste pile and flip cards face-up.
|
||||
gameState.wastePile = gameState.stockPile.reverse();
|
||||
gameState.wastePile.forEach(card => (card.faceUp = true));
|
||||
gameState.stockPile = [];
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import express from 'express';
|
||||
// --- Game Logic Imports ---
|
||||
import {
|
||||
createDeck, shuffle, deal, isValidMove, moveCard, drawCard,
|
||||
checkWinCondition, createSeededRNG, seededShuffle
|
||||
checkWinCondition, createSeededRNG, seededShuffle, undoMove, draw3Cards
|
||||
} from '../../game/solitaire.js';
|
||||
|
||||
// --- Game State & Database Imports ---
|
||||
@@ -28,7 +28,7 @@ export function solitaireRoutes(client, io) {
|
||||
// --- Game Initialization Endpoints ---
|
||||
|
||||
router.post('/start', (req, res) => {
|
||||
const { userId, userSeed } = req.body;
|
||||
const { userId, userSeed, hardMode } = req.body;
|
||||
if (!userId) return res.status(400).json({ error: 'User ID is required.' });
|
||||
|
||||
// If a game already exists for the user, return it instead of creating a new one.
|
||||
@@ -56,6 +56,10 @@ export function solitaireRoutes(client, io) {
|
||||
const gameState = deal(deck);
|
||||
gameState.seed = seed;
|
||||
gameState.isSOTD = false;
|
||||
gameState.score = 0;
|
||||
gameState.moves = 0;
|
||||
gameState.hist = [];
|
||||
gameState.hardMode = hardMode ?? false;
|
||||
activeSolitaireGames[userId] = gameState;
|
||||
|
||||
res.json({ success: true, gameState });
|
||||
@@ -88,6 +92,8 @@ export function solitaireRoutes(client, io) {
|
||||
moves: 0,
|
||||
score: 0,
|
||||
seed: sotd.seed,
|
||||
hist: [],
|
||||
hardMode: false,
|
||||
};
|
||||
|
||||
activeSolitaireGames[userId] = gameState;
|
||||
@@ -152,11 +158,27 @@ export function solitaireRoutes(client, io) {
|
||||
if (!gameState) return res.status(404).json({ error: 'Game not found.' });
|
||||
if (gameState.isDone) return res.status(400).json({ error: 'This game is already completed.'});
|
||||
|
||||
drawCard(gameState);
|
||||
if (gameState.hardMode) {
|
||||
draw3Cards(gameState);
|
||||
} else {
|
||||
drawCard(gameState);
|
||||
}
|
||||
updateGameStats(gameState, 'draw');
|
||||
res.json({ success: true, gameState });
|
||||
});
|
||||
|
||||
router.post('/undo', (req, res) => {
|
||||
const { userId } = req.body;
|
||||
const gameState = activeSolitaireGames[userId];
|
||||
|
||||
if (!gameState) return res.status(404).json({ error: 'Game not found.' });
|
||||
if (gameState.isDone) return res.status(400).json({ error: 'This game is already completed.'});
|
||||
if (gameState.hist.length === 0) return res.status(400).json({ error: 'No moves to undo.'});
|
||||
|
||||
undoMove(gameState);
|
||||
res.json({ success: true, gameState });
|
||||
})
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -165,7 +187,7 @@ export function solitaireRoutes(client, io) {
|
||||
|
||||
/** Updates game stats like moves and score after an action. */
|
||||
function updateGameStats(gameState, actionType, moveData = {}) {
|
||||
if (!gameState.isSOTD) return; // Only track stats for SOTD
|
||||
// if (!gameState.isSOTD) return; // Only track stats for SOTD
|
||||
|
||||
gameState.moves++;
|
||||
if (actionType === 'move') {
|
||||
|
||||
Reference in New Issue
Block a user