benjstorlie / sudoku-shuffle

Play sudoku
https://mighty-ridge-19238-3e456d2c37f3.herokuapp.com/
MIT License
0 stars 0 forks source link

Manage Local Storage for Moves #9

Open benjstorlie opened 1 year ago

benjstorlie commented 1 year ago

Implement functions to save and retrieve game moves using IndexedDB.

Save each move as one entry, with each game getting its own store, so it can remember the history for several games.

Save each move auto-incrementing the id, to be able to retrieve the most recent.

benjstorlie commented 1 year ago

Some starter code. First, add const [move, setMove] = useState(0). Then, each time the user moves, setMove((move) => move+1). This gives a way to reference where in the game history you are. 'move' will also need to be added to the server. Or have gameData = JSON.stringify({move,gameArray}), and make sure to parse retrieved gameData accordingly. Actually that's a better idea, since then there's the option of adding whatever to the gameData.

Make sure to install idb

A function to add to client/src/utils/GameContext.js. This would then be called right along saveGameState. When using it after saveNewGame, make sure to give it the new gameId.

import { openDB } from 'idb';

async function saveGameLocal(gameId, move, gameData) {
  const dbName = 'sudoku-shuffle-db';
  const dbVersion = 1; // You can update this when needed

  // Open the database
  const db = await openDB(dbName, dbVersion, {
    upgrade(db) {
      // Create a new store if it doesn't exist
      if (!db.objectStoreNames.contains(gameId)) {
        db.createObjectStore(gameId);
      }
    },
  });

  // Start a transaction and get the object store
  const tx = db.transaction(gameId, 'readwrite');
  const store = tx.objectStore(gameId);

  // Save the gameData with the specified move number
  await store.put(gameData, move);

  // Delete entries with move numbers greater than the specified move
  const keys = await store.getAllKeys();
  for (const key of keys) {
    if (key > move) {
      await store.delete(key);
    }
  }

  // Complete the transaction
  await tx.complete;

  // Close the database
  db.close();
}

// Example usage
const gameId = 'exampleGame';
const move = 5;
const gameArray = { /* ... */ }
saveGameLocal(gameId, move, JSON.stringify({gameArray}));
benjstorlie commented 1 year ago

Then, here's a component that holds the undo and redo buttons, plus the logic to figure out whether they should be disabled.

(I called it 'Undo', but it holds both buttons. Is there a word that refers to both?)

There might need to be some extra logic for whether undo is disabled or not, because maybe the user resumed a game from the database, but the game history is not in their local storage.

import React, { useState } from 'react';
import { useGameContext } from '../../utils/GameContext'

export default function Undo() {
  const { gameId, gameData, setGameData, move, setMove } = useGameContext();
  const [previousMove, setPreviousMove] = useState(-1);
  const [redoAttempts, setRedoAttempts] = useState(0);

  const canUndo = move > 0;
  const canRedo = redoAttempts > 0;

  const handleUndo = () => {
    if (canUndo) {
      setMove((move) => move - 1);
      setRedoAttempts(redoAttempts + 1); // Increment redo attempts
      setPreviousMove(move - 1); // Set the previous move
      // Implement your undo logic here
    }
  };

  const handleRedo = () => {
    if (canRedo) {
      setMove((move) => move + 1);
      setRedoAttempts(redoAttempts - 1); // Decrement redo attempts
      // Implement your redo logic here
    }
  };

  // Disable redo if move increases without redo being pressed
  if (move > previousMove && redoAttempts > 0) {
    setRedoAttempts(0); // Reset redo attempts
  }

  return (
    <div>
      <button onClick={handleUndo} disabled={!canUndo}>
        Undo
      </button>
      <button onClick={handleRedo} disabled={!canRedo}>
        Redo
      </button>
    </div>
  );
}
benjstorlie commented 1 year ago

Then here would be the corresponding retrieveGameLocal function that undo and redo will use.

import { openDB } from 'idb';

async function retrieveGameLocal(gameId, move) {
  const dbName = 'sudoku-shuffle-db';
  const dbVersion = 1; // You can update this when needed

  // Open the database
  const db = await openDB(dbName, dbVersion);

  // Start a transaction and get the object store
  const tx = db.transaction(gameId, 'readonly');
  const store = tx.objectStore(gameId);

  // Retrieve the gameData for the specified move number
  const gameData = await store.get(move);

  // Complete the transaction
  await tx.complete;

  // Close the database
  db.close();

  return gameData;
}

// Inside your React component

const handleUndo = async () => {
  if (move > 0) {
    setMove((move) => move - 1);
    const updatedGameData = await retrieveGameLocal(gameId, move - 1);
    setGameData(JSON.parse(updatedGameData).gameArray);
    // Implement any additional undo logic
  }
};

And redo would be handled similarly.