From 8d4120db21ab87c51995eee8607ef72ee6c7d5f2 Mon Sep 17 00:00:00 2001 From: Jeremy Rand Date: Thu, 16 Jul 2015 09:40:56 -0500 Subject: [PATCH] Add puzzle difficulty support. Load and save some game options --- a2sudoku/game.c | 12 ++- a2sudoku/game.h | 4 +- a2sudoku/puzzles.c | 99 ++++++++++++++---------- a2sudoku/puzzles.h | 9 ++- a2sudoku/ui.c | 185 ++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 248 insertions(+), 61 deletions(-) diff --git a/a2sudoku/game.c b/a2sudoku/game.c index 714fde9..e58d8ce 100644 --- a/a2sudoku/game.c +++ b/a2sudoku/game.c @@ -50,15 +50,15 @@ void refreshPos(tPos x, tPos y) return; square = &(SQUARE_XY(x, y)); - theGame.callback(x, y, square->value, square->scratchValues, square->correct, square->invalid); + theGame.callback(x, y, square->value, square->scratchValues, square->correct, square->invalid, square->knownAtStart); } -void startGame(tUpdatePosCallback callback) +void startGame(tDifficulty difficulty, tUpdatePosCallback callback) { tPos x, y; - theGame.puzzle = getRandomPuzzle(); + theGame.puzzle = getRandomPuzzle(difficulty); theGame.callback = callback; memset(&(theGame.squares), 0, sizeof(theGame.squares)); @@ -157,9 +157,13 @@ void refreshInvalid(tPos col, tPos row) for (y = 0; y < BOARD_SIZE; y++) { for (x = 0; x < BOARD_SIZE; x++) { - newInvalid = isSquareInvalid(x, y); square = &(SQUARE_XY(x, y)); + if (square->knownAtStart) + continue; + + newInvalid = isSquareInvalid(x, y); + if (newInvalid != square->invalid) { square->invalid = newInvalid; refreshPos(x, y); diff --git a/a2sudoku/game.h b/a2sudoku/game.h index 9813e80..2c5e073 100644 --- a/a2sudoku/game.h +++ b/a2sudoku/game.h @@ -21,12 +21,12 @@ // Typedefs typedef uint16_t tScratchValues; -typedef void (*tUpdatePosCallback)(tPos x, tPos y, tSquareVal val, tScratchValues scratch, bool correct, bool invalid); +typedef void (*tUpdatePosCallback)(tPos x, tPos y, tSquareVal val, tScratchValues scratch, bool correct, bool invalid, bool knownAtStart); // API -extern void startGame(tUpdatePosCallback callback); +extern void startGame(tDifficulty difficulty, tUpdatePosCallback callback); extern void refreshAllPos(void); diff --git a/a2sudoku/puzzles.c b/a2sudoku/puzzles.c index a9c67ea..e34c7a4 100644 --- a/a2sudoku/puzzles.c +++ b/a2sudoku/puzzles.c @@ -39,17 +39,25 @@ typedef struct tPuzzle { // Forward declarations -tPuzzle puzzles[]; +tPuzzle easyPuzzles[]; +tPuzzle mediumPuzzles[]; +tPuzzle hardPuzzles[]; -tPuzzleNum numPuzzles(void); +tPuzzleNum numPuzzles(tDifficulty difficulty); // Implementation -tPuzzle *getRandomPuzzle(void) +tPuzzle *getRandomPuzzle(tDifficulty difficulty) { - tPuzzleNum randomPuzzleNum = (rand() % numPuzzles()); - return &(puzzles[randomPuzzleNum]); + tPuzzleNum randomPuzzleNum = (rand() % numPuzzles(difficulty)); + + if (difficulty == DIFFICULTY_EASY) + return &(easyPuzzles[randomPuzzleNum]); + else if (difficulty == DIFFICULTY_MEDIUM) + return &(mediumPuzzles[randomPuzzleNum]); + + return &(hardPuzzles[randomPuzzleNum]); } @@ -66,41 +74,9 @@ bool checkValueAtPos(tPuzzle *puzzle, tSquareVal val, tPos x, tPos y) } -void printPuzzle(tPuzzle *puzzle, bool solution) -{ - tPos x, y; - - for (y = 0; y < BOARD_SIZE; y++) { - if ((y % SUBSQUARE_SIZE) == 0) { - printf("\n"); - } - - for (x = 0; x < BOARD_SIZE; x++) { - char displayChar = ' '; - tSquareVal squareVal; - - if ((x % SUBSQUARE_SIZE) == 0) { - printf(" "); - } - if (solution) { - squareVal = PUZZLE_SOLVED_VAL(PUZZLE_SQUARE(puzzle, x, y)); - } else { - squareVal = PUZZLE_START_VAL(PUZZLE_SQUARE(puzzle, x, y)); - } - - if (squareVal != EMPTY_SQUARE) - displayChar = '0' + squareVal; - - printf(" %c ", displayChar); - } - printf("\n"); - } -} - - // Puzzle definitions -tPuzzle puzzles[] = { +tPuzzle easyPuzzles[] = { { { PVAL(4),PVAL(3),PVAL(5), SVAL(2),SVAL(6),PVAL(9), SVAL(7),PVAL(8),SVAL(1), @@ -119,7 +95,50 @@ tPuzzle puzzles[] = { }; -tPuzzleNum numPuzzles(void) +tPuzzle mediumPuzzles[] = { + { + { + PVAL(4),PVAL(3),PVAL(5), SVAL(2),SVAL(6),PVAL(9), SVAL(7),PVAL(8),SVAL(1), + SVAL(6),SVAL(8),PVAL(2), PVAL(5),SVAL(7),PVAL(1), PVAL(4),SVAL(9),PVAL(3), + SVAL(1),SVAL(9),PVAL(7), PVAL(8),PVAL(3),SVAL(4), SVAL(5),PVAL(6),PVAL(2), + + SVAL(8),SVAL(2),PVAL(6), SVAL(1),PVAL(9),PVAL(5), PVAL(3),SVAL(4),PVAL(7), + PVAL(3),PVAL(7),SVAL(4), SVAL(6),PVAL(8),SVAL(2), SVAL(9),PVAL(1),PVAL(5), + PVAL(9),SVAL(5),PVAL(1), PVAL(7),PVAL(4),SVAL(3), PVAL(6),SVAL(2),SVAL(8), + + PVAL(5),PVAL(1),SVAL(9), SVAL(3),PVAL(2),PVAL(6), PVAL(8),SVAL(7),SVAL(4), + PVAL(2),SVAL(4),PVAL(8), PVAL(9),SVAL(5),PVAL(7), PVAL(1),SVAL(3),SVAL(6), + SVAL(7),PVAL(6),SVAL(3), PVAL(4),SVAL(1),SVAL(8), PVAL(2),PVAL(5),PVAL(9) + } + }, +}; + + +tPuzzle hardPuzzles[] = { + { + { + PVAL(4),PVAL(3),PVAL(5), SVAL(2),SVAL(6),PVAL(9), SVAL(7),PVAL(8),SVAL(1), + SVAL(6),SVAL(8),PVAL(2), PVAL(5),SVAL(7),PVAL(1), PVAL(4),SVAL(9),PVAL(3), + SVAL(1),SVAL(9),PVAL(7), PVAL(8),PVAL(3),SVAL(4), SVAL(5),PVAL(6),PVAL(2), + + SVAL(8),SVAL(2),PVAL(6), SVAL(1),PVAL(9),PVAL(5), PVAL(3),SVAL(4),PVAL(7), + PVAL(3),PVAL(7),SVAL(4), SVAL(6),PVAL(8),SVAL(2), SVAL(9),PVAL(1),PVAL(5), + PVAL(9),SVAL(5),PVAL(1), PVAL(7),PVAL(4),SVAL(3), PVAL(6),SVAL(2),SVAL(8), + + PVAL(5),PVAL(1),SVAL(9), SVAL(3),PVAL(2),PVAL(6), PVAL(8),SVAL(7),SVAL(4), + PVAL(2),SVAL(4),PVAL(8), PVAL(9),SVAL(5),PVAL(7), PVAL(1),SVAL(3),SVAL(6), + SVAL(7),PVAL(6),SVAL(3), PVAL(4),SVAL(1),SVAL(8), PVAL(2),PVAL(5),PVAL(9) + } + }, +}; + + +tPuzzleNum numPuzzles(tDifficulty difficulty) { - return sizeof(puzzles) / sizeof(puzzles[0]); + if (difficulty == DIFFICULTY_EASY) + return sizeof(easyPuzzles) / sizeof(easyPuzzles[0]); + else if (difficulty == DIFFICULTY_MEDIUM) + return sizeof(mediumPuzzles) / sizeof(mediumPuzzles[0]); + + return sizeof(hardPuzzles) / sizeof(hardPuzzles[0]); } \ No newline at end of file diff --git a/a2sudoku/puzzles.h b/a2sudoku/puzzles.h index 47359e9..18d26c8 100644 --- a/a2sudoku/puzzles.h +++ b/a2sudoku/puzzles.h @@ -25,6 +25,10 @@ #define EMPTY_SQUARE 0 +#define DIFFICULTY_EASY 0 +#define DIFFICULTY_MEDIUM 1 +#define DIFFICULTY_HARD 2 + // Forward declarations @@ -33,19 +37,18 @@ struct tPuzzle; // Typedefs +typedef uint8_t tDifficulty; typedef uint8_t tSquareVal; typedef uint8_t tPos; // API -extern struct tPuzzle *getRandomPuzzle(void); +extern struct tPuzzle *getRandomPuzzle(tDifficulty difficulty); extern tSquareVal getStartValueAtPos(struct tPuzzle *puzzle, tPos x, tPos y); extern bool checkValueAtPos(struct tPuzzle *puzzle, tSquareVal val, tPos x, tPos y); -extern void printPuzzle(struct tPuzzle *puzzle, bool solution); - #endif /* defined(__a2sudoku__puzzles__) */ diff --git a/a2sudoku/ui.c b/a2sudoku/ui.c index 9a34953..c9f795e 100644 --- a/a2sudoku/ui.c +++ b/a2sudoku/ui.c @@ -33,6 +33,10 @@ extern char a2e_hi; #define TEXT_OFFSET_X 12 #define TEXT_OFFSET_Y 6 +#define TEXT_UNDERLINE_OFFSET_X -2 +#define TEXT_UNDERLINE_OFFSET_Y 9 +#define TEXT_UNDERLINE_WIDTH 8 + #define TOTAL_WIDTH ((BOARD_SIZE * SQUARE_WIDTH) + \ ((BOARD_SIZE + 1) * THIN_LINE_WIDTH) + \ (((BOARD_SIZE / SUBSQUARE_SIZE) + 1) * (THICK_LINE_WIDTH - THIN_LINE_WIDTH))) @@ -48,16 +52,42 @@ extern char a2e_hi; #define SCRATCH_HEIGHT 6 +// Typedefs + +typedef struct tOptions { + tDifficulty difficulty; + bool showInvalid; + bool showWrong; +} tOptions; + + // Globals; tPos cursorX, cursorY; int screenSquaresX[BOARD_SIZE]; int screenSquaresY[BOARD_SIZE]; +tOptions gameOptions = { + DIFFICULTY_EASY, + true, + true +}; + // Implementation +char *difficultyString(tDifficulty difficulty) +{ + if (difficulty == DIFFICULTY_EASY) + return "Easy"; + else if (difficulty == DIFFICULTY_MEDIUM) + return "Medium"; + + return "Hard"; +} + + void drawGrid(void) { tPos pos; @@ -109,16 +139,118 @@ void initUI(void) } +void loadOptions(void) +{ + static bool optionsLoaded = false; + FILE *optionsFile; + + if (optionsLoaded) + return; + + optionsFile = fopen("a2sudokuopts", "rb"); + if (optionsFile != NULL) { + fread(&gameOptions, sizeof(gameOptions), 1, optionsFile); + fclose(optionsFile); + } + optionsLoaded = true; +} + + void shutdownUI(void) { + FILE *optionsFile; + optionsFile = fopen("a2sudokuopts", "wb"); + if (optionsFile != NULL) { + fwrite(&gameOptions, sizeof(gameOptions), 1, optionsFile); + fclose(optionsFile); + } + // Uninstall drivers tgi_uninstall(); } +void textMode(void) +{ + asm ("STA %w", 0xc051); +} + + +void graphicsMode(void) +{ + asm ("STA %w", 0xc050); +} + + +bool setOptions(void) +{ + bool shouldUpdate = false; + bool keepLooping = true; + + while (keepLooping) { + clrscr(); + + // 1111111111222222222233333333334 + // 1234567890123456789012345678901234567890 + printf( + " Apple ][ Sudoku\n" + " By Jeremy Rand\n" + "\n" + "Options:\n" + " Difficulty : %s\n" + " Show invalid values : %s\n" + " Show wrong values : %s\n" + "\n" + "Press D to change difficulty.\n" + "Press I to %sshow invalid values.\n" + "Press W to %sshow wrong values.\n" + "\n" + "Press any other key to return.\n", + difficultyString(gameOptions.difficulty), + (gameOptions.showInvalid ? "On" : "Off"), + (gameOptions.showWrong ? "On" : "Off"), + (gameOptions.showInvalid ? "not " : ""), + (gameOptions.showWrong ? "not " : "")); + + switch (cgetc()) { + case 'd': + case 'D': + if (gameOptions.difficulty == DIFFICULTY_HARD) + gameOptions.difficulty = DIFFICULTY_EASY; + else + gameOptions.difficulty++; + break; + + case 'i': + case 'I': + gameOptions.showInvalid = !gameOptions.showInvalid; + shouldUpdate = true; + break; + + case 'w': + case 'W': + gameOptions.showWrong = !gameOptions.showWrong; + shouldUpdate = true; + break; + + default: + keepLooping = false; + break; + } + } + + clrscr(); + + return shouldUpdate; +} + + void displayInstructions(void) { int seed = 0; + char ch; + + loadOptions(); clrscr(); @@ -135,25 +267,35 @@ void displayInstructions(void) "to enter a value. Press a number key\n" "while holding shift or open apple to\n" "toggle a scratch value. Press 0 to\n" - "clear a square.\n" + "clear a square. Play ends when the\n" + "puzzle is solved.\n" "\n" - "Play ends when the puzzle is solved.\n" + " Difficulty : %s\n" + " Show invalid values : %s\n" + " Show wrong values : %s\n" "\n" "Press escape or Q to quit at any time.\n" + "Press O to change options.\n" "Press R to start a new game.\n" "Press H to see this info again.\n" "\n" - "\n" - "\n" - " Press any key to start"); + " Press O to change options or any other\n" + " key to start", + difficultyString(gameOptions.difficulty), + (gameOptions.showInvalid ? "On" : "Off"), + (gameOptions.showWrong ? "On" : "Off")); // The amount of time the user waits to read the in while (!kbhit()) seed++; - cgetc(); + ch = cgetc(); srand(seed); + if ((ch == 'o') || + (ch == 'O')) + setOptions(); + clrscr(); } @@ -295,7 +437,7 @@ void drawScratch(tPos x, tPos y, tScratchValues scratch) } -void updatePos(tPos x, tPos y, tSquareVal val, tScratchValues scratch, bool correct, bool invalid) +void updatePos(tPos x, tPos y, tSquareVal val, tScratchValues scratch, bool correct, bool invalid, bool knownAtStart) { int screenX = screenSquaresX[x]; int screenY = screenSquaresY[y]; @@ -313,10 +455,19 @@ void updatePos(tPos x, tPos y, tSquareVal val, tScratchValues scratch, bool corr tgi_outtextxy(screenX + TEXT_OFFSET_X, screenY + TEXT_OFFSET_Y, buffer); - if (!correct) + if (knownAtStart) { + tgi_line(screenX + TEXT_OFFSET_X + TEXT_UNDERLINE_OFFSET_X, + screenY + TEXT_OFFSET_Y + TEXT_UNDERLINE_OFFSET_Y, + screenX + TEXT_OFFSET_X + TEXT_UNDERLINE_OFFSET_X + TEXT_UNDERLINE_WIDTH, + screenY + TEXT_OFFSET_Y + TEXT_UNDERLINE_OFFSET_Y); + } + + if ((gameOptions.showWrong) && + (!correct)) tgi_line(screenX, edgeY, edgeX, screenY); - if (invalid) + if ((gameOptions.showInvalid) && + (invalid)) tgi_line(screenX, edgeY, edgeX, screenY); } else if (scratch != 0) { drawScratch(x, y, scratch); @@ -360,11 +511,12 @@ bool playGame(void) drawGrid(); - startGame(updatePos); + startGame(DIFFICULTY_EASY, updatePos); while (true) { char ch; bool shouldNotBeep = true; + bool shouldRefresh = false; if (isPuzzleSolved()) { youWon(); @@ -376,9 +528,16 @@ bool playGame(void) switch (ch) { case 'h': case 'H': + textMode(); displayInstructions(); - clrscr(); - refreshAllPos(); + graphicsMode(); + break; + + case 'o': + case 'O': + textMode(); + shouldRefresh = setOptions(); + graphicsMode(); break; case 'r': @@ -492,6 +651,8 @@ bool playGame(void) if (!shouldNotBeep) { printf("\007"); } + if (shouldRefresh) + refreshAllPos(); } return false;