P05 lab code added
This commit is contained in:
parent
6f959c9759
commit
2f61cf20b1
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* @mainpage SNP - P05 Tic Tac Toe Game
|
||||||
|
*
|
||||||
|
* @section Purpose
|
||||||
|
*
|
||||||
|
* This is a lab on usage of arrays.
|
||||||
|
*
|
||||||
|
*/
|
|
@ -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 } };
|
||||||
|
}
|
||||||
|
}
|
|
@ -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_
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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_
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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_
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue