diff --git a/P05_TicTacToe/tic-tac-toe/Makefile b/P05_TicTacToe/tic-tac-toe/Makefile new file mode 100644 index 0000000..a29984f --- /dev/null +++ b/P05_TicTacToe/tic-tac-toe/Makefile @@ -0,0 +1,9 @@ +SNP_SHARED_MAKEFILE := $(if $(SNP_SHARED_MAKEFILE),$(SNP_SHARED_MAKEFILE),"~/snp/shared.mk") + +TARGET := bin/tic-tac-toe +MODULES := src/model.c src/view.c src/control.c +SOURCES := src/main.c $(MODULES) +TSTSOURCES := tests/tests.c $(MODULES) + +include $(SNP_SHARED_MAKEFILE) + diff --git a/P05_TicTacToe/tic-tac-toe/mainpage.dox b/P05_TicTacToe/tic-tac-toe/mainpage.dox new file mode 100644 index 0000000..b07e65d --- /dev/null +++ b/P05_TicTacToe/tic-tac-toe/mainpage.dox @@ -0,0 +1,8 @@ +/** + * @mainpage SNP - P05 Tic Tac Toe Game + * + * @section Purpose + * + * This is a lab on usage of arrays. + * + */ diff --git a/P05_TicTacToe/tic-tac-toe/src/control.c b/P05_TicTacToe/tic-tac-toe/src/control.c new file mode 100644 index 0000000..ccae643 --- /dev/null +++ b/P05_TicTacToe/tic-tac-toe/src/control.c @@ -0,0 +1,154 @@ +/** + * @file + * @brief Implementation + */ +#include "control.h" +#include "model.h" +#include + +/** + * @brief Conversion from control field number (1...9) to 0-based model position (row, col). + * @param cell [IN] Field number (1...9). + * @return Returns the position of the field. + * @remark Asserts proper field range. + */ +static model_pos_t get_pos(size_t cell) +{ + assert(1 <= cell && cell <= 9); + model_pos_t pos = { (cell - 1) / 3, (cell - 1) % 3 }; + return pos; +} + +/** + * @brief Conversion from control player to model state. + * @param player [IN] Control player value to convert. + * @return Returns the matching model state. + * @remark No assertion is done - defaults to model_state_none. + */ +static model_state_t get_state(control_player_t player) +{ + switch(player) { + case control_player_a: return model_state_a; + case control_player_b: return model_state_b; + default: return model_state_none; + } +} + +/** + * @brief Conversion from 0-based model position (row, col) to control field number (1...9). + * @param pos [IN] 0-based model position (row,col). + * @return The control filed number (1...9) + * @remark Asserts proper position range. + */ +static size_t get_cell(model_pos_t pos) +{ + assert(pos.row < 3); + assert(pos.col < 3); + return 1 + pos.row * 3 + pos.col; +} + +/** + * @brief Conversion from model state to control player. + * @param state [IN] Model state to convert + * @return Returns the matching control player value. + * @remark No assertion is done - defaults to control_no_player. + */ +static control_player_t get_player(model_state_t state) +{ + switch(state) { + case model_state_a: return control_player_a; + case model_state_b: return control_player_b; + default: return control_no_player; + } +} + +/** + * @brief Queries if a move is possible. + * @param instance [INOUT] The instance which holds the state. + * @return Returns 0 if no move is possible any more, otherwise 1. + */ +static int control_can_move(control_t *instance) +{ + assert(instance); + return model_can_move(instance->model); +} + +// public API function which is documented in the header file. +void control_init(control_t *instance, model_t *model) +{ + assert(instance); + assert(model); + instance->player = control_player_a; + instance->model = model; +} + +// public API function which is documented in the header file. +void control_move(control_t *instance, size_t cell) +{ + assert(instance); + if (model_move(instance->model, get_pos(cell), get_state(instance->player))) { + if (control_can_move(instance)) { + switch(instance->player) { + case control_player_a: + instance->player = control_player_b; + break; + case control_player_b: + instance->player = control_player_a; + break; + default: + break; + } + } else { + instance->player = control_no_player; + } + } +} + +// public API function which is documented in the header file. +control_player_t control_get_winner(control_t *instance) +{ + assert(instance); + return get_player(model_get_winner(instance->model)); +} + +// public API function which is documented in the header file. +control_player_t control_get_player(control_t *instance) +{ + assert(instance); + return instance->player; +} + +// public API function which is documented in the header file. +control_player_t control_get_state(control_t *instance, size_t cell) +{ + assert(instance); + return get_player(model_get_state(instance->model, get_pos(cell))); +} + +// public API function which is documented in the header file. +control_line_t control_get_win(control_t *instance) +{ + assert(instance); + if (control_get_winner(instance) == control_no_player) { + control_line_t no_win = { 0 }; + return no_win; + } + + model_line_t line = model_get_win_line(instance->model); + assert(line.dir != model_dir_none); + size_t start_cell = get_cell(line.start); + switch(line.dir) { + case model_dir_h: + return (control_line_t) { { start_cell, start_cell + 1, start_cell + 2 } }; + case model_dir_v: + return (control_line_t) { { start_cell, start_cell + 3, start_cell + 6 } }; + case model_dir_d: + if (start_cell == 1) { + return (control_line_t) { { start_cell, start_cell + 4, start_cell + 8 } }; + } else { + return (control_line_t) { { start_cell, start_cell + 2, start_cell + 6 } }; + } + default: + return (control_line_t) { { 1, 1, 1 } }; + } +} diff --git a/P05_TicTacToe/tic-tac-toe/src/control.h b/P05_TicTacToe/tic-tac-toe/src/control.h new file mode 100644 index 0000000..613bd8f --- /dev/null +++ b/P05_TicTacToe/tic-tac-toe/src/control.h @@ -0,0 +1,80 @@ +/** + * @file + * @brief MVC - agent between model and view + */ +#ifndef _CONTROL_H_ +#define _CONTROL_H_ + +#include "model.h" + +/** + * @brief The selection of possible players. + */ +typedef enum { + control_no_player, ///< none of the players + control_player_a, ///< first player + control_player_b, ///< second player +} control_player_t; + +/** + * @brief Sequence of winning cell numbers in increasing cell numbers. + */ +typedef struct { + size_t line[3]; ///< the sequence of cells (1...9) or 0 in the first element if no win +} control_line_t; + +/** + * @brief The instance type. + */ +typedef struct { + control_player_t player; ///< the current player + model_t *model; ///< the reference to the model +} control_t; + +/** + * @brief Constructor: initialize the instance memory. + * @param instance [INOUT] The instance which holds the state. + * @param model [IN] Dependency Injection of the model instance. + */ +void control_init(control_t *instance, model_t *model); + +/** + * @brief Performs a move on the board. + * @param instance [INOUT] The instance which holds the state. + * @param cell [IN] The affected field (1...9) + * @remark Silently ignores a move if it is not allowed (e.g. if already completed or the field is already played, etc.). + */ +void control_move(control_t *instance, size_t cell); + +/** + * @brief Queries the winning player. + * @param instance [INOUT] The instance which holds the state. + * @returns Returns the winning player (if any). + */ +control_player_t control_get_winner(control_t *instance); + +/** + * @brief Queries the next player. + * @param instance [INOUT] The instance which holds the state. + * @returns Returns the next player (if any). + * @remark This is updated by the control_move() function. + */ +control_player_t control_get_player(control_t *instance); + +/** + * @brief Queries the state of a field. + * @param instance [INOUT] The instance which holds the state. + * @param cell [IN] The affected field of the board (1...9). + * @returns Returns the player which played this field (if any). + */ +control_player_t control_get_state(control_t *instance, size_t cell); + +/** + * @brief Gets the winning fields (if any). + * @param instance [INOUT] The instance which holds the state. + * @returns Returns the field numbers in increasing order (1...9) which win the game (if any). + * @remark If there is no winner (yet), the first entry in the result is 0. + */ +control_line_t control_get_win(control_t *instance); + +#endif // _CONTROL_H_ diff --git a/P05_TicTacToe/tic-tac-toe/src/main.c b/P05_TicTacToe/tic-tac-toe/src/main.c new file mode 100644 index 0000000..7e66b7e --- /dev/null +++ b/P05_TicTacToe/tic-tac-toe/src/main.c @@ -0,0 +1,39 @@ + /* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Lab P04 dep2dot + */ +#include +#include + +#include "view.h" +#include "model.h" +#include "control.h" + +/** + * @brief main function + * @param argc [in] number of entries in argv + * @param argv [in] program name plus command line arguments + * @returns returns success if valid date is given, failure otherwise + */ +int main(int argc, const char *argv[]) +{ + view_t view; + control_t control; + model_t model; + + model_init(&model); + control_init(&control, &model); + view_init(&view, &control); + view_run(&view); + + return EXIT_SUCCESS; +} diff --git a/P05_TicTacToe/tic-tac-toe/src/model.c b/P05_TicTacToe/tic-tac-toe/src/model.c new file mode 100644 index 0000000..fd673bc --- /dev/null +++ b/P05_TicTacToe/tic-tac-toe/src/model.c @@ -0,0 +1,165 @@ +/** + * @file + * @brief Implementation + */ +#include "model.h" +#include + +/** + * @brief Asserts that the position is in range. + * @param [IN] The position to check. + */ +static void assert_pos(model_pos_t pos) +{ + assert(pos.row < MODEL_SIZE); + assert(pos.col < MODEL_SIZE); +} + +/** + * @brief Sets the field on the board to the given state. + * @param instance [INOUT] The instance which holds the state. + * @param pos [IN] The affected field. + * @param state [IN] The new state of the field. + */ +static void set_state(model_t *instance, model_pos_t pos, model_state_t state) +{ + assert_pos(pos); + + // Instructions to the students: + // set the field of the board to the new state + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + + + // END-STUDENTS-TO-ADD-CODE +} + +// public API function which is documented in the header file. +model_pos_t model_pos(size_t row, size_t col) +{ + return (model_pos_t){row, col}; +} + +// public API function which is documented in the header file. +void model_init(model_t *instance) +{ + assert(instance); + + // Instructions to the students: + // set all fields of the board to model_state_none + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + + + // END-STUDENTS-TO-ADD-CODE +} + +// public API function which is documented in the header file. +model_state_t model_get_state(model_t *instance, model_pos_t pos) +{ + assert(instance); + assert_pos(pos); + + // Instructions to the students: + // replace the stub implementation my access to the field at the given position. + // BEGIN-STUDENTS-TO-ADD-CODE + + + return model_state_none; // stub + + + // END-STUDENTS-TO-ADD-CODE +} + +// public API function which is documented in the header file. +model_line_t model_get_win_line(model_t *instance) +{ + assert(instance); + model_state_t anchor; + + // horizontal + for(size_t row = 0; row < MODEL_SIZE; row++) { + anchor = model_get_state(instance, model_pos(row, 0)); + if (anchor != model_state_none + && anchor == model_get_state(instance, model_pos(row, 1)) + && anchor == model_get_state(instance, model_pos(row, 2))) { + return (model_line_t) { model_dir_h, { row, 0 } }; + } + } + + // vertical + for(size_t col = 0; col < MODEL_SIZE; col++) { + anchor = model_get_state(instance, model_pos(0, col)); + if (anchor != model_state_none + && anchor == model_get_state(instance, model_pos(1, col)) + && anchor == model_get_state(instance, model_pos(2, col))) { + return (model_line_t) { model_dir_v, { 0, col } }; + } + + + } + + // diagonal + anchor = model_get_state(instance, model_pos(1, 1)); + if (anchor != model_state_none) { + if (anchor == model_get_state(instance, model_pos(0, 0)) && anchor == model_get_state(instance, model_pos(2, 2))) { + return (model_line_t) { model_dir_d, { 0, 0 } }; + } + if (anchor == model_get_state(instance, model_pos(2, 0)) && anchor == model_get_state(instance, model_pos(0, 2))) { + return (model_line_t) { model_dir_d, { 0, 2 } }; + } + } + + // fallback + return (model_line_t) { model_dir_none, { 0, 0 } }; +} + +// public API function which is documented in the header file. +model_state_t model_get_winner(model_t *instance) +{ + assert(instance); + model_line_t line = model_get_win_line(instance); + return line.dir == model_dir_none + ? model_state_none + : model_get_state(instance, model_pos(line.start.row, line.start.col)) + ; +} + +// public API function which is documented in the header file. +int model_can_move(model_t *instance) +{ + assert(instance); + if (model_get_winner(instance) == model_state_none) { + // Instructions to the students: + // scan all fields: return 1 with first field which equals model_state_none + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + + + + + // END-STUDENTS-TO-ADD-CODE + } + return 0; +} + +// public API function which is documented in the header file. +int model_move(model_t *instance, model_pos_t pos, model_state_t state) +{ + assert(instance); + assert_pos(pos); + if (model_get_state(instance, pos) == model_state_none && model_can_move(instance)) { + set_state(instance, pos, state); + return 1; + } + return 0; +} diff --git a/P05_TicTacToe/tic-tac-toe/src/model.h b/P05_TicTacToe/tic-tac-toe/src/model.h new file mode 100644 index 0000000..51e2678 --- /dev/null +++ b/P05_TicTacToe/tic-tac-toe/src/model.h @@ -0,0 +1,108 @@ +/** + * @file + * @brief MVC - Model instance + */ +#ifndef _MODEL_H_ +#define _MODEL_H_ + +#include + +#define MODEL_SIZE 3 ///< size of the game to avoid magic numbers in the code (not meant to modify) + +/** + * @brief The position on the board. + */ +typedef struct { + size_t row; ///< The row (0-based). + size_t col; ///< The column (0-based). +} model_pos_t; + +/** + * @brief Winner line direction - the winner line is given together with the start position. + */ +typedef enum { + model_dir_none, ///< no winner line + model_dir_h, ///< horizontal + model_dir_v, ///< vertical + model_dir_d, ///< diagonal +} model_dir_t; + +/** + * @brief The Winner line (if any). + */ +typedef struct { + model_dir_t dir; ///< the winner line direction (if any) + model_pos_t start; ///< the start position of the winner line +} model_line_t; + +/** + * @brief The state of a field. + */ +typedef enum { + model_state_none, ///< field available to play + model_state_a, ///< field already played + model_state_b, ///< field already played +} model_state_t; + +/** + * @brief The instance type. + */ +typedef struct { + model_state_t board[MODEL_SIZE][MODEL_SIZE]; ///< the play board +} model_t; + +/** + * @brief Convert to row and col to position. + * @param row [IN] position parameter + * @param col [IN] position parameter + * @return Returns the position given be row and col parameters. + */ +model_pos_t model_pos(size_t row, size_t col); + +/** + * @brief Constructor: initialize the instance memory. + * @param instance [INOUT] The instance which holds the state. + */ +void model_init(model_t *instance); + +/** + * @brief Queries the state of the given field. + * @param instance [INOUT] The instance which holds the state. + * @param pos [IN] The affected field. + * @return Returns the state of the field. + */ +model_state_t model_get_state(model_t *instance, model_pos_t pos); + +/** + * @brief Queries the winner (if any). + * @param instance [INOUT] The instance which holds the state. + * @return Returns the wining player or model_state_none if no winner (yet). + */ +model_state_t model_get_winner(model_t *instance); + +/** + * @brief Queries if a move is possible (i.e. not won yet and any field available?). + * @param instance [INOUT] The instance which holds the state. + * @return Returns 0 if no move possible, 1 otherwise. + */ +int model_can_move(model_t *instance); + +/** + * @brief Do a move if possible. + * @param instance [INOUT] The instance which holds the state. + * @param pos [IN] The field to play. + * @param state [IN] The new state (only model_state_a and model_state_b allowed). + * @return Returns if the attempt to move was successful. + * @remark Does only succeed if not yet won and if the field is available. + */ +int model_move(model_t *instance, model_pos_t pos, model_state_t state); + +/** + * @brief Gets the winner line (if any). + * @param instance [INOUT] The instance which holds the state. + * @returns The line which wins (if any). + * @remark The start position is 0/0, 1/0, 2/0 for horizontal, 0/0, 0/1, 0/2 for vertical, 0/0, 0/2 for diagonal. + */ +model_line_t model_get_win_line(model_t *instance); + +#endif // _MODEL_H_ diff --git a/P05_TicTacToe/tic-tac-toe/src/view.c b/P05_TicTacToe/tic-tac-toe/src/view.c new file mode 100644 index 0000000..a248c49 --- /dev/null +++ b/P05_TicTacToe/tic-tac-toe/src/view.c @@ -0,0 +1,255 @@ +/** + * @file + * @brief Implementation + */ +#include "view.h" +#include "control.h" + +#include // assert() +#include // various i/o +#include // isdigit() +#include // STDIN_FILENO, isatty() +#include // tcgetattr(), tcsetattr() + +#define EXIT '0' ///< the UI exit request + +#define CLS "\033[2J" ///< ANSI termial CSI sequence for clear screen +#define AVAILABLE "\033[40m" ///< ANSI termial CSI sequence for available fields (black) +#define PLAYER_A "\033[42m" ///< ANSI termial CSI sequence for one player (green) +#define PLAYER_B "\033[41m" ///< ANSI termial CSI sequence for the other player (red) +#define GAP "\033[47m" ///< ANSI termial CSI sequence for boarder (white) +#define RESET "\033[0m" ///< ANSI termial CSI sequence to reset all settings + +#define CELL_WIDTH 10 ///< rendering parameter: columns per cell +#define CELL_HEIGHT 5 ///< rendering parameter: rows per cell +#define GAP_WIDTH 4 ///< rendering parameter: columns per border +#define GAP_HEIGHT 2 ///< rendering parameter: rows per boarder + +#define SIZE 3 ///< size of the game to avoid magic numbers in the code (not meant to modify) +#define CELLS (SIZE * SIZE) ///< size of the game to avoid magic numbers in the code (not meant to modify) + +/** + * @brief Position the cursor for further output. + * @param row [IN] position parameter + * @param col [IN] position parameter + */ +static void goto_pos(size_t row, size_t col) +{ + printf("\033[%zd;%zdH", row, col); +} + +/** + * @brief Displays a sequence of spaces at the given position in the given background color. + * @param row [IN] position parameter + * @param col [IN] position parameter + * @param width [IN] how many spaces to write + * @param color [IN] the format string before writing the spaces (intent: background color) + * @remark After writing the spaces, the format is reset. + */ +static size_t show_bar(size_t row, size_t col, size_t width, const char* color) +{ + goto_pos(row, col); + printf("%s", color); + for(size_t col = 0; col < width; col++) { + putchar(' '); + } + printf(RESET); + return col + width; +} + +/** + * @brief Displays a horizontal border over the whole board width. + * @param row [IN] position parameter + * @param col [IN] position parameter + */ +static size_t show_h_gap(size_t row, size_t col) { + for(size_t i = 0; i < GAP_HEIGHT; i++) { + show_bar(row+i, col, GAP_WIDTH + CELL_WIDTH + GAP_WIDTH, GAP); + } + return row + GAP_HEIGHT; +} + +/** + * @brief Writes for the call at position y/x the given number with the given background color. + * @param y [IN] position parameter: the upper left row of the cell + * @param x [IN] position parameter: the upper left column of the cell + * @param n [IN] the number to write as text + * @param color [IN] the format string before writing the text (intent: background color) + * @remark After writing the number, the format is reset. + */ +static void show_cell_nr(size_t y, size_t x, size_t n, const char *color) +{ + size_t cy = (y + y + CELL_HEIGHT)/2; + size_t cx = (x + x + CELL_WIDTH - 2)/2; + + goto_pos(cy, cx); + printf("%s", color); + printf("%2zd", n); + printf(RESET); +} + +/** + * @brief Renders the given cell with the given background color, including the surrounding border. + * @param n [IN] the cell number (0...CELLS-1) + * @param color [IN] the format string for the cell content (intent: background color) + * @remark After writing the number, the format is reset. + */ +static void show_cell(size_t n, const char *color) +{ + // goto upper-left corner of a cell (the cell starts with an upper and left gap) + size_t y = 1 + n / SIZE * (GAP_HEIGHT + CELL_HEIGHT); + size_t x = 1 + n % SIZE * (GAP_WIDTH + CELL_WIDTH); + + size_t row = show_h_gap(y, x); + for(size_t i = 0; i < CELL_HEIGHT; i++) { + size_t col = x; + col = show_bar(row, col, GAP_WIDTH, GAP); + col = show_bar(row, col, CELL_WIDTH, color); + col = show_bar(row, col, GAP_WIDTH, GAP); + row++; + } + row = show_h_gap(row, x); + show_cell_nr(y + GAP_HEIGHT, x + GAP_WIDTH, n + 1, color); + goto_pos(row, 0); +} + +/** + * @brief Renders the given player's name in the given background color. + * @param player [IN] the player to render (select the background color and the name of the player + * @remark After writing the content, the format is reset. + */ +static void print_player(control_player_t player) +{ + switch(player) { + case control_player_a: + printf(PLAYER_A); + printf("Player A"); + printf(RESET); + break; + case control_player_b: + printf(PLAYER_B); + printf("Player B"); + printf(RESET); + break; + default: + printf(RESET); + printf("none"); + break; + } +} + +/** + * @brief Displays a label followed by the given player. + * @param row [IN] position parameter + * @param col [IN] position parameter + * @param label [IN] the profixing label + * @param player [IN] the player to display + */ +static void show_player(size_t row, size_t col, const char *label, control_player_t player) +{ + goto_pos(row, col); + printf(RESET); + col += printf("%s", label); + goto_pos(row, col); + print_player(player); +} + +/** + * @brief Renders the winner and the next player. + * @param winner [IN] the winning player (if any) + * @param next [IN] the next player (if any) + */ +static void show_status(control_player_t winner, control_player_t next) +{ + size_t y = GAP_HEIGHT; + size_t x = SIZE * (GAP_WIDTH + CELL_WIDTH) + GAP_WIDTH + GAP_WIDTH; + size_t row = y; + size_t col = x; + + show_player(row, col, "Winner is: ", winner); + row += 2; + show_player(row, col, "Next player is: ", next); + row += 2; + row += 2; + goto_pos(row, col); + printf("0: exit"); + row += 2; + goto_pos(row, col); + printf("1..9: play field"); +} + +/** + * @brief Renders the board from the status given by the control instance. + * @param instance [IN] the instance which holds the control instance + */ +static void show(view_t *instance) +{ + assert(instance); + assert(instance->control); + puts(CLS); + show_status(control_get_winner(instance->control), control_get_player(instance->control)); + + for(size_t i = 0; i < CELLS; i++) { + const char *color = AVAILABLE; + switch(control_get_state(instance->control, i+1)) { + case control_player_a: + color = PLAYER_A; + break; + case control_player_b: + color = PLAYER_B; + break; + default: + break; + } + show_cell(i, color); + } +} + +/** + * @brief Processes the input and dsiplays the result. + * @param the instance which holds the control instance + */ +static void notifier_loop(view_t *instance) +{ + show(instance); + int c = getchar(); + while(c != EOF && c != EXIT) { + if (isdigit(c)) { + control_move(instance->control, c-'0'); + } + show(instance); + c = getchar(); + } +} + + +// public API function which is documented in the header file. +void view_init(view_t *instance, control_t *control) +{ + assert(instance); + assert(control); + instance->control = control; +} + +// public API function which is documented in the header file. +void view_run(view_t *instance) +{ + if (isatty(STDIN_FILENO)) { // in case of an interactive terminal, the exhoing and buffering is disabled + // declare non POSIX function, which is available in glibc, but not in strinct C99 mode + void cfmakeraw(struct termios *termios_p); + + // replace original tty IO state... + struct termios orig; + struct termios raw; + cfmakeraw(&raw); + tcgetattr(STDIN_FILENO, &orig); + tcsetattr(STDIN_FILENO, TCSANOW, &raw); + // ...do the work... + notifier_loop(instance); + // ...and finalle restore original tty IO state + tcsetattr(STDIN_FILENO, TCSANOW, &orig); + } else { // if not an interactive terminal, no tweaking with IO is done + notifier_loop(instance); + } +} + diff --git a/P05_TicTacToe/tic-tac-toe/src/view.h b/P05_TicTacToe/tic-tac-toe/src/view.h new file mode 100644 index 0000000..84cee90 --- /dev/null +++ b/P05_TicTacToe/tic-tac-toe/src/view.h @@ -0,0 +1,31 @@ +/** + * @file + * @brief MVC - View instance + */ +#ifndef _VIEW_H_ +#define _VIEW_H_ + +#include "control.h" + +/** + * @brief The instance type. + */ +typedef struct { + control_t *control; ///< the instance knows of the control instance +} view_t; + +/** + * @brief Constructor: initialize the instance memory. + * @param instance [INOUT] The instance which holds the state. + * @param control [IN] Dependency Injection of the control instance. + */ +void view_init(view_t *instance, control_t *control); + +/** + * @brief Starts the notifyer loop: accepts input and displays the results. + * @param instance [INOUT] The instance which holds the state. + * @remark Does only return when termination is requested through the UI. + */ +void view_run(view_t *instance); + +#endif // _VIEW_H_ diff --git a/P05_TicTacToe/tic-tac-toe/tests/tests.c b/P05_TicTacToe/tic-tac-toe/tests/tests.c new file mode 100644 index 0000000..f2d15df --- /dev/null +++ b/P05_TicTacToe/tic-tac-toe/tests/tests.c @@ -0,0 +1,445 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Test suite for the given package. + */ +#include +#include +#include +#include +#include +#include +#include "test_utils.h" +#include "model.h" + +#ifndef TARGET // must be given by the make file --> see test target +#error missing TARGET define +#endif + +/// @brief alias for EXIT_SUCCESS +#define OK EXIT_SUCCESS +/// @brief alias for EXIT_FAILURE +#define FAIL EXIT_FAILURE + +/// @brief The name of the STDOUT text file. +#define OUTFILE "stdout.txt" +/// @brief The name of the STDERR text file. +#define ERRFILE "stderr.txt" + +#define TRACE_INDENT "\n " ///< allow for better stdout formatting in case of error + +// setup & cleanup +static int setup(void) +{ + remove_file_if_exists(OUTFILE); + remove_file_if_exists(ERRFILE); + return 0; // success +} + +static int teardown(void) +{ + // Do nothing. + // Especially: do not remove result files - they are removed in int setup(void) *before* running a test. + return 0; // success +} + +// test utils +static void init_model(model_t *instance, int act) +{ + if (act) printf(TRACE_INDENT "init_model:... "); + model_init(instance); + for(size_t row = 0; row < MODEL_SIZE; row++) { + for(size_t col = 0; col < MODEL_SIZE; col++) { + if (act) printf("%zd/%zd ", row, col); + CU_ASSERT_EQUAL_FATAL(instance->board[row][col], model_state_none); + } + } + if (act) printf(TRACE_INDENT); +} + +static void print_board(model_state_t board[MODEL_SIZE][MODEL_SIZE]) +{ + for(size_t row = 0; row < MODEL_SIZE; row++) { + printf("{ "); + for(size_t col = 0; col < MODEL_SIZE; col++) { + printf("%d ", board[row][col]); + } + printf("} "); + } +} + + +// tests +static void test_model_init(void) +{ + // check void model_init(model_t *instance); + + // arrange + model_t model; + // act & assert + init_model(&model, 1); +} + +static void test_model_get_state(void) +{ + // check: model_state_t model_get_state(model_t *instance, model_pos_t pos); + + { + // arrange + model_t model; + init_model(&model, 0); + // act & assert + printf(TRACE_INDENT "initial state:... "); + print_board(model.board); + for(size_t row = 0; row < MODEL_SIZE; row++) { + for(size_t col = 0; col < MODEL_SIZE; col++) { + printf("%zd/%zd ", row, col); + CU_ASSERT_EQUAL_FATAL(model_get_state(&model, model_pos(row, col)), model_state_none); + } + } + } + { + // arrange + static const model_state_t board[MODEL_SIZE][MODEL_SIZE] = { + { model_state_none, model_state_a, model_state_b }, + { model_state_a, model_state_b, model_state_none }, + { model_state_b, model_state_none, model_state_a }, + }; + model_t model; + init_model(&model, 0); + memcpy(model.board, board, sizeof(board)); + + // act & assert + printf(TRACE_INDENT "modified state:... "); + print_board(model.board); + for(size_t row = 0; row < MODEL_SIZE; row++) { + for(size_t col = 0; col < MODEL_SIZE; col++) { + printf("%zd/%zd ", row, col); + CU_ASSERT_EQUAL_FATAL(model_get_state(&model, model_pos(row, col)), board[row][col]); + } + } + } + printf(TRACE_INDENT); +} + +static void test_model_get_winner(void) +{ + // check: model_state_t model_get_winner(model_t *instance); + + { + // arrange + model_t model; + init_model(&model, 0); + // act & assert + printf(TRACE_INDENT "initial no winner:... "); + print_board(model.board); + CU_ASSERT_EQUAL_FATAL(model_get_winner(&model), model_state_none); + } + { + // arrange + static const model_state_t board[MODEL_SIZE][MODEL_SIZE] = { + { model_state_none, model_state_a, model_state_b }, + { model_state_a, model_state_b, model_state_none }, + { model_state_b, model_state_none, model_state_a }, + }; + model_t model; + init_model(&model, 0); + memcpy(model.board, board, sizeof(board)); + + // act & assert + printf(TRACE_INDENT "winner:... "); + print_board(model.board); + CU_ASSERT_EQUAL_FATAL(model_get_winner(&model), model_state_b); + } + printf(TRACE_INDENT); +} + +static void test_model_can_move(void) +{ + // check: int model_can_move(model_t *instance); + + { + // arrange + model_t model; + init_model(&model, 0); + // act & assert + printf(TRACE_INDENT "initial can move:... "); + print_board(model.board); + CU_ASSERT_EQUAL_FATAL(model_can_move(&model), 1); + } + { + // arrange + static const model_state_t board[MODEL_SIZE][MODEL_SIZE] = { + { model_state_none, model_state_a, model_state_a }, + { model_state_a, model_state_b, model_state_none }, + { model_state_b, model_state_none, model_state_b }, + }; + model_t model; + init_model(&model, 0); + memcpy(model.board, board, sizeof(board)); + + // act & assert + printf(TRACE_INDENT "can move while not yet done nor win:... "); + print_board(model.board); + CU_ASSERT_EQUAL_FATAL(model_can_move(&model), 1); + } + { + // arrange + static const model_state_t board[MODEL_SIZE][MODEL_SIZE] = { + { model_state_none, model_state_a, model_state_b }, + { model_state_a, model_state_b, model_state_none }, + { model_state_b, model_state_none, model_state_a }, + }; + model_t model; + init_model(&model, 0); + memcpy(model.board, board, sizeof(board)); + + // act & assert + printf(TRACE_INDENT "cannot move after win:... "); + print_board(model.board); + CU_ASSERT_EQUAL_FATAL(model_can_move(&model), 0); + } + { + // arrange + static const model_state_t board[MODEL_SIZE][MODEL_SIZE] = { + { model_state_b, model_state_a, model_state_a }, + { model_state_a, model_state_b, model_state_b }, + { model_state_b, model_state_a, model_state_a }, + }; + model_t model; + init_model(&model, 0); + memcpy(model.board, board, sizeof(board)); + + // act & assert + printf(TRACE_INDENT "cannot move when all done:... "); + print_board(model.board); + CU_ASSERT_EQUAL_FATAL(model_can_move(&model), 0); + } + printf(TRACE_INDENT); +} + +static void test_model_move(void) +{ + // check: int model_move(model_t *instance, model_pos_t pos, model_state_t state); + + { + // arrange + model_t model; + init_model(&model, 0); + // act & assert + printf(TRACE_INDENT "initial move:... "); + print_board(model.board); + model_pos_t pos_a = model_pos(0, 0); + printf("%zd/%zd ", pos_a.row, pos_a.col); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos_a, model_state_a), 1); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos_a, model_state_a), 0); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos_a, model_state_b), 0); + model_pos_t pos_b = model_pos(2, 2); + printf("%zd/%zd ", pos_b.row, pos_b.col); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos_b, model_state_b), 1); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos_b, model_state_b), 0); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos_b, model_state_a), 0); + } + { + // arrange + static const model_state_t board[MODEL_SIZE][MODEL_SIZE] = { + { model_state_none, model_state_a, model_state_a }, + { model_state_a, model_state_b, model_state_none }, + { model_state_b, model_state_none, model_state_b }, + }; + model_t model; + init_model(&model, 0); + memcpy(model.board, board, sizeof(board)); + + // act & assert + printf(TRACE_INDENT "can move while not yet done nor win:... "); + print_board(model.board); + model_pos_t pos = model_pos(2, 1); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos, model_state_a), 1); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos, model_state_a), 0); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos, model_state_b), 0); + } + { + // arrange + static const model_state_t board[MODEL_SIZE][MODEL_SIZE] = { + { model_state_none, model_state_a, model_state_b }, + { model_state_a, model_state_b, model_state_none }, + { model_state_b, model_state_none, model_state_a }, + }; + model_t model; + init_model(&model, 0); + memcpy(model.board, board, sizeof(board)); + + // act & assert + printf(TRACE_INDENT "cannot move after win:... "); + print_board(model.board); + model_pos_t pos = model_pos(2, 1); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos, model_state_a), 0); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos, model_state_b), 0); + } + { + // arrange + static const model_state_t board[MODEL_SIZE][MODEL_SIZE] = { + { model_state_b, model_state_a, model_state_a }, + { model_state_a, model_state_b, model_state_b }, + { model_state_b, model_state_a, model_state_a }, + }; + model_t model; + init_model(&model, 0); + memcpy(model.board, board, sizeof(board)); + + // act & assert + printf(TRACE_INDENT "cannot move when all done:... "); + print_board(model.board); + for(size_t row = 0; row < MODEL_SIZE; row++) { + for(size_t col = 0; col < MODEL_SIZE; col++) { + model_pos_t pos = model_pos(row, col); + printf("%zd/%zd ", row, col); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos, model_state_a), 0); + CU_ASSERT_EQUAL_FATAL(model_move(&model, pos, model_state_b), 0); + } + } + CU_ASSERT_EQUAL_FATAL(model_can_move(&model), 0); + } + printf(TRACE_INDENT); +} + +static void test_model_get_win_line(void) +{ + // check: model_line_t model_get_win_line(model_t *instance); + + { + // arrange + model_t model; + init_model(&model, 0); + // act & assert + printf(TRACE_INDENT "initial no winner:... "); + print_board(model.board); + model_line_t no_win = model_get_win_line(&model); + CU_ASSERT_EQUAL_FATAL(no_win.dir, model_dir_none); + } + { + // arrange + static const model_state_t board[MODEL_SIZE][MODEL_SIZE] = { + { model_state_none, model_state_a, model_state_a }, + { model_state_a, model_state_b, model_state_none }, + { model_state_b, model_state_none, model_state_b }, + }; + model_t model; + init_model(&model, 0); + memcpy(model.board, board, sizeof(board)); + + // act & assert + printf(TRACE_INDENT "no winner while not yet done nor win:... "); + print_board(model.board); + model_line_t no_win = model_get_win_line(&model); + CU_ASSERT_EQUAL_FATAL(no_win.dir, model_dir_none); + } + { + // arrange + static const model_state_t board[MODEL_SIZE][MODEL_SIZE] = { + { model_state_b, model_state_a, model_state_a }, + { model_state_a, model_state_b, model_state_b }, + { model_state_b, model_state_a, model_state_a }, + }; + model_t model; + init_model(&model, 0); + memcpy(model.board, board, sizeof(board)); + + // act & assert + printf(TRACE_INDENT "no winner when all done:... "); + print_board(model.board); + model_line_t no_win = model_get_win_line(&model); + CU_ASSERT_EQUAL_FATAL(no_win.dir, model_dir_none); + } + { + for(size_t row = 0; row < MODEL_SIZE; row++) { + // arrange + model_t model; + init_model(&model, 0); + for(size_t col = 0; col < MODEL_SIZE; col++) { + CU_ASSERT_EQUAL_FATAL(model_move(&model, model_pos(row, col), model_state_a), 1); + } + // act & assert + printf(TRACE_INDENT "row winner:... "); + print_board(model.board); + model_line_t win = model_get_win_line(&model); + CU_ASSERT_EQUAL_FATAL(win.dir, model_dir_h); + CU_ASSERT_EQUAL_FATAL(win.start.row, row); + CU_ASSERT_EQUAL_FATAL(win.start.col, 0); + } + } + { + for(size_t col = 0; col < MODEL_SIZE; col++) { + // arrange + model_t model; + init_model(&model, 0); + for(size_t row = 0; row < MODEL_SIZE; row++) { + CU_ASSERT_EQUAL_FATAL(model_move(&model, model_pos(row, col), model_state_a), 1); + } + // act & assert + printf(TRACE_INDENT "column winner:... "); + print_board(model.board); + model_line_t win = model_get_win_line(&model); + CU_ASSERT_EQUAL_FATAL(win.dir, model_dir_v); + CU_ASSERT_EQUAL_FATAL(win.start.row, 0); + CU_ASSERT_EQUAL_FATAL(win.start.col, col); + } + } + { + printf(TRACE_INDENT "diagonal left-right winner:... "); + // arrange + model_t model; + init_model(&model, 0); + for(size_t i = 0; i < MODEL_SIZE; i++) { + CU_ASSERT_EQUAL_FATAL(model_move(&model, model_pos(i, i), model_state_a), 1); + } + // act & assert + print_board(model.board); + model_line_t win = model_get_win_line(&model); + CU_ASSERT_EQUAL_FATAL(win.dir, model_dir_d); + CU_ASSERT_EQUAL_FATAL(win.start.row, 0); + CU_ASSERT_EQUAL_FATAL(win.start.col, 0); + } + { + printf(TRACE_INDENT "diagonal right-left winner:... "); + // arrange + model_t model; + init_model(&model, 0); + for(size_t i = 0; i < MODEL_SIZE; i++) { + CU_ASSERT_EQUAL_FATAL(model_move(&model, model_pos(MODEL_SIZE - 1 - i, i), model_state_a), 1); + } + // act & assert + print_board(model.board); + model_line_t win = model_get_win_line(&model); + CU_ASSERT_EQUAL_FATAL(win.dir, model_dir_d); + CU_ASSERT_EQUAL_FATAL(win.start.row, 0); + CU_ASSERT_EQUAL_FATAL(win.start.col, MODEL_SIZE - 1); + } + printf(TRACE_INDENT); +} + +/** + * @brief Registers and runs the tests. + * @returns success (0) or one of the CU_ErrorCode (>0) + */ +int main(void) +{ + // setup, run, teardown + TestMainBasic("lab test", setup, teardown + , test_model_init + , test_model_get_state + , test_model_get_winner + , test_model_can_move + , test_model_move + , test_model_get_win_line + ); +}