P05 lab code added

This commit is contained in:
Andreas Gieriet 2020-03-22 14:11:32 +01:00
parent 6f959c9759
commit 2f61cf20b1
10 changed files with 1294 additions and 0 deletions

View File

@ -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)

View File

@ -0,0 +1,8 @@
/**
* @mainpage SNP - P05 Tic Tac Toe Game
*
* @section Purpose
*
* This is a lab on usage of arrays.
*
*/

View File

@ -0,0 +1,154 @@
/**
* @file
* @brief Implementation
*/
#include "control.h"
#include "model.h"
#include <assert.h>
/**
* @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 } };
}
}

View File

@ -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_

View File

@ -0,0 +1,39 @@
/* ----------------------------------------------------------------------------
* -- _____ ______ _____ -
* -- |_ _| | ____|/ ____| -
* -- | | _ __ | |__ | (___ Institute of Embedded Systems -
* -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur -
* -- _| |_| | | | |____ ____) | (University of Applied Sciences) -
* -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland -
* ----------------------------------------------------------------------------
*/
/**
* @file
* @brief Lab P04 dep2dot
*/
#include <stdio.h>
#include <stdlib.h>
#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;
}

View File

@ -0,0 +1,165 @@
/**
* @file
* @brief Implementation
*/
#include "model.h"
#include <assert.h>
/**
* @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;
}

View File

@ -0,0 +1,108 @@
/**
* @file
* @brief MVC - Model instance
*/
#ifndef _MODEL_H_
#define _MODEL_H_
#include <stdlib.h>
#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_

View File

@ -0,0 +1,255 @@
/**
* @file
* @brief Implementation
*/
#include "view.h"
#include "control.h"
#include <assert.h> // assert()
#include <stdio.h> // various i/o
#include <ctype.h> // isdigit()
#include <unistd.h> // STDIN_FILENO, isatty()
#include <termios.h> // 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);
}
}

View File

@ -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_

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <time.h>
#include <assert.h>
#include <CUnit/Basic.h>
#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
);
}