added some functions to ms_game.c
This commit is contained in:
parent
5e22e2d056
commit
e3a4919714
1 changed files with 449 additions and 0 deletions
449
ms_game.c
449
ms_game.c
|
|
@ -10,11 +10,460 @@
|
||||||
* ----------------------------------------------------------
|
* ----------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "ms_game.h"
|
||||||
|
#include "ms_board.h"
|
||||||
|
#include "ms_cell.h"
|
||||||
|
#include "ms_ui_utils.h"
|
||||||
|
|
||||||
/** Encapsulate a cell and its board coordinates */
|
/** Encapsulate a cell and its board coordinates */
|
||||||
|
struct MsCellCoord {
|
||||||
|
MsCell cell;
|
||||||
|
ColAddr col;
|
||||||
|
RowAddr row;
|
||||||
|
};
|
||||||
|
|
||||||
/** The only instance of cell data */
|
/** The only instance of cell data */
|
||||||
|
static struct MsCellCoord cell_coords[MAX_BOARD_SIZE * MAX_BOARD_SIZE] = {0};
|
||||||
|
|
||||||
/** The struct holding the data of the game */
|
/** The struct holding the data of the game */
|
||||||
|
struct MsGameData {
|
||||||
|
MsBoard board;
|
||||||
|
GameState state;
|
||||||
|
};
|
||||||
|
|
||||||
/** The instance of the game */
|
/** The instance of the game */
|
||||||
|
static MsGame the_game = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a new game using the given mode. If a mode with a
|
||||||
|
* predefined configuration is used, this function shall actually start
|
||||||
|
* the game with that configuration using function `msg_start_game`.
|
||||||
|
* Otherwise, the user shall be prompted for required attributes
|
||||||
|
* (board size, mines) and the game is started afterwards.
|
||||||
|
*
|
||||||
|
* @param mode The configuration mode of the game to start.
|
||||||
|
* @return The game if if could be started with a predefined configuration,
|
||||||
|
* or 0 if it was not started because a custom configuration is required.
|
||||||
|
*/
|
||||||
|
MsGame msg_start_configured_game(GameMode mode) {
|
||||||
|
if (the_game != 0) {
|
||||||
|
the_game->state = INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
Count cols = 0;
|
||||||
|
Count rows = 0;
|
||||||
|
Count mines = 0;
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case BEGINNER:
|
||||||
|
cols = 9;
|
||||||
|
rows = 9;
|
||||||
|
mines = 10;
|
||||||
|
break;
|
||||||
|
case ADVANCED:
|
||||||
|
cols = 16;
|
||||||
|
rows = 16;
|
||||||
|
mines = 40;
|
||||||
|
break;
|
||||||
|
case EXPERT:
|
||||||
|
cols = 30;
|
||||||
|
rows = 16;
|
||||||
|
mines = 99;
|
||||||
|
break;
|
||||||
|
case CUSTOM:
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg_start_game(cols, rows, mines);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a new game, regardless of the state of the current game.
|
||||||
|
* Therefore, completed games or games in progress are aborted
|
||||||
|
* and a new game is 'restarted'.
|
||||||
|
*
|
||||||
|
* Note: Mines are distributed when the first cell is uncovered
|
||||||
|
* to ensure to uncover an empty cell.
|
||||||
|
*
|
||||||
|
* @param column_count The number of columns to use on the game board.
|
||||||
|
* @param row_count The number of rows to use on the game board.
|
||||||
|
* @param mine_count The number of mines distributed on the board.
|
||||||
|
* It must be greater than 0;
|
||||||
|
* @return The started game or 0 in case errors.
|
||||||
|
*/
|
||||||
|
MsGame msg_start_game(Count column_count, Count row_count, Count mine_count) {
|
||||||
|
if (the_game != 0) {
|
||||||
|
the_game->state = INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column_count == 0 || row_count == 0 || mine_count == 0 || column_count * row_count < mine_count + 9) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
the_game = (MsGame)calloc(1, sizeof(struct MsGameData));
|
||||||
|
the_game->board = msb_get_board();
|
||||||
|
msb_init_board(the_game->board, column_count, row_count);
|
||||||
|
the_game->state = IN_PROGRESS;
|
||||||
|
|
||||||
|
for (CellIdx row = 0; row < row_count; row++) {
|
||||||
|
for (CellIdx col = 0; col < column_count; col++) {
|
||||||
|
cell_coords[row * column_count + col].cell = msb_get_cell(the_game->board, col, row);
|
||||||
|
cell_coords[row * column_count + col].col = msu_idx_to_col_address(col);
|
||||||
|
cell_coords[row * column_count + col].row = msu_idx_to_row_address(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return the_game;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not the given game is valid.
|
||||||
|
* A game is NOT valid, if:
|
||||||
|
* + it is 0
|
||||||
|
* + or if it has an invalid board
|
||||||
|
* + or if it has no mines
|
||||||
|
* + or if it has less than 10 cells
|
||||||
|
* + or if it has more mines than cells - 9.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return True if the given game is valid, false otherwise.
|
||||||
|
*/
|
||||||
|
bool msg_is_valid(MsGame game) {
|
||||||
|
if (game == 0 || !msb_is_valid(game->board)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Count cols = msb_get_column_count(game->board);
|
||||||
|
Count rows = msb_get_row_count(game->board);
|
||||||
|
Count mines = 0;
|
||||||
|
|
||||||
|
bool any_uncovered = false;
|
||||||
|
for (CellIdx row = 0; row < rows && !any_uncovered; row++) {
|
||||||
|
for (CellIdx col = 0; col < cols && !any_uncovered; col++) {
|
||||||
|
MsCell cell = msb_get_cell(game->board, col, row);
|
||||||
|
if (!msc_is_covered(cell)) {
|
||||||
|
any_uncovered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (any_uncovered) {
|
||||||
|
Count mines = 0;
|
||||||
|
for (CellIdx row = 0; row < rows; row++) {
|
||||||
|
for (CellIdx col = 0; col < cols; col++) {
|
||||||
|
if (msc_has_mine(msb_get_cell(game->board, col, row))) {
|
||||||
|
mines++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cols * rows >= 10 && mines > 0 && mines < cols * rows - 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cols * rows >= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the addressed cell without performing any action on that cell. Only
|
||||||
|
* covered cells can be selected. The function clears any previous selection
|
||||||
|
* and selects the addressed cell. Therefore invalid addresses or already uncovered cells
|
||||||
|
* has only the effect of clearing a previous selection.
|
||||||
|
*
|
||||||
|
* To avoid uncovering unwanted cells due to wrong user inputs, the cell to uncover
|
||||||
|
* is selected in the first step. Only the selected cell can be uncovered.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @param column The column of the cell to select in the format used on the UI (e.g. 'A', 'B').
|
||||||
|
* @param row The row of the cell to select in the format used on the UI (e.g. 1, 2).
|
||||||
|
* @return True if the addressed cell is selected, false otherwise.
|
||||||
|
*/
|
||||||
|
bool msg_select_cell(MsGame game, ColAddr column, RowAddr row) {
|
||||||
|
if (!msg_is_valid(game)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Count cols = msb_get_column_count(game->board);
|
||||||
|
Count rows = msb_get_row_count(game->board);
|
||||||
|
CellIdx col_idx = msu_col_address_to_index(column);
|
||||||
|
CellIdx row_idx = msu_row_address_to_index(row);
|
||||||
|
|
||||||
|
if (col_idx >= cols || row_idx >= rows) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MsCell cell = msb_get_cell(game->board, col_idx, row_idx);
|
||||||
|
if (!msc_is_covered(cell)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CellIdx r = 0; r < rows; r++) {
|
||||||
|
for (CellIdx c = 0; c < cols; c++) {
|
||||||
|
cell_coords[r * cols + c].cell = 0;
|
||||||
|
cell_coords[r * cols + c].col = 0;
|
||||||
|
cell_coords[r * cols + c].row = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cell_coords[row_idx * cols + col_idx].cell = cell;
|
||||||
|
cell_coords[row_idx * cols + col_idx].col = column;
|
||||||
|
cell_coords[row_idx * cols + col_idx].row = row;
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the currently selected cell.
|
||||||
|
*
|
||||||
|
* @return The currently selected cell or 0,
|
||||||
|
* if no cell is selected.
|
||||||
|
*/
|
||||||
|
MsCell msg_get_selected_cell(MsGame game) {
|
||||||
|
if (!msg_is_valid(game)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Count cols = msb_get_column_count(game->board);
|
||||||
|
Count rows = msb_get_row_count(game->board);
|
||||||
|
|
||||||
|
for (CellIdx r = 0; r < rows; r++) {
|
||||||
|
for (CellIdx c = 0; c < cols; c++) {
|
||||||
|
if (cell_coords[r * cols + c].cell != 0) {
|
||||||
|
return cell_coords[r * cols + c].cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uncovers the currently selected cell.
|
||||||
|
* To avoid uncovering unwanted cells due to wrong user inputs, the cell to uncover
|
||||||
|
* must be selected in the first step via function ´msg_select_cell´.
|
||||||
|
*
|
||||||
|
* Only the selected cell can be uncovered, otherwise the invocation is ignored.
|
||||||
|
* No cell is selected afterwards.
|
||||||
|
*
|
||||||
|
* Mines are randomly distributed on the board when the first cell of a game is uncovered.
|
||||||
|
* Mines shall be distributed in a way that keeps the first uncovered cell empty.
|
||||||
|
* Depending on the size of the board and the number of mines, the randomized mine distribution
|
||||||
|
* may take long to find a proper cell for all mines. Therefore mine distribution shall be
|
||||||
|
* aborted after 10 X mine-count trials. The number of mines on the board need to be updated
|
||||||
|
* accordingly, if not all mines could be dropped.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
*/
|
||||||
|
void msg_uncover_selected_cell(MsGame game) {
|
||||||
|
if (!msg_is_valid(game)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MsCell cell = msg_get_selected_cell(game);
|
||||||
|
if (cell == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!msc_uncover(cell)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Count cols = msb_get_column_count(game->board);
|
||||||
|
Count rows = msb_get_row_count(game->board);
|
||||||
|
Count mines = 0;
|
||||||
|
|
||||||
|
for (CellIdx r = 0; r < rows; r++) {
|
||||||
|
for (CellIdx c = 0; c < cols; c++) {
|
||||||
|
if (msc_has_mine(msb_get_cell(game->board, c, r))) {
|
||||||
|
mines++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mines == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CellIdx r = 0; r < rows; r++) {
|
||||||
|
for (CellIdx c = 0; c < cols; c++) {
|
||||||
|
MsCell current = msb_get_cell(game->board, c, r);
|
||||||
|
if (msc_has_mine(current)) {
|
||||||
|
msc_drop_mine(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the marker on the addressed cell. To clear the marker, the marker ´NONE´ is used.
|
||||||
|
* The cell to mark must be selected in the first step via function ´msg_select_cell´.
|
||||||
|
* Only the selected cell can be marked, otherwise the invocation is ignored.
|
||||||
|
* No cell is selected afterwards.
|
||||||
|
*
|
||||||
|
* Note: The number of set mine-detected markers must NOT be larger than the number
|
||||||
|
* of mines on the board.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @param marker The marker to apply.
|
||||||
|
*/
|
||||||
|
void msg_mark_selected_cell(MsGame game, CellMarker marker) {
|
||||||
|
if (!msg_is_valid(game)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MsCell cell = msg_get_selected_cell(game);
|
||||||
|
if (cell == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (marker == MINE_DETECTED) {
|
||||||
|
Count mines = msg_get_mines_left_count(game);
|
||||||
|
if (mines == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msc_set_marker(cell, marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the state of the current game.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return The state of the current game.
|
||||||
|
*/
|
||||||
|
GameState msg_get_state(MsGame game) {
|
||||||
|
if (!msg_is_valid(game)) {
|
||||||
|
return INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
Count cols = msb_get_column_count(game->board);
|
||||||
|
Count rows = msb_get_row_count(game->board);
|
||||||
|
Count mines = 0;
|
||||||
|
Count uncovered = 0;
|
||||||
|
|
||||||
|
for (CellIdx r = 0; r < rows; r++) {
|
||||||
|
for (CellIdx c = 0; c < cols; c++) {
|
||||||
|
MsCell cell = msb_get_cell(game->board, c, r);
|
||||||
|
if (msc_has_mine(cell) && msc_get_marker(cell) == MINE_DETECTED) {
|
||||||
|
mines++;
|
||||||
|
}
|
||||||
|
if (!msc_is_covered(cell)) {
|
||||||
|
uncovered++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uncovered == cols * rows) {
|
||||||
|
return SOLVED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mines > 0) {
|
||||||
|
return FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IN_PROGRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the number of mines that are detected.
|
||||||
|
* This is the difference of total number of mines the board carries
|
||||||
|
* minus the number of cells being marked as 'detected'.
|
||||||
|
* Cells marked as 'suspected' does not contribute to this value.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return The number of undetected mines
|
||||||
|
* or 0, if the game is not valid.
|
||||||
|
*/
|
||||||
|
Count msg_get_mines_left_count(MsGame game) {
|
||||||
|
if (!msg_is_valid(game)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Count cols = msb_get_column_count(game->board);
|
||||||
|
Count rows = msb_get_row_count(game->board);
|
||||||
|
Count mines = 0;
|
||||||
|
|
||||||
|
for (CellIdx r = 0; r < rows; r++) {
|
||||||
|
for (CellIdx c = 0; c < cols; c++) {
|
||||||
|
MsCell cell = msb_get_cell(game->board, c, r);
|
||||||
|
if (msc_has_mine(cell) && msc_get_marker(cell) == MINE_DETECTED) {
|
||||||
|
mines++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the number of cells that are suspected carrying mines.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return The number of suspected mines
|
||||||
|
* or 0, if the game is not valid.
|
||||||
|
*/
|
||||||
|
Count msg_get_mines_suspected_count(MsGame game) {
|
||||||
|
if (!msg_is_valid(game)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Count cols = msb_get_column_count(game->board);
|
||||||
|
Count rows = msb_get_row_count(game->board);
|
||||||
|
Count mines = 0;
|
||||||
|
|
||||||
|
for (CellIdx r = 0; r < rows; r++) {
|
||||||
|
for (CellIdx c = 0; c < cols; c++) {
|
||||||
|
MsCell cell = msb_get_cell(game->board, c, r);
|
||||||
|
if (msc_has_mine(cell) && msc_get_marker(cell) == MINE_SUSPECTED) {
|
||||||
|
mines++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the number of times a cell was actively uncovered by the user.
|
||||||
|
* Note: This is not (necessarily) equal to the number of uncovered cells,
|
||||||
|
* because uncovering empty cells uncovers more than one cell. Such an
|
||||||
|
* activity is counted as 'one action' only.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return The number of times the used uncovered a cell
|
||||||
|
* or 0, if the game is not valid.
|
||||||
|
*/
|
||||||
|
Count msg_get_uncover_action_count(MsGame game) {
|
||||||
|
if (!msg_is_valid(game)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Count cols = msb_get_column_count(game->board);
|
||||||
|
Count rows = msb_get_row_count(game->board);
|
||||||
|
Count actions = 0;
|
||||||
|
|
||||||
|
for (CellIdx r = 0; r < rows; r++) {
|
||||||
|
for (CellIdx c = 0; c < cols; c++) {
|
||||||
|
MsCell cell = msb_get_cell(game->board, c, r);
|
||||||
|
if (!msc_is_covered(cell)) {
|
||||||
|
actions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to the underlying game board. This function is intended
|
||||||
|
* for being used by game visualizer but should not be needed by the game
|
||||||
|
* application.
|
||||||
|
*
|
||||||
|
* @param game The game instance in focus.
|
||||||
|
* @return The board used by the game instance or 0, if the game is invalid.
|
||||||
|
*/
|
||||||
|
MsBoard msg_get_board(MsGame game) {
|
||||||
|
return msg_is_valid(game) ? game->board : 0;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue