a2sudoku/a2sudoku/game.c

493 lines
12 KiB
C

//
// game.c
// a2sudoku
//
// Created by Jeremy Rand on 2015-07-15.
// Copyright (c) 2015 Jeremy Rand. All rights reserved.
//
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include "game.h"
#include "puzzles.h"
// Macros
#define SQUARE_XY(x, y) (theGame.squares[((y) * BOARD_SIZE) + (x)])
#define PREV_SQUARE_XY(x, y) (theGame.prevSquares[((y) * BOARD_SIZE) + (x)])
#define SAVE_GAME_FILE "a2sudoku.game"
// Typedefs
typedef struct tGameSquare {
tSquareVal value;
tScratchValues scratchValues;
bool knownAtStart;
bool correct;
bool invalid;
} tGameSquare;
typedef struct tGame {
tGameSquare squares[BOARD_SIZE * BOARD_SIZE];
tGameSquare prevSquares[BOARD_SIZE * BOARD_SIZE];
struct tPuzzle *puzzle;
tUpdatePosCallback callback;
bool undoValid;
time_t startTime;
} tGame;
// Globals
tGame theGame;
// Implementation
void refreshPos(tPos x, tPos y)
{
tGameSquare *square;
if (theGame.callback == NULL)
return;
square = &(SQUARE_XY(x, y));
theGame.callback(x, y, square->value, square->scratchValues, square->correct, square->invalid, square->knownAtStart);
}
void restartGame(void)
{
tPos x, y;
tGameSquare *square;
for (y = 0; y < BOARD_SIZE; y++) {
for (x = 0; x < BOARD_SIZE; x++) {
square = &(SQUARE_XY(x, y));
if (square->knownAtStart)
continue;
if ((square->value == EMPTY_SQUARE) &&
(square->scratchValues == 0))
continue;
square->value = EMPTY_SQUARE;
square->scratchValues = 0;
square->correct = false;
square->invalid = false;
refreshPos(x, y);
}
}
theGame.undoValid = false;
theGame.startTime = _systime();
}
void startGame(tDifficulty difficulty, tUpdatePosCallback callback)
{
tPos x, y;
theGame.puzzle = getRandomPuzzle(difficulty);
theGame.callback = callback;
memset(&(theGame.squares), 0, sizeof(theGame.squares));
for (y = 0; y < BOARD_SIZE; y++) {
for (x = 0; x < BOARD_SIZE; x++) {
tSquareVal squareVal = getStartValueAtPos(theGame.puzzle, x, y);
if (squareVal != EMPTY_SQUARE) {
SQUARE_XY(x, y).value = squareVal;
SQUARE_XY(x, y).knownAtStart = true;
SQUARE_XY(x, y).correct = true;
}
}
}
theGame.startTime = _systime();
}
void saveGame(void)
{
FILE *saveFile = fopen(SAVE_GAME_FILE, "wb");
if (saveFile != NULL) {
bool isValid = true;
fwrite(&isValid, sizeof(isValid), 1, saveFile);
// Change the start time into an elapsed time before saving...
if (theGame.startTime != 0xffffffff) {
theGame.startTime = _systime() - theGame.startTime;
}
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;
// The saved start time is an elapsed time. Convert it back into a start time relative to now.
if (theGame.startTime != 0xffffffff) {
theGame.startTime = _systime() - theGame.startTime;
}
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;
for (y = 0; y < BOARD_SIZE; y++) {
for (x = 0; x < BOARD_SIZE; x++) {
refreshPos(x, y);
}
}
}
bool isPuzzleSolved(void)
{
tPos x, y;
for (y = 0; y < BOARD_SIZE; y++) {
for (x = 0; x < BOARD_SIZE; x++) {
if (!(SQUARE_XY(x, y).correct))
return false;
}
}
return true;
}
time_t timeToSolve(void)
{
if (theGame.startTime == 0xffffffff)
return theGame.startTime;
return _systime() - theGame.startTime;
}
bool isSquareInvalid(tPos col, tPos row)
{
tSquareVal value = SQUARE_XY(col, row).value;
tPos x, y;
tPos subSquareXStart, subSquareXEnd;
tPos subSquareYStart, subSquareYEnd;
// Empty is always valid
if (value == EMPTY_SQUARE)
return false;
for (y = 0; y < BOARD_SIZE; y++) {
// If this value appears somewhere else in the same column, it is invalid
if ((y != row) &&
(value == SQUARE_XY(col, y).value))
return true;
}
for (x = 0; x < BOARD_SIZE; x++) {
// If this value appears somewhere else in the same row, it is invalid
if ((x != col) &&
(value == SQUARE_XY(x, row).value))
return true;
}
// Need to find the sub-square for this position and check among them...
subSquareXStart = ((col / SUBSQUARE_SIZE) * SUBSQUARE_SIZE);
subSquareXEnd = subSquareXStart + SUBSQUARE_SIZE;
subSquareYStart = ((row / SUBSQUARE_SIZE) * SUBSQUARE_SIZE);
subSquareYEnd = subSquareYStart + SUBSQUARE_SIZE;
for (y = subSquareYStart; y < subSquareYEnd; y++) {
for (x = subSquareXStart; x < subSquareXEnd; x++) {
if (x == col)
continue;
if (y == row)
continue;
if (value == SQUARE_XY(x, y).value)
return true;
}
}
// If we haven't seen this same value in the column, row or subsquare,
// then it is not invalid (it is valid).
return false;
}
void refreshNeighbours(tPos col, tPos row, tSquareVal newValue, tSquareVal oldValue)
{
tPos x, y;
tGameSquare *square = &(SQUARE_XY(col, row));
bool newInvalid;
bool checkInvalid = true;
tPos subSquareXStart, subSquareXEnd;
tPos subSquareYStart, subSquareYEnd;
newInvalid = isSquareInvalid(col, row);
if (newInvalid != square->invalid) {
square->invalid = newInvalid;
refreshPos(col, row);
}
// If the value was empty, and this square is not invalid,
// then nothing more to check. We couldn't be adding a
// new invalid square nor fixing an existing invalid
// square.
if ((oldValue == EMPTY_SQUARE) &&
(!newInvalid))
checkInvalid = false;
for (y = 0; y < BOARD_SIZE; y++) {
if (y == row)
continue;
square = &(SQUARE_XY(col, y));
if (square->knownAtStart)
continue;
if (checkInvalid) {
newInvalid = isSquareInvalid(col, y);
if (newInvalid != square->invalid) {
square->invalid = newInvalid;
refreshPos(col, y);
}
}
if ((newValue != EMPTY_SQUARE) &&
(SCRATCH_TEST(square->scratchValues, newValue))) {
square->scratchValues ^= (0x1 << newValue);
refreshPos(col, y);
}
}
for (x = 0; x < BOARD_SIZE; x++) {
if (x == col)
continue;
square = &(SQUARE_XY(x, row));
if (square->knownAtStart)
continue;
if (checkInvalid) {
newInvalid = isSquareInvalid(x, row);
if (newInvalid != square->invalid) {
square->invalid = newInvalid;
refreshPos(x, row);
}
}
if ((newValue != EMPTY_SQUARE) &&
(SCRATCH_TEST(square->scratchValues, newValue))) {
square->scratchValues ^= (0x1 << newValue);
refreshPos(x, row);
}
}
subSquareXStart = ((col / SUBSQUARE_SIZE) * SUBSQUARE_SIZE);
subSquareXEnd = subSquareXStart + SUBSQUARE_SIZE;
subSquareYStart = ((row / SUBSQUARE_SIZE) * SUBSQUARE_SIZE);
subSquareYEnd = subSquareYStart + SUBSQUARE_SIZE;
for (y = subSquareYStart; y < subSquareYEnd; y++) {
for (x = subSquareXStart; x < subSquareXEnd; x++) {
if (x == col)
continue;
if (y == row)
continue;
square = &(SQUARE_XY(x, y));
if (square->knownAtStart)
continue;
if (checkInvalid) {
newInvalid = isSquareInvalid(x, y);
if (newInvalid != square->invalid) {
square->invalid = newInvalid;
refreshPos(x, y);
}
}
if ((newValue != EMPTY_SQUARE) &&
(SCRATCH_TEST(square->scratchValues, newValue))) {
square->scratchValues ^= (0x1 << newValue);
refreshPos(x, y);
}
}
}
}
bool setValueAtPos(tPos x, tPos y, tSquareVal val)
{
tGameSquare *square = &(SQUARE_XY(x, y));
bool update = false;
bool checkValues = false;
bool correct;
tSquareVal oldValue = EMPTY_SQUARE;
if (square->knownAtStart) {
return false;
}
theGame.undoValid = true;
memcpy(theGame.prevSquares, theGame.squares, sizeof(theGame.squares));
if (square->value != val) {
oldValue = square->value;
square->value = val;
update = true;
checkValues = true;
}
if (square->scratchValues != 0) {
square->scratchValues = 0;
update = true;
}
if (checkValues) {
correct = checkValueAtPos(theGame.puzzle, val, x, y);
if (square->correct != correct) {
square->correct = correct;
update = true;
}
}
if (update)
refreshPos(x,y);
if (checkValues)
refreshNeighbours(x, y, val, oldValue);
return true;
}
bool toggleScratchValueAtPos(tPos x, tPos y, tSquareVal val)
{
tGameSquare *square = &(SQUARE_XY(x, y));
if (square->knownAtStart) {
return false;
}
theGame.undoValid = true;
memcpy(theGame.prevSquares, theGame.squares, sizeof(theGame.squares));
square->scratchValues ^= (0x1 << val);
refreshPos(x, y);
return true;
}
bool undoLastMove(void)
{
tGameSquare *square;
tGameSquare *prevSquare;
tPos x, y;
if (!theGame.undoValid)
return false;
for (y = 0; y < BOARD_SIZE; y++) {
for (x = 0; x < BOARD_SIZE; x++) {
square = &(SQUARE_XY(x, y));
if (square->knownAtStart)
continue;
prevSquare = &(PREV_SQUARE_XY(x, y));
if ((square->value != prevSquare->value) ||
(square->scratchValues != prevSquare->scratchValues) ||
(square->invalid != prevSquare->invalid)) {
memcpy(square, prevSquare, sizeof(*square));
refreshPos(x, y);
}
}
}
theGame.undoValid = false;
return true;
}