From 45ca073efa08426b9bad6b5b48ae5dfdbe6cac11 Mon Sep 17 00:00:00 2001 From: Jeremy Rand Date: Thu, 16 Jul 2015 19:40:27 -0500 Subject: [PATCH] Add the ability to save and load your game. --- a2sudoku/game.c | 87 +++++++++++++++++++++++++++++++++++++++++++++- a2sudoku/game.h | 4 +++ a2sudoku/puzzles.c | 15 ++++++++ a2sudoku/puzzles.h | 5 +++ a2sudoku/ui.c | 70 ++++++++++++++++++++++++++++++++++--- 5 files changed, 175 insertions(+), 6 deletions(-) diff --git a/a2sudoku/game.c b/a2sudoku/game.c index 7ecc7ae..c23ef58 100644 --- a/a2sudoku/game.c +++ b/a2sudoku/game.c @@ -7,6 +7,8 @@ // +#include +#include #include #include "game.h" @@ -15,6 +17,7 @@ // Macros #define SQUARE_XY(x, y) (theGame.squares[((y) * BOARD_SIZE) + (x)]) +#define SAVE_GAME_FILE "a2sudoku.game" // Typedefs @@ -80,12 +83,94 @@ void startGame(tDifficulty difficulty, tUpdatePosCallback callback) SQUARE_XY(x, y).knownAtStart = true; SQUARE_XY(x, y).correct = true; } - refreshPos(x, y); } } } +void saveGame(void) +{ + FILE *saveFile = fopen(SAVE_GAME_FILE, "wb"); + if (saveFile != NULL) { + bool isValid = true; + fwrite(&isValid, sizeof(isValid), 1, saveFile); + fwrite(&theGame, sizeof(theGame), 1, saveFile); + savePuzzle(theGame.puzzle, saveFile); + fclose(saveFile); + } +} + + +void deleteGame(void) +{ + // So, I tried using unlink() from unistd.h but it seems it + // does nothing on an Apple // with cc65. Instead, I will + // just open the file for writing and close it again which + // will leave it empty. That way, there won't be a saved + // game in the file. + FILE *saveFile = fopen(SAVE_GAME_FILE, "wb"); + if (saveFile != NULL) { + bool isValid = false; + fwrite(&isValid, sizeof(isValid), 1, saveFile); + fclose(saveFile); + } +} + + +#undef LOAD_GAME_DEBUG +bool loadGame(tUpdatePosCallback callback) +{ + bool isValid = false; + bool result = false; + FILE *saveFile= fopen(SAVE_GAME_FILE, "rb"); + + if (saveFile == NULL) { +#ifdef LOAD_GAME_DEBUG + printf("Cannot open save game file\n"); + cgetc(); +#endif + return false; + } + + if ((fread(&isValid, sizeof(isValid), 1, saveFile) != 1) || + (!isValid)) { + fclose(saveFile); +#ifdef LOAD_GAME_DEBUG + printf("Save is not valid\n"); + cgetc(); +#endif + return false; + } + + if (fread(&theGame, sizeof(theGame), 1, saveFile) != 1) { + fclose(saveFile); + deleteGame(); +#ifdef LOAD_GAME_DEBUG + printf("Unable to read game from save\n"); + cgetc(); +#endif + return false; + } + + theGame.callback = callback; + + theGame.puzzle = loadPuzzle(saveFile); + + fclose(saveFile); + deleteGame(); + + if (theGame.puzzle == NULL) { +#ifdef LOAD_GAME_DEBUG + printf("Unable to read puzzle from save\n"); + cgetc(); +#endif + return false; + } + + return true; +} + + void refreshAllPos(void) { tPos x, y; diff --git a/a2sudoku/game.h b/a2sudoku/game.h index dd15208..3976400 100644 --- a/a2sudoku/game.h +++ b/a2sudoku/game.h @@ -43,5 +43,9 @@ extern bool toggleScratchValueAtPos(tPos x, tPos y, tSquareVal val); // Returns false if the last move cannot be undone. extern bool undoLastMove(void); +extern void saveGame(void); + +extern bool loadGame(tUpdatePosCallback callback); + #endif /* defined(__a2sudoku__game__) */ diff --git a/a2sudoku/puzzles.c b/a2sudoku/puzzles.c index ea22bd3..f00d22a 100644 --- a/a2sudoku/puzzles.c +++ b/a2sudoku/puzzles.c @@ -128,4 +128,19 @@ tSquareVal getStartValueAtPos(tPuzzle *puzzle, tPos x, tPos y) bool checkValueAtPos(tPuzzle *puzzle, tSquareVal val, tPos x, tPos y) { return (PUZZLE_SOLVED_VAL(PUZZLE_SQUARE(puzzle, x, y)) == val); +} + + +void savePuzzle(tPuzzle *puzzle, FILE *saveFile) +{ + fwrite(puzzle, sizeof(*puzzle), 1, saveFile); +} + + +tPuzzle *loadPuzzle(FILE *saveFile) +{ + if (fread(&thePuzzle, sizeof(thePuzzle), 1, saveFile) != 1) + return NULL; + + return &thePuzzle; } \ No newline at end of file diff --git a/a2sudoku/puzzles.h b/a2sudoku/puzzles.h index 18d26c8..17e261a 100644 --- a/a2sudoku/puzzles.h +++ b/a2sudoku/puzzles.h @@ -9,6 +9,7 @@ #include #include +#include #ifndef __a2sudoku__puzzles__ @@ -50,5 +51,9 @@ extern tSquareVal getStartValueAtPos(struct tPuzzle *puzzle, tPos x, tPos y); extern bool checkValueAtPos(struct tPuzzle *puzzle, tSquareVal val, tPos x, tPos y); +extern void savePuzzle(struct tPuzzle *puzzle, FILE *saveFile); + +extern struct tPuzzle *loadPuzzle(FILE *saveFile); + #endif /* defined(__a2sudoku__puzzles__) */ diff --git a/a2sudoku/ui.c b/a2sudoku/ui.c index c9f5c0b..dbed015 100644 --- a/a2sudoku/ui.c +++ b/a2sudoku/ui.c @@ -517,24 +517,59 @@ void youWon(void) bool playGame(void) { + bool shouldSave = false; + bool gameLoaded = false; + char ch; + initUI(); clrscr(); - printf("\n\nLoading your puzzle for you\n Please be patient..."); - textMode(); + if (loadGame(updatePos)) { + bool gotAnswer = false; + + printf("\n\nYou have a saved puzzle!\n Would you like to continue it (Y/N)"); + + while (!gotAnswer) { + ch = cgetc(); + switch (ch) { + case 'y': + case 'Y': + printf("\n\nLoading your saved puzzle"); + gameLoaded = true; + gotAnswer = true; + shouldSave = true; + break; + + case 'n': + case 'N': + gotAnswer = true; + break; + + default: + printf("\007"); + break; + } + } + } + cursorX = 0; cursorY = 0; - drawGrid(); + if (!gameLoaded) { + clrscr(); + + printf("\n\nLoading a new puzzle for you\n Please be patient..."); + startGame(gameOptions.difficulty, updatePos); + } - startGame(gameOptions.difficulty, updatePos); + drawGrid(); + refreshAllPos(); graphicsMode(); while (true) { - char ch; bool shouldNotBeep = true; bool shouldRefresh = false; @@ -568,6 +603,11 @@ bool playGame(void) case 'Q': case CH_ESC: shutdownUI(); + if (shouldSave) { + clrscr(); + printf("\n\nSaving your puzzle so you can continue\n later..."); + saveGame(); + } return false; case 'i': @@ -637,51 +677,71 @@ bool playGame(void) case '8': case '9': shouldNotBeep = setValueAtPos(cursorX, cursorY, ch - '0'); + if (shouldNotBeep) + shouldSave = true; break; case CH_F1: case '!': shouldNotBeep = toggleScratchValueAtPos(cursorX, cursorY, 1); + if (shouldNotBeep) + shouldSave = true; break; case CH_F2: case '@': shouldNotBeep = toggleScratchValueAtPos(cursorX, cursorY, 2); + if (shouldNotBeep) + shouldSave = true; break; case CH_F3: case '#': shouldNotBeep = toggleScratchValueAtPos(cursorX, cursorY, 3); + if (shouldNotBeep) + shouldSave = true; break; case CH_F4: case '$': shouldNotBeep = toggleScratchValueAtPos(cursorX, cursorY, 4); + if (shouldNotBeep) + shouldSave = true; break; case CH_F5: case '%': shouldNotBeep = toggleScratchValueAtPos(cursorX, cursorY, 5); + if (shouldNotBeep) + shouldSave = true; break; case CH_F6: case '^': shouldNotBeep = toggleScratchValueAtPos(cursorX, cursorY, 6); + if (shouldNotBeep) + shouldSave = true; break; case CH_F7: case '&': shouldNotBeep = toggleScratchValueAtPos(cursorX, cursorY, 7); + if (shouldNotBeep) + shouldSave = true; break; case CH_F8: case '*': shouldNotBeep = toggleScratchValueAtPos(cursorX, cursorY, 8); + if (shouldNotBeep) + shouldSave = true; break; case CH_F9: case '(': shouldNotBeep = toggleScratchValueAtPos(cursorX, cursorY, 9); + if (shouldNotBeep) + shouldSave = true; break; case 'u':