diff --git a/ms_board.c b/ms_board.c index 02a2dad..cfd8b0f 100644 --- a/ms_board.c +++ b/ms_board.c @@ -13,6 +13,7 @@ #include "ms_board.h" #include "config.h" +#include struct MsBoardData { Count column_count; @@ -31,7 +32,7 @@ static MsBoard the_board = 0; */ MsBoard msb_get_board() { if (the_board == 0) { - the_board = (MsBoard)GC_MALLOC(sizeof(struct MsBoardData)); + the_board = (MsBoard)calloc(1, sizeof(struct MsBoardData)); } return the_board; } @@ -54,6 +55,8 @@ void msb_init_board(MsBoard board, Count column_count, Count row_count) { board->column_count = column_count > MAX_BOARD_SIZE ? MAX_BOARD_SIZE : column_count; board->row_count = row_count > MAX_BOARD_SIZE ? MAX_BOARD_SIZE : row_count; + msc_reset_cell_factory(); + for (int row = 0; row < board->row_count; row++) { for (int col = 0; col < board->column_count; col++) { board->cells[col][row] = msc_produce_cell(); diff --git a/ms_cell.c b/ms_cell.c index 0a30241..98c69d1 100644 --- a/ms_cell.c +++ b/ms_cell.c @@ -12,8 +12,169 @@ #define CELL_COUNT MAX_BOARD_SIZE * MAX_BOARD_SIZE +#include +#include "ms_cell.h" + /** Implementation of cell data */ +struct MsCellData { + CellIdx idx; + bool covered; + CellMarker marker; + bool mine; + Count dangerous_neighbor_count; +}; /** Maximum amount of cell instances */ +static MsCell cells[CELL_COUNT] = {0}; /** The number of produced cells */ +static Count produced_cells = 0; + +/** + * Provides another, yet unused instance, initialized Mine Sweeper cell + * The provided cell is covered, carries no marker, no mine, and has 0 dangerous neighbors. + * + * Up to `MAX_BOARD_SIZE * MAX_BOARD_SIZE` cells can be produced by subsequent + * calls of this function. + * + * @return The fresh cell instance or 0, if no more instance is available. + */ +MsCell msc_produce_cell() { + if (produced_cells >= CELL_COUNT) { + return 0; + } + + if (cells[produced_cells] == 0) { + cells[produced_cells] = (MsCell)calloc(1, sizeof(struct MsCellData)); + } + + MsCell cell = cells[produced_cells]; + cell->idx = produced_cells; + cell->covered = true; + cell->marker = NONE; + cell->mine = false; + cell->dangerous_neighbor_count = 0; + + produced_cells++; + return cell; +} + +/** + * Resets the internal cell factory. Function `msc_produce_cell` is capable + * to produce the full number of cell after this function is called. + */ +void msc_reset_cell_factory() { + produced_cells = 0; +} + +/** + * Determines whether or not the given cell is valid. + * A cell is NOT valid, if it is 0 or has a neighbor mine count + * greater than 8. + * + * @param cell The cell instance in focus. + * @return True if the given cell is valid, false otherwise. + */ +bool msc_is_valid(MsCell cell) { + return cell != 0 && cell->dangerous_neighbor_count <= 8; +} + +/** + * Determines whether or not the given cell is currently covered. + * + * @param cell The cell instance in focus. + * @return True if the given cell is covered, false otherwise. + */ +bool msc_is_covered(MsCell cell) { + return cell != 0 && cell->covered; +} + +/** + * Uncovers the given cell. + * If the cell carries a veritable marker (detected or suspected) + * or is already uncovered, the request is ignored. Otherwise + * function `msc_is_covered` returns `false` for this cell afterwards. + * + * Note: an uncovered cell cannot be covered again. + * + * @param cell The cell instance in focus. + * @return True if the cell was successfully uncovered, false otherwise. + */ +bool msc_uncover(MsCell cell) { + if (!msc_is_valid(cell) || !cell->covered || cell->marker != NONE) { + return false; + } + + cell->covered = false; + return true; +} + +/** + * Determines whether or not the given cell carries a mine. + * + * @param cell The cell instance in focus. + * @return True if the cell carries a mine, false otherwise. + */ +bool msc_has_mine(MsCell cell) { + return cell != 0 && cell->mine; +} + +/** + * Specifies that the given cell carries a mine. + * Invocations for invalid cells are ignored. + * + * @param cell The cell instance in focus. + */ +void msc_drop_mine(MsCell cell) { + if (msc_is_valid(cell)) { + cell->mine = true; + } +} + +/** + * Provides the number of direct neighbor cells, which carries a mine. + * + * @param cell The cell instance in focus. + * @return The number between 0 to 8 that encounters the neighbor cells + * that carries a mine or 255 if the cell is not valid. + */ +Byte msc_get_dangerous_neighbor_count(MsCell cell) { + return msc_is_valid(cell) ? cell->dangerous_neighbor_count : 0xFF; +} + +/** + * Increments (+1) the count of neighbor cells that carries a mine. + * Invocations for invalid cells are ignored. + * + * @param cell The cell instance in focus. + */ +void msc_inc_dangerous_neighbor_count(MsCell cell) { + if (msc_is_valid(cell)) { + cell->dangerous_neighbor_count++; + } +} + +/** + * Provides the given marker of the given cell. + * + * @param cell The cell instance in focus. + * @return The marker carried by the cell + * or `NONE`, if the cell is not valid. + */ +CellMarker msc_get_marker(MsCell cell) { + return msc_is_valid(cell) ? cell->marker : NONE; +} + +/** + * Applies the given marker to the given cell. The marker is cleared + * by applying a 'none' marker. + * Invocations for invalid cells are ignored. + * + * @param cell The cell instance in focus. + * @param marker The marker to apply. + */ +void msc_set_marker(MsCell cell, CellMarker marker) { + if (msc_is_valid(cell)) { + cell->marker = marker; + } +} diff --git a/ms_ui_utils.c b/ms_ui_utils.c index 850d925..ff6095c 100644 --- a/ms_ui_utils.c +++ b/ms_ui_utils.c @@ -23,5 +23,160 @@ CellIdx msu_get_random_index(Count upper_limit) { return (upper_limit == 0 ? 0 : rand() % (upper_limit)); } +/** + * Maps the given cell index to the corresponding column address. + * + * @param idx The index to map. + * @return ColAddr The corresponding column address or a '0' value, + * if the index cannot be mapped. + */ +ColAddr msu_idx_to_col_address(CellIdx idx) { + if (idx > 0 && idx <= MAX_BOARD_SIZE) { + return (ColAddr)idx; + } + return 0; +} +/** + * Maps the given cell index to the corresponding row address. + * + * @param idx The index to map. + * @return RowAddr The corresponding row address or a '0' value, + * if the index cannot be mapped. + */ +RowAddr msu_idx_to_row_address(CellIdx idx) { + if (idx > 0 && idx <= MAX_BOARD_SIZE) { + return (RowAddr)idx; + } + return 0; +} + +/** + * Maps the given column address to the corresponding cell index. + * + * @param addr The column address to map. + * @return CellIdx The corresponding cell index or 0, + * if the address cannot be mapped. + */ +CellIdx msu_col_address_to_index(ColAddr addr) { + if (addr > 0 && addr < MAX_BOARD_SIZE) { + return (CellIdx)addr; + } + return 0; +} + +/** + * Maps the given row address to the corresponding cell index. + * + * @param addr The row address to map. + * @return CellIdx The corresponding cell index or 0, + * if the address cannot be mapped. + */ +CellIdx msu_row_address_to_index(RowAddr addr) { + if (addr > 0 && addr < MAX_BOARD_SIZE) { + return (CellIdx)addr; + } + return 0; +} + +/** + * Provides the character a user may type to perform the given action. + * The key must be unique for each action. + * + * @param action The action for which the corresponding 'key' is requested. + * @return char The corresponding 'key' character or '\0', + * if the action cannot reasonably be mapped. + */ +char msu_get_action_char(Action action) { + switch (action) { + case MARK_MINE: + return 'm'; + case MARK_SUSPECT: + return 's'; + case CLEAR_MARKER: + return 'c'; + case UNCOVER: + return 'u'; + case QUIT_GAME: + return 'q'; + default: + return '\0'; + } +} + +/** + * Provides the action for the given 'key' character (the counterpart of ´msu_get_action_char´). + * + * @param key The key for the action. + * @return Action The action corresponding to the given 'key' + * or 'INVALID',if the key cannot be mapped. + */ +Action msu_get_action(char key) { + switch (key) { + case 'm': + return MARK_MINE; + case 's': + return MARK_SUSPECT; + case 'c': + return CLEAR_MARKER; + case 'u': + return UNCOVER; + case 'q': + return QUIT_GAME; + default: + return UNKNOWN; + } +} + +/** + * Maps the given cell marker to its presentation symbol + * (the character shown in the game visualization). + * + * @param marker The marker. + * @return char The corresponding character or '#', if + * the marker cannot be mapped. + */ +char msu_get_marker_symbol(CellMarker marker) { + switch (marker) { + case NONE: + return ' '; + case MINE_DETECTED: + return 'X'; + case MINE_SUSPECTED: + return '?'; + default: + return '#'; + } +} + +/** + * Provides the symbol that represents a mine + * (the character shown in the game visualization). + * + * @return char The symbol of a mine. + */ +char msu_get_mine_symbol() { + return '*'; +} + +/** + * Maps the given game status to a user presentable label. + * + * @param status The status to map. + * @return char* The text for the status or 0, if + * the status cannot be mapped. + */ +char* msu_get_status_label(GameState status) { + switch (status) { + case IN_PROGRESS: + return "In Progress"; + case SOLVED: + return "Solved"; + case FAILED: + return "Failed"; + case INVALID: + default: + return "Invalid"; + } +} diff --git a/ms_ui_utils.h b/ms_ui_utils.h index 0bce0f5..70fc369 100644 --- a/ms_ui_utils.h +++ b/ms_ui_utils.h @@ -15,6 +15,7 @@ #define ___MS_UI_UTILS_H #include "general.h" +#include "config.h" /** * Initializes the random number generator. It must be invoked @@ -67,15 +68,6 @@ CellIdx msu_col_address_to_index(ColAddr addr); */ CellIdx msu_row_address_to_index(RowAddr addr); -/** - * Maps the given column address to the corresponding cell index. - * - * @param addr The column address to map. - * @return CellIdx The corresponding cell index or 0, - * if the address cannot be mapped. - */ -CellIdx msu_col_address_to_index(ColAddr addr); - /** * Provides the character a user may type to perform the given action. * The key must be unique for each action.