diff --git a/triangle.c b/triangle.c index 42d91cd..f90678d 100644 --- a/triangle.c +++ b/triangle.c @@ -1,14 +1,14 @@ /** - * FileName: triangle.c + * FileName: triangle.c * * @file * Main program loop and initialization code * * @author LE - * + * * REVISION HISTORY: * - * - LE 06/30/15 -> original development + * - LE 06/30/15 -> original development */ //system header files @@ -31,94 +31,87 @@ #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0) +#define UNSPECIFIED 255 // sentinel for unset row/column command-line args + + +/* + * A jump move is fully described by the offsets, relative to the destination + * (empty) cell, of the two pegs involved: the one being jumped over and the + * one that is the source of the jump. Six distinct directions are possible. + */ +typedef struct { + int8_t jumped_dr, jumped_dc; // offset to peg being jumped over + int8_t source_dr, source_dc; // offset to peg making the jump + const char* name; +} DIRECTION; + +#define NUM_DIRECTIONS 6 +static const DIRECTION DIRECTIONS[NUM_DIRECTIONS] = { + { -1, -1, -2, -2, "Diagonal Down L->R" }, + { -1, 0, -2, 0, "Diagonal Down R->L" }, + { 1, 0, 2, 0, "Diagonal Up L->R" }, + { 1, 1, 2, 2, "Diagonal Up R->L" }, + { 0, -1, 0, -2, "Horizontal L->R" }, + { 0, 1, 0, 2, "Horizontal R->L" }, +}; + // Local function prototypes -static uint8_t num_moves(const GAME_STATE* state); -static void init_game(GAME_STATE* state, uint8_t missing_row, uint8_t missing_column); +static int parse_args(int argc, char** argv, + uint8_t* missing_row, uint8_t* missing_column, + uint8_t* final_row, uint8_t* final_column); +static uint8_t num_moves(const GAME_STATE* state); +static void init_game(GAME_STATE* state, uint8_t missing_row, uint8_t missing_column); static int check_for_winning_state(const GAME_STATE* state, uint8_t final_row, uint8_t final_column); -static int get_new_state(const uint8_t move, const GAME_STATE* current_state, GAME_STATE* new_state); -static void printf_board(const GAME_STATE* state); +static int get_new_state(uint8_t move, const GAME_STATE* current_state, GAME_STATE* new_state); +static int in_triangle(int row, int column); +static int is_valid_jump(const GAME_STATE* state, int empty_row, int empty_col, const DIRECTION* dir); +static void apply_move(GAME_STATE* new_state, const GAME_STATE* current_state, + int empty_row, int empty_col, const DIRECTION* dir); +static void printf_board(const GAME_STATE* state); /** * Program entry point - * + * */ int main (int argc, char **argv) { - GAME_STATE moves[MAX_MOVES]; //this will hold all of the moves that we will try + GAME_STATE moves[MAX_MOVES]; // holds every move tried along the current search path uint8_t done = false; uint8_t current_depth = 0; int i; - uint8_t missing_row = 255; - uint8_t missing_column = 255; - uint8_t final_row = 255; - uint8_t final_column = 255; + uint8_t missing_row = UNSPECIFIED; + uint8_t missing_column = UNSPECIFIED; + uint8_t final_row = UNSPECIFIED; + uint8_t final_column = UNSPECIFIED; uint64_t steps = 0; - int syntax_error = false; - //make sure the user passed us 2 (or 4) arguments that were integers 0-4 - if (argc != 3 && argc != 5) - { - syntax_error = true; - } - else - { - if (sscanf (argv[1], "%hhu", &missing_row) != 1) - { - syntax_error = true; - } - - if (sscanf (argv[2], "%hhu", &missing_column) != 1) - { - syntax_error = true; - } - if (missing_row > 4 || missing_column > 4) - { - syntax_error = true; - } - if (5 == argc) - { - if (sscanf (argv[3], "%hhu", &final_row) != 1) - { - syntax_error = true; - } - - if (sscanf (argv[4], "%hhu", &final_column) != 1) - { - syntax_error = true; - } - } - } - if (syntax_error) + if (!parse_args(argc, argv, &missing_row, &missing_column, &final_row, &final_column)) { printf("Usage: \"triangle \" or \n"); printf("\t \"triangle \" \n\n"); printf("\t where and are integers 0-4\n\n"); - exit (1); + exit(1); } - //sanity check values if (missing_column > missing_row) { printf("Invalid column. (Max column# for row %d is %d.)\n", missing_row, missing_row); - exit (1); + exit(1); } - - //initialize the game + for (i = 0; i < MAX_MOVES; i++) { - memset(&moves[i],0, sizeof(GAME_STATE)); + memset(&moves[i], 0, sizeof(GAME_STATE)); } - init_game(&moves[0], missing_row, missing_column); - - //Start Triangle Solver application. + MAINDBG("MAIN: ********* TRIANGLE SOLVER APPLICATION STARTING ************.\n"); - + printf("%s\n", moves[0].description); printf_board(&moves[0]); - if (final_row <= 4 && final_column<=4) + if (final_row <= 4 && final_column <= 4) { printf("Search will only stop if the last peg is at space (%d,%d).\n", final_row, final_column); } @@ -129,19 +122,19 @@ int main (int argc, char **argv) while (!done) { - //Check to see if we have a final answer... + // Check whether the current node is a winning state. if (WINNING_STATE == check_for_winning_state(&moves[current_depth], final_row, final_column)) { MAINDBG("MAIN: We have a winning state!\n"); done = true; break; - } - - steps ++; + } - //if our deepest node has no options, it's a dead end... go up one and mark that we've tried this path. - if ((0 == moves[current_depth].num_possible_moves) || \ - (moves[current_depth].num_moves_tried == moves[current_depth].num_possible_moves)) + steps++; + + // Dead end: no moves available, or all moves already tried. Backtrack. + if ((0 == moves[current_depth].num_possible_moves) || + (moves[current_depth].num_moves_tried == moves[current_depth].num_possible_moves)) { if (0 == current_depth) { @@ -151,16 +144,16 @@ int main (int argc, char **argv) } MAINDBG("MAIN: Move at level %d is a dead end. Moving up one.\n", current_depth); - - moves[current_depth-1].num_moves_tried++; + + moves[current_depth - 1].num_moves_tried++; current_depth--; } - //If our deepest node has more options left to try, try one of them. + // Otherwise descend into the next untried move. if (moves[current_depth].num_moves_tried < moves[current_depth].num_possible_moves) { MAINDBG("MAIN: Node at depth %d has more options to try... trying option %d\n", current_depth, moves[current_depth].num_moves_tried); - if (MAX_MOVES-1 == current_depth) + if (MAX_MOVES - 1 == current_depth) { MAINDBG("MAIN: Error - something went wrong and we think there are more options at depth %d. Exiting.\n", current_depth); exit(1); @@ -183,144 +176,153 @@ int main (int argc, char **argv) } printf("Steps required to find answer: %llu\n\n", steps); - - exit (0); - + + exit(0); +} + +/** + * Parse command-line arguments. Returns true on success, false on syntax error. + * On success, missing_row/missing_column are always set; final_row/final_column + * are set only when 4 arguments are provided (otherwise left untouched). + */ +static int parse_args(int argc, char** argv, + uint8_t* missing_row, uint8_t* missing_column, + uint8_t* final_row, uint8_t* final_column) +{ + if (argc != 3 && argc != 5) + { + return false; + } + + if (sscanf(argv[1], "%hhu", missing_row) != 1) return false; + if (sscanf(argv[2], "%hhu", missing_column) != 1) return false; + if (*missing_row > 4 || *missing_column > 4) return false; + + if (argc == 5) + { + if (sscanf(argv[3], "%hhu", final_row) != 1) return false; + if (sscanf(argv[4], "%hhu", final_column) != 1) return false; + } + + return true; +} + +/** + * Returns true if (row, column) is a real cell of the triangular board. + */ +static int in_triangle(int row, int column) +{ + return row >= 0 && row < BOARD_SIZE && column >= 0 && column <= row; } -static uint8_t num_moves(const GAME_STATE* state) +/** + * Returns true if a peg can jump in the given direction *into* the empty cell + * at (empty_row, empty_col): both the source peg and the jumped peg must be + * on the board and present. + */ +static int is_valid_jump(const GAME_STATE* state, int empty_row, int empty_col, const DIRECTION* dir) +{ + int jr = empty_row + dir->jumped_dr; + int jc = empty_col + dir->jumped_dc; + int sr = empty_row + dir->source_dr; + int sc = empty_col + dir->source_dc; + + return in_triangle(jr, jc) && in_triangle(sr, sc) && + state->peg[jr][jc] == PEG_PRESENT && + state->peg[sr][sc] == PEG_PRESENT; +} + +/** + * Apply a jump: source peg lands in the empty cell, and the source and jumped + * pegs are removed. Recomputes the move count for the resulting state. + */ +static void apply_move(GAME_STATE* new_state, const GAME_STATE* current_state, + int empty_row, int empty_col, const DIRECTION* dir) { - uint8_t result = 0; - int row = 0; - int column = 0; - uint8_t column_max[5] = {0,1,2,3,4}; //define the maximum column for each row - - - //Moves are only possible if there is a blank space. Look for a blank space... - for (row = 0; row < 5; row++) + int jr = empty_row + dir->jumped_dr; + int jc = empty_col + dir->jumped_dc; + int sr = empty_row + dir->source_dr; + int sc = empty_col + dir->source_dc; + + memcpy(new_state, current_state, sizeof(GAME_STATE)); + new_state->peg[empty_row][empty_col] = PEG_PRESENT; + new_state->peg[jr][jc] = NO_PEG_PRESENT; + new_state->peg[sr][sc] = NO_PEG_PRESENT; + new_state->num_possible_moves = num_moves(new_state); + new_state->num_moves_tried = 0; + snprintf(new_state->description, sizeof(new_state->description), + "Peg (%d,%d) moves %s", sr, sc, dir->name); +} + +/** + * Count the number of legal moves available from this state. A move is keyed + * on its destination (an empty cell), so we walk every empty cell and try + * each of the 6 jump directions. + */ +static uint8_t num_moves(const GAME_STATE* state) +{ + uint8_t count = 0; + + for (int row = 0; row < BOARD_SIZE; row++) { - for (column = 0; column <= column_max[row]; column++) + for (int column = 0; column <= row; column++) { - if (NO_PEG_PRESENT == state->peg[row][column]) + if (state->peg[row][column] != NO_PEG_PRESENT) { - /* - * we have a blank space! Check for valid moves in the following order: - * 1) diagonal down l->r - * 2) diagonal down r->l - * 3) diagonal up l->r - * 4) diagonal up r->l - * 5) horizontal l->r - * 6) horizontal r->l - */ - - //Diagonal down, l->r - if ((row >= 2) && (column >= 2)) - { - if ((PEG_PRESENT == state->peg[row-1][column-1]) && \ - (PEG_PRESENT == state->peg[row-2][column-2])) - { - result++; - } - } - //Diagonal down, r->l - if ((row >= 2) && (column <= (column_max[row] - 2))) - { - if ((PEG_PRESENT == state->peg[row-1][column]) && \ - (PEG_PRESENT == state->peg[row-2][column])) - { - result++; - } - } - //Diagonal up, l->r - if ((row <= 2) && (column <= 2)) - { - if ((PEG_PRESENT == state->peg[row+1][column]) && \ - (PEG_PRESENT == state->peg[row+2][column])) - { - result++; - } - } - //Diagonal up, r->l - if ((row <= 2) && (column >= (column_max[row] - 2))) - { - if ((PEG_PRESENT == state->peg[row+1][column+1]) && \ - (PEG_PRESENT == state->peg[row+2][column+2])) - { - result++; - } - } - //Horizontal, l->r - if (column >= 2) - { - if ((PEG_PRESENT == state->peg[row][column-1]) && \ - (PEG_PRESENT == state->peg[row][column-2])) - { - result++; - } - } - //Horizontal, r->l - if (column <= 2) + continue; + } + for (int d = 0; d < NUM_DIRECTIONS; d++) + { + if (is_valid_jump(state, row, column, &DIRECTIONS[d])) { - if ((PEG_PRESENT == state->peg[row][column+1]) && \ - (PEG_PRESENT == state->peg[row][column+2])) - { - result++; - } + count++; } } } } - - return result; + + return count; } static void init_game(GAME_STATE* state, uint8_t missing_row, uint8_t missing_column) { - int row, column; - uint8_t column_max[5] = {0,1,2,3,4}; //define the maximum column for each row - uint8_t moves; - - //initialize beginning state - for (row = 0; row < 5; row++) + for (int row = 0; row < BOARD_SIZE; row++) { - for (column = 0; column < 5; column++) + for (int column = 0; column < BOARD_SIZE; column++) { - if (column <= column_max[row]) + if (column <= row) { state->peg[row][column] = PEG_PRESENT; } else - { + { state->peg[row][column] = PEG_INVALID; } } } - //take out one of the pegs state->peg[missing_row][missing_column] = NO_PEG_PRESENT; - //calculate number of possible moves... - moves = num_moves(state); - state->num_possible_moves = moves; + state->num_possible_moves = num_moves(state); state->num_moves_tried = 0; - snprintf(state->description, 50, "Initial board - peg (%d,%d) missing.", missing_row, missing_column); - MAINDBG("MAIN: Initial setup has %d allowable moves. (Desciption: %s)\n", moves, state->description); + snprintf(state->description, sizeof(state->description), + "Initial board - peg (%d,%d) missing.", missing_row, missing_column); + MAINDBG("MAIN: Initial setup has %d allowable moves. (Desciption: %s)\n", + state->num_possible_moves, state->description); } static int check_for_winning_state(const GAME_STATE* state, uint8_t final_row, uint8_t final_column) { - int row, column; uint8_t num_pegs_left = 0; - + if (state->num_possible_moves > 0) { return NOT_WINNING_STATE; } - - //initialize beginning state - for (row = 0; row < 5; row++) + + for (int row = 0; row < BOARD_SIZE; row++) { - for (column = 0; column < 5; column++) + for (int column = 0; column < BOARD_SIZE; column++) { if (PEG_PRESENT == state->peg[row][column]) { @@ -333,174 +335,56 @@ static int check_for_winning_state(const GAME_STATE* state, uint8_t final_row, u } } - // if we get here, we have 1 or fewer pegs in the board! + // At most 1 peg remains. If the caller specified a target cell, the peg must be there. if (final_row <= 4 && final_column <= 4) { - if (state->peg[final_row][final_column] == PEG_PRESENT) - { - return WINNING_STATE; - } - else - { - return NOT_WINNING_STATE; - } - } - else - { - return WINNING_STATE; + return (state->peg[final_row][final_column] == PEG_PRESENT) ? WINNING_STATE : NOT_WINNING_STATE; } + return WINNING_STATE; } -static int get_new_state(const uint8_t move, const GAME_STATE* current_state, GAME_STATE* new_state) +/** + * Find the move-th legal move (0-indexed) and write the resulting board to + * new_state. The enumeration order matches num_moves(), so the caller can + * iterate move=0..num_possible_moves-1 to walk every option. + */ +static int get_new_state(uint8_t move, const GAME_STATE* current_state, GAME_STATE* new_state) { - int row = 0; - int column = 0; - uint8_t column_max[5] = {0,1,2,3,4}; //define the maximum column for each row - uint8_t result = 0; - - if (move > current_state->num_possible_moves) + uint8_t found = 0; + + if (move >= current_state->num_possible_moves) { - MAINDBG("MAIN: ERROR! Tried move %d but there were only %d moves possible.\n", move, current_state->num_possible_moves); + MAINDBG("MAIN: ERROR! Tried move %d but there were only %d moves possible.\n", + move, current_state->num_possible_moves); return false; } - - //Moves are only possible if there is a blank space. Look for a blank space... - for (row = 0; row < 5; row++) + for (int row = 0; row < BOARD_SIZE; row++) { - for (column = 0; column <= column_max[row]; column++) + for (int column = 0; column <= row; column++) { - if (NO_PEG_PRESENT == current_state->peg[row][column]) + if (current_state->peg[row][column] != NO_PEG_PRESENT) { - /* - * we have a blank space! Check for valid moves in the following order: - * 1) diagonal down l->r - * 2) diagonal down r->l - * 3) diagonal up l->r - * 4) diagonal up r->l - * 5) horizontal l->r - * 6) horizontal r->l - */ - - //Diagonal down, l->r - if ((row >= 2) && (column >= 2)) - { - if ((PEG_PRESENT == current_state->peg[row-1][column-1]) && \ - (PEG_PRESENT == current_state->peg[row-2][column-2])) - { - result++; - if ((move + 1) == result) - { - memcpy(new_state, current_state, sizeof(GAME_STATE)); - new_state->peg[row][column] = PEG_PRESENT; - new_state->peg[row-1][column-1] = NO_PEG_PRESENT; - new_state->peg[row-2][column-2] = NO_PEG_PRESENT; - new_state->num_possible_moves = num_moves(new_state); - new_state->num_moves_tried = 0; - snprintf(new_state->description,50,"Peg (%d,%d) moves Diagonal Down L->R",(row-2),(column-2)); - } - } - } - //Diagonal down, r->l - if ((row >= 2) && (column <= (column_max[row] - 2))) - { - if ((PEG_PRESENT == current_state->peg[row-1][column]) && \ - (PEG_PRESENT == current_state->peg[row-2][column])) - { - result++; - if ((move + 1) == result) - { - memcpy(new_state, current_state, sizeof(GAME_STATE)); - new_state->peg[row][column] = PEG_PRESENT; - new_state->peg[row-1][column] = NO_PEG_PRESENT; - new_state->peg[row-2][column] = NO_PEG_PRESENT; - new_state->num_possible_moves = num_moves(new_state); - new_state->num_moves_tried = 0; - snprintf(new_state->description,50,"Peg (%d,%d) moves Diagonal Down R->L",(row-2),(column)); - } - } - } - //Diagonal up, l->r - if ((row <= 2) && (column <= 2)) - { - if ((PEG_PRESENT == current_state->peg[row+1][column]) && \ - (PEG_PRESENT == current_state->peg[row+2][column])) - { - result++; - if ((move + 1) == result) - { - memcpy(new_state, current_state, sizeof(GAME_STATE)); - new_state->peg[row][column] = PEG_PRESENT; - new_state->peg[row+1][column] = NO_PEG_PRESENT; - new_state->peg[row+2][column] = NO_PEG_PRESENT; - new_state->num_possible_moves = num_moves(new_state); - new_state->num_moves_tried = 0; - snprintf(new_state->description,50,"Peg (%d,%d) moves Diagonal Up L->R",(row+2),(column)); - } - } - } - //Diagonal up, r->l - if ((row <= 2) && (column >= (column_max[row] - 2))) - { - if ((PEG_PRESENT == current_state->peg[row+1][column+1]) && \ - (PEG_PRESENT == current_state->peg[row+2][column+2])) - { - result++; - if ((move + 1) == result) - { - memcpy(new_state, current_state, sizeof(GAME_STATE)); - new_state->peg[row][column] = PEG_PRESENT; - new_state->peg[row+1][column+1] = NO_PEG_PRESENT; - new_state->peg[row+2][column+2] = NO_PEG_PRESENT; - new_state->num_possible_moves = num_moves(new_state); - new_state->num_moves_tried = 0; - snprintf(new_state->description,50,"Peg (%d,%d) moves Diagonal Up R->L",(row+2),(column+2)); - } - } - } - //Horizontal, l->r - if (column >= 2) + continue; + } + for (int d = 0; d < NUM_DIRECTIONS; d++) + { + if (!is_valid_jump(current_state, row, column, &DIRECTIONS[d])) { - if ((PEG_PRESENT == current_state->peg[row][column-1]) && \ - (PEG_PRESENT == current_state->peg[row][column-2])) - { - result++; - if ((move + 1) == result) - { - memcpy(new_state, current_state, sizeof(GAME_STATE)); - new_state->peg[row][column] = PEG_PRESENT; - new_state->peg[row][column-1] = NO_PEG_PRESENT; - new_state->peg[row][column-2] = NO_PEG_PRESENT; - new_state->num_possible_moves = num_moves(new_state); - new_state->num_moves_tried = 0; - snprintf(new_state->description,50,"Peg (%d,%d) moves Horizontal L->R",(row),(column-2)); - } - } + continue; } - //Horizontal, r->l - if (column <= 2) + if (found == move) { - if ((PEG_PRESENT == current_state->peg[row][column+1]) && \ - (PEG_PRESENT == current_state->peg[row][column+2])) - { - result++; - if ((move + 1) == result) - { - memcpy(new_state, current_state, sizeof(GAME_STATE)); - new_state->peg[row][column] = PEG_PRESENT; - new_state->peg[row][column+1] = NO_PEG_PRESENT; - new_state->peg[row][column+2] = NO_PEG_PRESENT; - new_state->num_possible_moves = num_moves(new_state); - new_state->num_moves_tried = 0; - snprintf(new_state->description,50,"Peg (%d,%d) moves Horizontal R->L",(row),(column+2)); - } - } + apply_move(new_state, current_state, row, column, &DIRECTIONS[d]); + return true; } + found++; } } } - - return result; + + // Should be unreachable given the bounds check above. + return false; } static void printf_board(const GAME_STATE* state) diff --git a/triangle.h b/triangle.h index 6d17915..60f1f6d 100644 --- a/triangle.h +++ b/triangle.h @@ -34,7 +34,8 @@ #define WINNING_STATE 0 #define NOT_WINNING_STATE 1 -#define MAX_MOVES 14 //Each game starts with 14 pegs; 13 good moves will result in 1 peg left, so 14 states total (including initial) +#define BOARD_SIZE 5 // Triangle has 5 rows: row r has r+1 cells (15 cells total) +#define MAX_MOVES 14 // 14 pegs initially; 13 jumps to reach 1 peg, so 14 board states (including initial) /* Game Status Structure */ #pragma pack(push, 1) //do this to avoid byte boundary nonsense