diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d8ebf68 --- /dev/null +++ b/.clang-format @@ -0,0 +1,148 @@ +--- +Language: Cpp +# BasedOnStyle: Chromium +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 160 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 4 +UseTab: ForIndentation +... + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01f9cb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +.vscode/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7686160 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,114 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) game_text", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/game_text", + "args": [], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "preLaunchTask": "make_build" + }, + { + "name": "(gdb) debug tests thoguet", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/game_test_thoguet", + "args": [ + "game_default_solution" + ], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "preLaunchTask": "make_build" + }, + { + "name": "(gdb) debug tests histo", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/history_test", + "args": [ + "history_copy" + ], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "preLaunchTask": "make_build" + }, + { + "name": "(gdb) debug tests thperrier", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/game_test_thperrier", + "args": [ + "game_copy" + ], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "preLaunchTask": "make_build" + }, + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8431ee7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "files.associations": { + "game_ext.h": "c", + "game.h": "c", + "game_private.h": "c" + }, + "editor.formatOnSave": true, + "C_Cpp.clang_format_path": "/usr/bin/clang-format" +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..eeb2c12 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,87 @@ +#version minimale de cmake nécessaire +cmake_minimum_required(VERSION 2.6...3.13) + +enable_testing() + +#nom du projet +project (game_text) +include(CTest) + +#positionne la variable +set(CMAKE_C_FLAGS "-std=c99 -g -Wall") + +#définit le nom du programme ainsi que +#ses sources +add_executable(game_text game_text.c) +add_executable(game_test_thoguet game_test_thoguet.c) +add_executable(game_test_thperrier game_test_thperrier.c) +add_executable(game_test_nnunes game_test_nnunes.c) +#add_executable(history_test history_test.c) + +#les dépendances sont calculées automatiquement +target_link_libraries(game_text game) +target_link_libraries(game_test_thoguet game) +target_link_libraries(game_test_thperrier game) +target_link_libraries(game_test_nnunes game) +#target_link_libraries(history_test game) + +#Libraries +add_library (game game.c game_aux.c game_ext.c game_private.c history.c) + +#Tests THOGUET +add_test(test_thoguet_dummy ./game_test_thoguet dummy) +add_test(test_thoguet_game_new ./game_test_thoguet game_new) +add_test(test_thoguet_game_print ./game_test_thoguet game_print) +add_test(test_thoguet_game_get_square ./game_test_thoguet game_get_square) +add_test(test_thoguet_game_default ./game_test_thoguet game_default) +add_test(test_thoguet_game_default_solution ./game_test_thoguet game_default_solution) +add_test(test_thoguet_game_restart ./game_test_thoguet game_restart) +add_test(test_thoguet_game_is_over ./game_test_thoguet game_is_over) +add_test(test_thoguet_game_update_flags ./game_test_thoguet game_update_flags) +add_test(test_thoguet_game_play_move ./game_test_thoguet game_play_move) +add_test(test_thoguet_game_check_move ./game_test_thoguet game_check_move) +add_test(test_thoguet_game_new_empty_ext ./game_test_thoguet game_new_empty_ext) +add_test(test_thoguet_game_new_ext ./game_test_thoguet game_new_ext) + +#Tests THPERRIER +add_test(test_thperrier_dummy ./game_test_thperrier dummy) +add_test(test_thperrier_game_copy ./game_test_thperrier game_copy) +add_test(test_thperrier_game_new_empty ./game_test_thperrier game_new_empty) +add_test(test_thperrier_game_equal ./game_test_thperrier game_equal) +add_test(test_thperrier_game_delete ./game_test_thperrier game_delete) +add_test(test_thperrier_game_is_blank ./game_test_thperrier game_is_blank) +add_test(test_thperrier_game_is_lightbulb ./game_test_thperrier game_is_lightbulb) +add_test(test_thperrier_game_is_black ./game_test_thperrier game_is_black) +add_test(test_thperrier_game_get_black_number ./game_test_thperrier game_get_black_number) +add_test(test_thperrier_game_undo ./game_test_thperrier game_undo) +add_test(test_thperrier_game_redo ./game_test_thperrier game_redo) + +#Tests NNUNES +add_test(test_nnunes_dummy ./game_test_nnunes dummy) +add_test(test_nnunes_game_set_square ./game_test_nnunes game_set_square) +add_test(test_nnunes_game_get_state ./game_test_nnunes game_get_state) +add_test(test_nnunes_game_get_flags ./game_test_nnunes game_get_flags) +add_test(test_nnunes_game_is_marked ./game_test_nnunes game_is_marked) +add_test(test_nnunes_game_is_lighted ./game_test_nnunes game_is_lighted) +add_test(test_nnunes_game_has_error ./game_test_nnunes game_has_error) +add_test(test_nnunes_game_nb_rows ./game_test_nnunes game_nb_rows) +add_test(test_nnunes_game_nb_cols ./game_test_nnunes game_nb_cols) +add_test(test_nnunes_game_is_wrapping ./game_test_nnunes game_is_wrapping) + +#Tests HISTORY +# add_test(test_history_history_alloc ./history_test history_alloc) +# add_test(test_history_create_empty ./history_test history_create_empty) +# add_test(test_history_delete_entire_history ./history_test history_delete_entire_history) +# add_test(test_history_is_empty ./history_test history_is_empty) +# add_test(test_history_next ./history_test history_next) +# add_test(test_history_prev ./history_test history_prev) +# add_test(test_history_insert_first ./history_test history_insert_first) +# add_test(test_history_delete_first ./history_test history_delete_first) +# add_test(test_history_insert_after ./history_test history_insert_after) +# add_test(test_history_delete_after ./history_test history_delete_after) +# add_test(test_history_insert_before ./history_test history_insert_before) +# add_test(test_history_delete_before ./history_test history_delete_before) +# add_test(test_history_last ./history_test history_last) +# add_test(test_history_first ./history_test history_first) +# add_test(test_history_append ./history_test history_append) +# add_test(test_history_copy ./history_test history_copy) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..76e3fbf --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# lightup-a42e + +Projet Techno + +:warning: LE PREMIER QUI COMMIT DU CODE QUI COMPILE PAS JE L'ATTRAPE :warning: + +## game_ext.h + +| Fonction | REF | Assigned to | Notes | Finished | +| ------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------- | ----- | :--------------------: | +| [game_new_ext](./game_ext.c#L?) | [game game_new_ext(uint nb_rows, uint nb_cols, square *squares, bool wrapping)](./game_ext.h#L20-30) | @thoguet | | | +| [game_new_empty_ext](./game_ext.c#L?) | [game game_new_empty_ext(uint nb_rows, uint nb_cols, bool wrapping);](./game_ext.h#L32-41) | @thoguet | | | +| [game_nb_rows](./game_ext.c#L?) | [uint game_nb_rows(cgame g);](./game_ext.h#L43-59) | @nnunes | | | +| [game_nb_cols](./game_ext.c#L?) | [uint game_nb_cols(cgame g);](./game_ext.h#L51-57) | @nnunes | | | +| [game_is_wrapping](./game_ext.c#L?) | [bool game_is_wrapping(cgame g);](./game_ext.h#L59-64) | @nnunes | | | +| [game_undo](./game_ext.c#L?) | [void game_undo(game g);](./game_ext.h#L66-75) | @thperrier | | | +| [game_redo](./game_ext.c#L?) | [void game_redo(game g);](./game_ext.h#L77-86) | @thperrier | | | + +## game.c + +| Fonction | REF | Assigned to | Notes | Finished | +| ------------------------------------------ | ---------------------------------------------------------------------------- | ----------- | ----- | :--------------------: | +| [game_new](./game.c#L9-17) | [game game_new(square *squares)](./game.h#L73-81) | @thperrier | | | +| [game_new_empty](./game.c#L19-41) | [game game_new_empty(void)](./game.h#L83-88) | @thperrier | | | +| [game_copy](./game.c#L43-51) | [game game_copy(cgame g)](./game.h#L90-96) | @thperrier | | | +| [game_equal](./game.c#L53-62) | [bool game_equal(cgame g1, cgame g2)](./game.h#L98-106) | @thperrier | | | +| [game_delete](./game.c#L64-72) | [void game_delete(game g);](./game.h#L108-113) | @thperrier | | | +| [game_set_square](./game.c#L?) | [void game_set_square(game g, uint i, uint j, square s)](./game.h#L115-128) | @nnunes | | | +| [game_get_square](./game.c#L78-80) | [square game_get_square(cgame g, uint i, uint j)](./game.h#L130-140) | @thoguet | | | +| [game_get_state](./game.c#L?) | [square game_get_state(cgame g, uint i, uint j)](./game.h#L142-153) | @nnunes | | | +| [game_get_flags](./game.c#L?) | [square game_get_flags(cgame g, uint i, uint j)](./game.h#L155-166) | @nnunes | | | +| [game_is_blank](./game.c#L?) | [bool game_is_blank(cgame g, uint i, uint j)](./game.h#L168-178) | @thperrier | | | +| [game_is_lightbulb](./game.c#L?) | [bool game_is_lightbulb(cgame g, uint i, uint j)](./game.h#L180-190) | @thperrier | | | +| [game_is_black](./game.c#L98-102) | [bool game_is_black(cgame g, uint i, uint j)](./game.h#L192-202) | @thoguet | | | +| [game_get_black_number](./game.c#L104-108) | [int game_get_black_number(cgame g, uint i, uint j)](./game.h#L204-214) | @thoguet | | | +| [game_is_marked](./game.c#L?) | [bool game_is_marked(cgame g, uint i, uint j)](./game.h#L216-226) | @nnunes | | | +| [game_is_lighted](./game.c#L?) | [bool game_is_lighted(cgame g, uint i, uint j)](./game.h#L228-238) | @nnunes | | | +| [game_has_error](./game.c#L?) | [bool game_has_error(cgame g, uint i, uint j)](./game.h#L240-252) | @nnunes | | | +| [game_check_move](./game.c#L122-128) | [bool game_check_move(cgame g, uint i, uint j, square s)](./game.h#L242-269) | @thoguet | | | +| [game_play_move](./game.c#L130-133) | [void game_play_move(game g, uint i, uint j, square s)](./game.h#L271-284) | @thoguet | | | +| [game_update_flags](./game.c#L135-234) | [void game_update_flags(game g)](./game.h#L145-406) | @thoguet | | | +| [game_is_over](./game.c#L139-150) | [bool game_is_over(cgame g)](./game.h#L295-304) | @thoguet | | | +| [game_restart](./game.c#L152-160) | [void game_restart(game g)](./game.h#L306-313) | @thoguet | | | + +## Test + +| Test | REF | Assigned to | Notes | Finished | +| --------------------------------------------------- | ---------------------------------------------------------------------------- | ----------- | ----------------------------------------------- | :--------------------: | +| [game_new](./game_test_thoguet.c#L49) | [game game_new(square *squares)](./game.h#L73-81) | @thoguet | | | +| [game_new_empty](./game_test_thperrier.c#L16) | [game game_new_empty(void)](./game.h#L83-88) | @thperrier | | | +| [game_copy](./game_test_thperrier.c#L32) | [game game_copy(cgame g)](./game.h#L90-96) | @thperrier | | | +| [game_equal](./game_test_thperrier.c#L?) | [bool game_equal(cgame g1, cgame g2)](./game.h#L98-106) | @thperrier | | | +| [game_delete](./game_test_thperrier.c#L?) | [void game_delete(game g);](./game.h#L108-113) | @thperrier | | | +| [game_set_square](./game_test_nnunes.c#L?) | [void game_set_square(game g, uint i, uint j, square s)](./game.h#L115-128) | @nnunes | | | +| [game_get_square](./game_test_thoguet.c#L89) | [square game_get_square(cgame g, uint i, uint j)](./game.h#L130-140) | @thoguet | | | +| [game_get_state](./game_test_nnunes.c#L?) | [square game_get_state(cgame g, uint i, uint j)](./game.h#L142-153) | @nnunes | | | +| [game_get_flags](./game_test_nnunes.c#L?) | [square game_get_flags(cgame g, uint i, uint j)](./game.h#L155-166) | @nnunes | | | +| [game_is_blank](./game_test_thperrier.c#L?) | [bool game_is_blank(cgame g, uint i, uint j)](./game.h#L168-178) | @thperrier | | | +| [game_is_lightbulb](./game_test_thperrier.c#L?) | [bool game_is_lightbulb(cgame g, uint i, uint j)](./game.h#L180-190) | @thperrier | | | +| [game_is_black](./game_test_thperrier.c#L?) | [bool game_is_black(cgame g, uint i, uint j)](./game.h#L192-202) | @thperrier | | | +| [game_get_black_number](./game_test_thperrier.c#L?) | [int game_get_black_number(cgame g, uint i, uint j)](./game.h#L204-214) | @thperrier | | | +| [game_is_marked](./game_test_nnunes.c#L?) | [bool game_is_marked(cgame g, uint i, uint j)](./game.h#L216-226) | @nnunes | | | +| [game_is_lighted](./game_test_nnunes.c#L?) | [bool game_is_lighted(cgame g, uint i, uint j)](./game.h#L228-238) | @nnunes | | | +| [game_has_error](./game_test_nnunes.c#L?) | [bool game_has_error(cgame g, uint i, uint j)](./game.h#L240-252) | @nnunes | | | +| [game_check_move](./game_test_thoguet.c#L439-463) | [bool game_check_move(cgame g, uint i, uint j, square s)](./game.h#L242-269) | @thoguet | | | +| [game_play_move](./game_test_thoguet.c#L412-436) | [void game_play_move(game g, uint i, uint j, square s)](./game.h#L271-284) | @thoguet | | | +| [game_update_flags](./game_test_thoguet.c#L144-410) | [void game_update_flags(game g)](./game.h#L145-406) | @thoguet | done but not sure if it test every possibilites | | +| [game_is_over](./game_test_thoguet.c#L17) | [bool game_is_over(cgame g)](./game.h#L295-304) | @thoguet | | | +| [game_restart](./game_test_thoguet.c#L37) | [void game_restart(game g)](./game.h#L306-313) | @thoguet | | | +| [game_print](./game_test_thoguet.c#L70) | [void game_print(cgame g)](./game_aux.h#L13-20) | @thoguet | | | +| [game_default](./game_test_thoguet.c#L107) | [game game_default(void)](./game_aux.h#L22-28) | @thoguet | | | +| [game_default_solution](./game_test_thoguet.c#L125) | [game game_default_solution(void)](./game_aux.h#L30-36) | @thoguet | | | diff --git a/game.c b/game.c new file mode 100644 index 0000000..1eb4cd5 --- /dev/null +++ b/game.c @@ -0,0 +1,291 @@ +#include "game.h" +#include +#include +#include "game_ext.h" +#include "game_private.h" + +// JWRAPPING and IWRAPPING have a "+ game_nb_xxxx(g)" to avoid falling in negative when we are at 0 for exemple +#define JWRAPPING (j + game_nb_cols(g) + gap_position * tab[index_tab + 1]) % game_nb_cols(g) +#define IWRAPPING (i + game_nb_rows(g) + gap_position * tab[index_tab]) % game_nb_rows(g) +#define JNORMAL j + gap_position* tab[index_tab + 1] +#define INORMAL i + gap_position* tab[index_tab] + +game game_new(square* squares) { + game g = game_new_empty(); + for (int i = 0; i < game_nb_rows(g); i++) { + for (int j = 0; j < game_nb_cols(g); j++) { + g->tab_cell[i][j] = squares[game_nb_cols(g) * i + j]; + } + } + return g; +} + +game game_new_empty(void) { + // alloc struct + game g = (game)malloc(sizeof(struct game_s)); + if (g == NULL) { + memoryError(); + } + // set variable of struct + g->wrapping = false; + g->height = DEFAULT_SIZE; + g->width = DEFAULT_SIZE; + g->hist = history_create_empty(); + // alloc the tab with variables set before + g->tab_cell = (square**)malloc(sizeof(square*) * game_nb_rows(g)); + if (g->tab_cell == NULL) { + memoryError(); + } + for (int i = 0; i < game_nb_rows(g); i++) { + g->tab_cell[i] = (square*)malloc(sizeof(square) * game_nb_cols(g)); + if (g->tab_cell[i] == NULL) { + memoryError(); + } + for (int j = 0; j < game_nb_cols(g); j++) { + g->tab_cell[i][j] = S_BLANK; + } + } + // init history with a cell to avoid falling off of g->hist (if there is only this cell we consider the history as empty) + g->hist = history_insert_first(g->hist, F_ERROR, 0, 0); + return g; +} + +game game_copy(cgame g1) { + game g2 = game_new_empty_ext(g1->height, g1->width, g1->wrapping); + for (int i = 0; i < g2->height; i++) { + for (int j = 0; j < g2->width; j++) { + g2->tab_cell[i][j] = g1->tab_cell[i][j]; + } + } + // delete first allocated hist (by game new) of g2 + history_delete_entire_history(g2->hist); + g2->hist = history_copy(g1->hist); + return g2; +} + +bool game_equal(cgame g1, cgame g2) { + // check if the two games have the same options + if (g1->height != g2->height || g1->width != g2->width || g1->wrapping != g2->wrapping) + return false; + // check each cell and seek any difference between g1 and g2 + for (int i = 0; i < g1->height; i++) { + for (int j = 0; j < g1->width; j++) { + if (g1->tab_cell[i][j] != g2->tab_cell[i][j]) { + return false; + } + } + } + return true; +} + +void game_delete(game g) { + // free each tab of tab_cell + for (int i = 0; i < game_nb_rows(g); i++) { + free(g->tab_cell[i]); + g->tab_cell[i] = NULL; + } + free(g->tab_cell); + g->tab_cell = NULL; + history_delete_entire_history(g->hist); + g->hist = NULL; + free(g); + g = NULL; +} + +void game_set_square(game g, uint i, uint j, square s) { + g->tab_cell[i][j] = s; + return; +} + +square game_get_square(cgame g, uint i, uint j) { + return g->tab_cell[i][j]; +} + +square game_get_state(cgame g, uint i, uint j) { + square temp = g->tab_cell[i][j]; + square list_flag[] = {F_ERROR, F_LIGHTED}; + for (uint flag_index = 0; flag_index < sizeof(list_flag) / sizeof(list_flag[0]); flag_index++) { + if (temp >= list_flag[flag_index]) { + temp -= list_flag[flag_index]; + } + } + return temp; +} + +square game_get_flags(cgame g, uint i, uint j) { + square temp = g->tab_cell[i][j]; + square list_flag[] = {(F_ERROR | F_LIGHTED), F_ERROR, F_LIGHTED}; + for (uint flag_index = 0; flag_index < sizeof(list_flag) / sizeof(list_flag[0]); flag_index++) { + if (temp >= list_flag[flag_index]) { + return list_flag[flag_index]; + } + } + return (S_BLANK); +} + +bool game_is_blank(cgame g, uint i, uint j) { + return game_get_state(g, i, j) == S_BLANK; +} + +bool game_is_lightbulb(cgame g, uint i, uint j) { + return game_get_state(g, i, j) == S_LIGHTBULB; +} + +bool game_is_black(cgame g, uint i, uint j) { + return game_get_state(g, i, j) >= S_BLACK0 && game_get_state(g, i, j) <= S_BLACKU; +} + +int game_get_black_number(cgame g, uint i, uint j) { + if (game_get_state(g, i, j) == S_BLACKU) + return -1; + return game_get_state(g, i, j) - S_BLACK; +} + +bool game_is_marked(cgame g, uint i, uint j) { + return game_get_state(g, i, j) == S_MARK; +} + +bool game_is_lighted(cgame g, uint i, uint j) { + return game_get_flags(g, i, j) >= F_LIGHTED; +} + +bool game_has_error(cgame g, uint i, uint j) { + return g->tab_cell[i][j] >= F_ERROR; +} + +bool game_check_move(cgame g, uint i, uint j, square s) { + return (i < game_nb_rows(g) && j < game_nb_cols(g) && (s == S_BLANK || s == S_MARK || s == S_LIGHTBULB) && !game_is_black(g, i, j)); +} + +void game_play_move(game g, uint i, uint j, square s) { + // shouldn't happen because we initalized in game new + if (g->hist == NULL) { + g->hist = history_create_empty(); + g->hist = history_insert_first(g->hist, game_get_state(g, i, j), i, j); + } else { + // to avoid redo after play + g->hist = history_delete_all_after(g->hist, g->hist); + // add new played element to hist and get the last element of hist + g->hist = history_next(history_insert_after(g->hist, g->hist, game_get_state(g, i, j), i, j)); + } + game_set_square(g, i, j, s); + game_update_flags(g); +} + +void delete_flags(game g) { + // delete prev flags + for (uint i = 0; i < game_nb_rows(g); i++) { + for (uint j = 0; j < game_nb_cols(g); j++) { + game_set_square(g, i, j, game_get_state(g, i, j)); + } + } +} + +void addF_LIGHTED(game g, uint i, uint j) { + square flags = game_get_flags(g, i, j); + if (flags == S_BLANK) + game_set_square(g, i, j, game_get_state(g, i, j) | F_LIGHTED); + else if (flags == F_ERROR) { + game_set_square(g, i, j, game_get_state(g, i, j) | F_LIGHTED | F_ERROR); + } +} + +void update_flags_lightbulb(game g, uint i, uint j) { + // add F_LIGHTED to the current lightbulb + addF_LIGHTED(g, i, j); + // add F_LIGHTED on the 4 directions, when reaching a wall by following a direction, remove it from tab, when reaching another lightbulb, put a + // F_ERROR on the initial lightbulb + // tab[] = { i, j, i, j, i, j, i, j} + int tab[] = {-1, 0, 1, 0, 0, -1, 0, 1}; + for (int gap_position = 1; gap_position < max(game_nb_cols(g), game_nb_rows(g)); gap_position++) { + // index_tab => pair = i + // index_tab => odd = j + for (uint index_tab = 0; index_tab < sizeof(tab) / sizeof(tab[0]); index_tab += 2) { + // big if to check if INORMAL and JNORMAL are correct or if wrapping is true and IWRAPPING and JWRAPPING are not the initial case + if (/* test if both tab are not 0*/ tab[index_tab] != tab[index_tab + 1] && + (/* normal check */ (JNORMAL >= 0 && JNORMAL < game_nb_cols(g) && INORMAL >= 0 && INORMAL < game_nb_rows(g)) || + /* wrapping check + not the inital case*/ (game_is_wrapping(g) && !(IWRAPPING == i && JWRAPPING == j)))) { + if (game_is_black(g, IWRAPPING, JWRAPPING)) { + tab[index_tab] = 0; + tab[index_tab + 1] = 0; + } else { + // if we found another lightbulb on the line + if (game_is_lightbulb(g, IWRAPPING, JWRAPPING)) + // update initial lightbulb on F_ERROR + game_set_square(g, i, j, (S_LIGHTBULB | F_LIGHTED | F_ERROR)); + addF_LIGHTED(g, IWRAPPING, JWRAPPING); + } + } + } + } +} + +void update_flags_walls(game g, uint i, uint j) { + int lightbulb = 0; + int not_empty = 0; + int tab[] = {-1, 0, 1, 0, 0, -1, 0, 1}; + for (uint index_tab = 0; index_tab < (sizeof(tab) / sizeof(tab[0])); index_tab += 2) { + uint gap_position = 1; + if (/* normal check*/ (JNORMAL >= 0 && JNORMAL < game_nb_cols(g) && INORMAL >= 0 && INORMAL < game_nb_rows(g)) || + /*wrapping check*/ (game_is_wrapping(g) && + /*not the same case*/ !(IWRAPPING == i && JWRAPPING == j))) { + if (game_is_lightbulb(g, IWRAPPING, JWRAPPING)) + lightbulb++; + else if (!game_is_blank(g, IWRAPPING, JWRAPPING) || game_is_lighted(g, IWRAPPING, JWRAPPING)) + not_empty++; + } else { + not_empty++; + } + } + if (lightbulb > game_get_black_number(g, i, j)) { + game_set_square(g, i, j, (game_get_state(g, i, j) | F_ERROR)); + } + // look if there is enough empty cell around the wall (4 for the four directions) + else if (not_empty > abs(game_get_black_number(g, i, j) - 4)) { + game_set_square(g, i, j, (game_get_state(g, i, j) | F_ERROR)); + } +} + +void game_update_flags(game g) { + delete_flags(g); + // update flags lighted and lightbulb error + for (uint i = 0; i < game_nb_rows(g); i++) { + for (uint j = 0; j < game_nb_cols(g); j++) { + if (game_is_lightbulb(g, i, j)) { + update_flags_lightbulb(g, i, j); + } + } + } + // update flags wall (need to wait all FLIGHTED being updated) + for (uint i = 0; i < game_nb_rows(g); i++) { + for (uint j = 0; j < game_nb_cols(g); j++) { + if (game_is_black(g, i, j) && game_get_black_number(g, i, j) >= 0) { + update_flags_walls(g, i, j); + } + } + } +} + +bool game_is_over(cgame g) { + for (uint i = 0; i < game_nb_rows(g); i++) { + for (uint j = 0; j < game_nb_cols(g); j++) { + if ((game_get_state(g, i, j) == S_BLANK && game_get_flags(g, i, j) == S_BLANK) || game_get_flags(g, i, j) == F_ERROR || + game_get_flags(g, i, j) == (F_LIGHTED | F_ERROR)) + return false; + } + } + return true; +} + +void game_restart(game g) { + for (uint i = 0; i < game_nb_rows(g); i++) { + for (uint j = 0; j < game_nb_cols(g); j++) { + if (game_get_state(g, i, j) == S_MARK || game_is_lightbulb(g, i, j)) + game_set_square(g, i, j, S_BLANK); + } + } + game_update_flags(g); + history_delete_entire_history(g->hist); + // insert f_error on first cell of histo. initalized to avoid bug on first move undo + g->hist = history_insert_first(history_create_empty(), F_ERROR, 0, 0); +} diff --git a/game.h b/game.h new file mode 100644 index 0000000..4c0ecb0 --- /dev/null +++ b/game.h @@ -0,0 +1,316 @@ +/** + * @file game.h + * @brief Basic Game Functions. + * @details See @ref index for further details. + * @copyright University of Bordeaux. All rights reserved, 2021. + **/ + +#ifndef __GAME_H__ +#define __GAME_H__ + +#include + +/** + * @brief Standard unsigned integer type. + **/ +typedef unsigned int uint; + +/** + * @brief Size of the default game grid. + **/ +#define DEFAULT_SIZE 7 + +/** + * @brief Different squares used in the game. + * @details The value of a square is an integer represented on one byte, with a + * low-order half-byte that codes for the square state (blank, black wall, light + * bulb or mark) and a high-order half-byte that codes for the additionnal + * square flags (lighted or error). While each square has a single state, it can + * have zero, one or several flags (combined with bitwise operations). For + * instance, a light bulb can have both the lighted and error flags. In this + * case, the raw square value is S_LIGHTBULB | F_LIGHTED | F_ERROR, using the + * bitwise OR operator (|). For more details on bitwise operations, see + * https://en.wikipedia.org/wiki/Bitwise_operations_in_C. + **/ +typedef enum { + /* states */ + S_BLANK = 0, /**< a blank square */ + S_LIGHTBULB = 1, /**< a light bulb */ + S_MARK = 2, /**< a marked square (as not-a-light) */ + S_BLACK = 8, /**< black wall base code */ + S_BLACK0 = S_BLACK, /**< a numbered black wall (with 0 adjacent lights) */ + S_BLACK1, /**< a numbered black wall (with 1 adjacent light) */ + S_BLACK2, /**< a numbered black wall (with 2 adjacent lights) */ + S_BLACK3, /**< a numbered black wall (with 3 adjacent lights) */ + S_BLACK4, /**< a numbered black wall (with 4 adjacent lights) */ + S_BLACKU, /**< an unnumbered black wall (any number of adjacent lights) */ + /* flags */ + F_LIGHTED = 16, /**< lighted flag */ + F_ERROR = 32 /**< error flag */ +} square; + +/** state mask used in square enum */ +#define S_MASK 0x0F + +/** flag mask used in square enum */ +#define F_MASK 0xF0 + +/** square mask used in square enum */ +#define A_MASK 0xFF + +/** + * @brief The structure pointer that stores the game state. + **/ +typedef struct game_s* game; + +/** + * @brief The structure constant pointer that stores the game state. + * @details That means that it is not possible to modify the game using this + * pointer. + **/ +typedef const struct game_s* cgame; + +/** + * @brief Creates a new game with default size and initializes it. + * @param squares an array describing the initial square values (row-major + * storage) + * @pre @p squares must be an initialized array of default size squared. + * @return the created game + **/ +game game_new(square* squares); + +/** + * @brief Creates a new empty game with defaut size. + * @details All squares are initialized with blank squares. + * @return the created game + **/ +game game_new_empty(void); + +/** + * @brief Duplicates a game. + * @param g the game to copy + * @return the copy of the game + * @pre @p g must be a valid pointer toward a game structure. + **/ +game game_copy(cgame g); + +/** + * @brief Tests if two games are equal (same states and same flags). + * @param g1 the first game + * @param g2 the second game + * @return true if the two games are equal, false otherwise + * @pre @p g1 must be a valid pointer toward a game structure. + * @pre @p g2 must be a valid pointer toward a game structure. + **/ +bool game_equal(cgame g1, cgame g2); + +/** + * @brief Deletes the game and frees the allocated memory. + * @param g the game to delete + * @pre @p g must be a valid pointer toward a game structure. + **/ +void game_delete(game g); + +/** + * @brief Sets the value of a given square. + * @details This function is useful for initializing the squares of an empty + * game. + * @param g the game + * @param i row index + * @param j column index + * @param s the square value + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @pre @p s must be a valid square value. + **/ +void game_set_square(game g, uint i, uint j, square s); + +/** + * @brief Gets the raw value of a given square. + * @param g the game + * @param i row index + * @param j column index + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @return the square value + **/ +square game_get_square(cgame g, uint i, uint j); + +/** + * @brief Gets the state of a given square. + * @details See description of enum square. + * @param g the game + * @param i row index + * @param j column index + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @return the square state + **/ +square game_get_state(cgame g, uint i, uint j); + +/** + * @brief Gets the flags of a given square. + * @details See description of enum square. + * @param g the game + * @param i row index + * @param j column index + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @return the square flags + **/ +square game_get_flags(cgame g, uint i, uint j); + +/** + * @brief Test if a given square is blank. + * @param g the game + * @param i row index + * @param j column index + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @return true if the square is blank + **/ +bool game_is_blank(cgame g, uint i, uint j); + +/** + * @brief Test if a given square is a lightbulb. + * @param g the game + * @param i row index + * @param j column index + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @return true if the square is a lightbulb + **/ +bool game_is_lightbulb(cgame g, uint i, uint j); + +/** + * @brief Test if a given square is black (whether or not it is numbered). + * @param g the game + * @param i row index + * @param j column index + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @return true if the square is black + **/ +bool game_is_black(cgame g, uint i, uint j); + +/** + * @brief Get the number of lightbulbs expected against a black wall. + * @param g the game + * @param i row index + * @param j column index + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @pre The square at position (i,j) must be a black wall (either numbered or + * unumbered). + * @return the back wall number, or -1 if it is unumbered + **/ +int game_get_black_number(cgame g, uint i, uint j); + +/** + * @brief Test if a given square is marked (as not-a-light). + * @param g the game + * @param i row index + * @param j column index + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @return true if the square is marked + **/ +bool game_is_marked(cgame g, uint i, uint j); + +/** + * @brief Test if a given square is lighted. + * @param g the game + * @param i row index + * @param j column index + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @return true if the square is lighted + **/ +bool game_is_lighted(cgame g, uint i, uint j); + +/** + * @brief Test if a given square has an error flag + * @details An error can only occur on an numbered black wall or on a light + * bulb. + * @param g the game + * @param i row index + * @param j column index + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @return true if the square has an error flag + **/ +bool game_has_error(cgame g, uint i, uint j); + +/** + * @brief Checks if a given move is legal. + * @details This function checks that it is possible to play a move at a given + * position in the grid. More precisely, a move is said to be legal: 1) if the + * coordinates (i,j) are inside the grid, 2) if @p s is either a blank, light + * bulb or mark square, and 3) if the current square at (i,j) is not a black + * square. + * @param g the game + * @param i row index + * @param j column index + * @param s the square value + * @pre @p g must be a valid pointer toward a game structure. + * @return false if the move is not legal. + **/ +bool game_check_move(cgame g, uint i, uint j, square s); + +/** + * @brief Plays a move in a given square. + * @details The grid flags are updated consequently after each move. + * @param g the game + * @param i row index + * @param j column index + * @param s the square value + * @pre @p g must be a valid pointer toward a game structure. + * @pre @p i < game height + * @pre @p j < game width + * @pre @p s must be either BLANK, LIGHTBULB or MARK. + * @pre The game square at position (i,j) must not be a black square. + **/ +void game_play_move(game g, uint i, uint j, square s); + +/** + * @brief Update all grid flags. + * @details This is a low-level function, which is not intended to be used + * directly by end-user during the game. + * @param g the game + * @pre @p g must be a valid pointer toward a game structure. + **/ +void game_update_flags(game g); + +/** + * @brief Checks if the game is won. + * @param g the game + * @details This function checks that all the game rules (decribed on @ref + * index) are satisfied, ie. that all the blank squares are ligthed, without any + * errors. + * @return true if the game ended successfully, false otherwise + * @pre @p g must be a valid pointer toward a game structure. + **/ +bool game_is_over(cgame g); + +/** + * @brief Restarts a game. + * @details All the game is reset to its initial state. In particular, all the + * squares except black walls are reset to blank, and all flags are cleared. + * @param g the game + * @pre @p g must be a valid pointer toward a game structure. + **/ +void game_restart(game g); + +#endif // __GAME_H__ \ No newline at end of file diff --git a/game_aux.c b/game_aux.c new file mode 100644 index 0000000..b9ab3bb --- /dev/null +++ b/game_aux.c @@ -0,0 +1,69 @@ +#include +#include +#include "game.h" +#include "game_ext.h" +#include "game_private.h" + +char charPrintCell(cgame g, uint i, uint j) { + square sqState = game_get_state(g, i, j); + square sqFlags = game_get_flags(g, i, j); + char tab[] = {' ', '*', '-', '?', '?', '?', '?', '?', '0', '1', '2', '3', '4', 'w', '?', '?', '.'}; + if (!sqState) + return tab[sqFlags]; + return tab[sqState]; +} + +void game_print(cgame g) { + if (g != NULL) { + uint i; + printf(" 0"); + for (i = 1; i < game_nb_cols(g); i++) { + printf("%u", i); + } + printf("\n -"); + for (i = 1; i < game_nb_cols(g); i++) { + printf("-"); + } + printf("\n"); + for (i = 0; i < game_nb_rows(g); i++) { + printf("%u |", i); + for (uint j = 0; j < game_nb_cols(g); j++) { + printf("%c", charPrintCell(g, i, j)); + } + printf("|\n"); + } + printf(" -"); + for (i = 1; i < game_nb_cols(g); i++) { + printf("-"); + } + printf("\n"); + } +} +game game_default(void) { + game g = game_new_empty(); + game_set_square(g, 0, 2, S_BLACK1); + game_set_square(g, 1, 2, S_BLACK2); + game_set_square(g, 2, 5, S_BLACKU); + game_set_square(g, 2, 6, S_BLACK2); + game_set_square(g, 4, 0, S_BLACK1); + game_set_square(g, 4, 1, S_BLACKU); + game_set_square(g, 5, 4, S_BLACK2); + game_set_square(g, 6, 4, S_BLACKU); + return g; +} + +game game_default_solution(void) { + game g = game_default(); + game_set_square(g, 0, 0, S_LIGHTBULB); + game_set_square(g, 1, 1, S_LIGHTBULB); + game_set_square(g, 0, 3, S_LIGHTBULB); + game_set_square(g, 1, 6, S_LIGHTBULB); + game_set_square(g, 2, 2, S_LIGHTBULB); + game_set_square(g, 3, 6, S_LIGHTBULB); + game_set_square(g, 4, 4, S_LIGHTBULB); + game_set_square(g, 5, 0, S_LIGHTBULB); + game_set_square(g, 5, 5, S_LIGHTBULB); + game_set_square(g, 6, 1, S_LIGHTBULB); + game_update_flags(g); + return g; +} \ No newline at end of file diff --git a/game_aux.h b/game_aux.h new file mode 100644 index 0000000..1e425a6 --- /dev/null +++ b/game_aux.h @@ -0,0 +1,39 @@ +/** + * @file game_aux.h + * @brief Auxiliarry Game Functions. + * @details See @ref index for further details. + * @copyright University of Bordeaux. All rights reserved, 2021. + **/ + +#ifndef __GAME_AUX_H__ +#define __GAME_AUX_H__ +#include + +#include "game.h" + +/** + * @brief Prints a game as text on the standard output stream. + * @details The different squares are respectively displayed as text, based on a + * square-character mapping table, as described in @ref index. + * @param g the game + * @pre @p g must be a valid pointer toward a game structure. + **/ +void game_print(cgame g); + +/** + * @brief Creates the default game. + * @details See the description of the default game in @ref index. + * @details https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/lightup.html#7x7:b1f2iB2g1Bi2fBb + * @return the created game + **/ +game game_default(void); + +/** + * @brief Creates the default game solution. + * @details See the description of the default game in @ref index. + * @details https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/lightup.html#7x7:b1f2iB2g1Bi2fBb + * @return the created game + **/ +game game_default_solution(void); + +#endif // __GAME_AUX_H__ \ No newline at end of file diff --git a/game_ext.c b/game_ext.c new file mode 100644 index 0000000..c2f4044 --- /dev/null +++ b/game_ext.c @@ -0,0 +1,84 @@ +#include "game_ext.h" +#include +#include +#include "game_private.h" + +game game_new_ext(uint nb_rows, uint nb_cols, square* squares, bool wrapping) { + game g = game_new_empty_ext(nb_rows, nb_cols, wrapping); + for (int i = 0; i < game_nb_rows(g); i++) { + for (int j = 0; j < game_nb_cols(g); j++) { + g->tab_cell[i][j] = squares[game_nb_cols(g) * i + j]; + } + } + return g; +} + +game game_new_empty_ext(uint nb_rows, uint nb_cols, bool wrapping) { + // alloc struct + game g = (game)malloc(sizeof(struct game_s)); + if (g == NULL) { + fprintf(stderr, "not enough memory\n"); + exit(EXIT_FAILURE); + } + // set variable of struct + g->wrapping = wrapping; + g->height = nb_rows; + g->width = nb_cols; + g->hist = history_create_empty(); + // alloc the tab with variables set before + // alloc first tab + g->tab_cell = (square**)malloc(sizeof(square*) * game_nb_rows(g)); + if (g->tab_cell == NULL) { + fprintf(stderr, "not enough memory\n"); + exit(EXIT_FAILURE); + } + // alloc each tab of tab_cell + for (int i = 0; i < game_nb_rows(g); i++) { + g->tab_cell[i] = (square*)calloc(game_nb_cols(g), sizeof(square)); + if (g->tab_cell[i] == NULL) { + fprintf(stderr, "not enough memory\n"); + exit(EXIT_FAILURE); + } + } + // insert f_error on first cell of histo. initalized to avoid bug on first move undo + g->hist = history_insert_first(g->hist, F_ERROR, 0, 0); + return g; +} + +uint game_nb_rows(cgame g) { + return g->height; +} + +uint game_nb_cols(cgame g) { + return g->width; +} + +bool game_is_wrapping(cgame g) { + return g->wrapping; +} + +void game_undo(game g) { + // check if g->hist is not empty or if it is possible to undo + if (g->hist == NULL || history_is_empty(history_prev(g->hist))) { + printf("you are at your first move\n"); + } else { + square tmp = game_get_state(g, history_i(g->hist), history_j(g->hist)); + game_set_square(g, history_i(g->hist), history_j(g->hist), history_state(g->hist)); + game_update_flags(g); + g->hist->state = tmp; + g->hist = history_prev(g->hist); + } +} + +void game_redo(game g) { + // check if g->hist is not empty or if it is possible to redo + if (g->hist == NULL || history_next(g->hist) == NULL) { + printf("you are at your last move\n"); + } else { + g->hist = history_next(g->hist); + square tmp = game_get_state(g, history_i(g->hist), history_j(g->hist)); + game_set_square(g, history_i(g->hist), history_j(g->hist), history_state(g->hist)); + game_update_flags(g); + g->hist->state = tmp; + } +} \ No newline at end of file diff --git a/game_ext.h b/game_ext.h new file mode 100644 index 0000000..b38ed7e --- /dev/null +++ b/game_ext.h @@ -0,0 +1,92 @@ +/** + * @file game_ext.h + * @brief Extended Game Functions. + * @details See @ref index for further details. + * @copyright University of Bordeaux. All rights reserved, 2021. + **/ + +#ifndef __GAME_EXT_H__ +#define __GAME_EXT_H__ + +#include + +#include "game.h" + +/** + * @name Extended Functions + * @{ + */ + +/** + * @brief Creates a new game with extended options and initializes it. + * @details See description of game extensions on @ref index. + * @param nb_rows number of rows in game + * @param nb_cols number of columns in game + * @param squares an array describing the initial state of each square + * (row-major storage) + * @param wrapping wrapping option + * @return the created game + **/ +game game_new_ext(uint nb_rows, uint nb_cols, square* squares, bool wrapping); + +/** + * @brief Creates a new empty game with extended options. + * @details All squares are initialized with empty squares. + * @details See description of game extensions on @ref index. + * @param nb_rows number of rows in game + * @param nb_cols number of columns in game + * @param wrapping wrapping option + * @return the created game + **/ +game game_new_empty_ext(uint nb_rows, uint nb_cols, bool wrapping); + +/** + * @brief Gets the number of rows (or height). + * @param g the game + * @return the number of rows on this game + * @pre @p g is a valid pointer toward a cgame structure + **/ +uint game_nb_rows(cgame g); + +/** + * @brief Gets the number of columns (or width). + * @param g the game + * @return the the number of columns on this game + * @pre @p g is a valid pointer toward a cgame structure + **/ +uint game_nb_cols(cgame g); + +/** + * @brief Checks if the game has the wrapping option + * @return true, if wrapping, false otherwise + * @pre @p g is a valid pointer toward a cgame structure + **/ +bool game_is_wrapping(cgame g); + +/** + * @brief Undoes the last move. + * @details Searches in the history the last move played (by calling + * @ref game_play_move or @ref game_redo), and restores the state of the game + * before that move. If no moves have been played, this function does nothing. + * The @ref game_restart function clears the history. + * @param g the game + * @pre @p g is a valid pointer toward a cgame structure + **/ +void game_undo(game g); + +/** + * @brief Redoes the last move. + * @details Searches in the history the last cancelled move (by calling @ref + * game_undo), and replays it. If there are no more moves to be replayed, this + * function does nothing. After playing a new move with @ref game_play_move, it + * is no longer possible to redo an old cancelled move. + * @param g the game + * @pre @p g is a valid pointer toward a cgame structure + **/ +void game_redo(game g); + +/** + * @} + */ + +#endif // __GAME_EXT_H__ \ No newline at end of file diff --git a/game_private.c b/game_private.c new file mode 100644 index 0000000..5dbbd6e --- /dev/null +++ b/game_private.c @@ -0,0 +1,15 @@ +#include +#include + +int max(int a, int b) { + if (a > b) + return a; + return b; +} + +void checkPointer(void* p) { + if (!p) { + fprintf(stderr, "Error on given pointer.\n"); + exit(EXIT_FAILURE); + } +} \ No newline at end of file diff --git a/game_private.h b/game_private.h new file mode 100644 index 0000000..28fef93 --- /dev/null +++ b/game_private.h @@ -0,0 +1,31 @@ +#ifndef __GAME_PRIVATE_H__ +#define __GAME_PRIVATE_H__ +#include "history.h" + +struct game_s { + // tab of tab of square (eg: tab_cell[i][j]) + square** tab_cell; + bool wrapping; + uint height; // i in general + uint width; // j in general + // case of history + history hist; +}; + +/** + * @brief return the max between a and b + * + * @param a int to test + * @param b int to test + * @return the max between a and b + */ +int max(int a, int b); + +/** + * @brief check if the pointer is NULL fprinf and exit else nothing + * + * @param p any pointer + */ +void checkPointer(void* p); + +#endif \ No newline at end of file diff --git a/game_test_nnunes.c b/game_test_nnunes.c new file mode 100644 index 0000000..d6af704 --- /dev/null +++ b/game_test_nnunes.c @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include +#include "game.h" +#include "game_aux.h" +#include "game_ext.h" +#include "game_private.h" + +bool test_dummy(void) { + return true; +} + +void usage(int argc, char* argv[]) { + fprintf(stderr, "Usage: %s [<...>]\n", argv[0]); + exit(EXIT_FAILURE); +} + +/* ************** game_set_square ************** */ +bool test_game_set_square(void) { + game g = game_new_empty(); + square list[] = {S_BLANK, S_LIGHTBULB, S_MARK, S_BLACK0, S_BLACK1, S_BLACK2, S_BLACK3, S_BLACK4, S_BLACKU}; + square list_flag[] = {F_ERROR, F_LIGHTED, (F_ERROR | F_LIGHTED)}; + for (uint i = 0; i < DEFAULT_SIZE; i++) { + for (uint j = 0; j < DEFAULT_SIZE; j++) { + for (uint flag_index = 0; flag_index < sizeof(list_flag) / sizeof(list_flag[0]); flag_index++) { + for (uint tab_index = 0; tab_index < sizeof(list) / sizeof(list[0]); tab_index++) { + game_set_square(g, i, j, (list[tab_index] | list_flag[flag_index])); + if (game_get_square(g, i, j) != (list[tab_index] | list_flag[flag_index])) { + game_delete(g); + return false; + } + } + } + } + } + game_delete(g); + return true; +} + +/* ************** game_get_state ************** */ +bool test_game_get_state(void) { + game g = game_new_empty(); + square list[] = {S_BLANK, S_LIGHTBULB, S_MARK, S_BLACK0, S_BLACK1, S_BLACK2, S_BLACK3, S_BLACK4, S_BLACKU}; + square list_flag[] = {S_BLANK, F_ERROR, F_LIGHTED, (F_ERROR | F_LIGHTED)}; + for (uint i = 0; i < DEFAULT_SIZE; i++) { + for (uint j = 0; j < DEFAULT_SIZE; j++) { + for (uint flag_index = 0; flag_index < sizeof(list_flag) / sizeof(list_flag[0]); flag_index++) { + for (uint tab_index = 0; tab_index < sizeof(list) / sizeof(list[0]); tab_index++) { + game_set_square(g, i, j, (list[tab_index] | list_flag[flag_index])); + if (game_get_state(g, i, j) != list[tab_index]) { + game_delete(g); + return false; + } + } + } + } + } + game_delete(g); + return true; +} + +/* ************** game_get_flags ************** */ +bool test_game_get_flags(void) { + game g = game_new_empty(); + square list_flag[] = {F_ERROR, F_LIGHTED, (F_ERROR | F_LIGHTED)}; + for (uint i = 0; i < DEFAULT_SIZE; i++) { + for (uint j = 0; j < DEFAULT_SIZE; j++) { + for (uint flag_index = 0; flag_index < sizeof(list_flag) / sizeof(list_flag[0]); flag_index++) { + game_set_square(g, i, j, (S_BLANK | list_flag[flag_index])); + if (game_get_flags(g, i, j) != list_flag[flag_index]) { + game_delete(g); + return false; + } + } + } + } + game_delete(g); + return true; +} + +/* ************** game_is_marked ************** */ +bool test_game_is_marked(void) { + game g = game_new_empty(); + square list_flag[] = {S_BLANK, F_ERROR, F_LIGHTED, (F_ERROR | F_LIGHTED)}; + for (uint i = 0; i < DEFAULT_SIZE; i++) { + for (uint j = 0; j < DEFAULT_SIZE; j++) { + for (uint flag_index = 0; flag_index < sizeof(list_flag) / sizeof(list_flag[0]); flag_index++) { + game_set_square(g, i, j, (S_MARK | list_flag[flag_index])); + if ((game_get_state(g, i, j) == S_MARK) && game_is_marked(g, i, j)) { + game_delete(g); + return true; + } + } + } + } + game_delete(g); + return false; +} + +/* ************** game_is_lighted ************** */ +bool test_game_is_lighted(void) { + game g = game_new_empty(); + square list_flag[] = {F_LIGHTED, (F_ERROR | F_LIGHTED)}; + for (uint i = 0; i < DEFAULT_SIZE; i++) { + for (uint j = 0; j < DEFAULT_SIZE; j++) { + for (uint flag_index = 0; flag_index < sizeof(list_flag) / sizeof(list_flag[0]); flag_index++) { + game_set_square(g, i, j, (S_BLANK | list_flag[flag_index])); + if ((game_get_flags(g, i, j) == list_flag[flag_index]) && game_is_lighted(g, i, j)) { + game_delete(g); + return true; + } + } + } + } + game_delete(g); + return false; +} + +/* ************** game_has_error ************** */ +bool test_game_has_error(void) { + game g = game_new_empty(); + square list_flag[] = {F_ERROR, (F_ERROR | F_LIGHTED)}; + for (uint i = 0; i < DEFAULT_SIZE; i++) { + for (uint j = 0; j < DEFAULT_SIZE; j++) { + for (uint flag_index = 0; flag_index < sizeof(list_flag) / sizeof(list_flag[0]); flag_index++) { + game_set_square(g, i, j, (S_BLANK | list_flag[flag_index])); + if ((game_get_flags(g, i, j) == list_flag[flag_index]) && game_has_error(g, i, j)) { + game_delete(g); + return true; + } + } + } + } + game_delete(g); + return false; +} + +/* ************** game_nb_rows ************** */ +bool test_game_nb_rows(void) { + game g = game_new_empty(); + if (game_get_square(g, game_nb_rows(g) - 1, 0) == S_BLANK) { + game_delete(g); + return true; + } + game_delete(g); + return false; +} + +/* ************** game_nb_cols ************** */ +bool test_game_nb_cols(void) { + game g = game_new_empty(); + if (game_get_square(g, 0, game_nb_cols(g) - 1) == S_BLANK) { + game_delete(g); + return true; + } + game_delete(g); + return false; +} + +/* ************** game_is_wrapping ************** */ +bool test_game_is_wrapping(void) { + game g = game_new_empty(); + game_set_square(g, 0, 0, S_LIGHTBULB); + game_set_square(g, 1, 0, S_BLACK); + game_set_square(g, 0, 1, S_BLACK); + for (uint i = 0; i < DEFAULT_SIZE; i++) { + if (!(game_is_lighted(g, 0, i) && game_is_wrapping(g))) { + game_delete(g); + return true; + } + if (!(game_is_lighted(g, i, 0) && game_is_wrapping(g))) { + game_delete(g); + return true; + } + } + game_delete(g); + return false; +} + +int main(int argc, char* argv[]) { + if (argc != 2) { + usage(argc, argv); + } + fprintf(stderr, "=> Start test \"%s\"\n", argv[1]); + bool success = false; + if (strcmp("dummy", argv[1]) == 0) + success = test_dummy(); + else if (strcmp("game_set_square", argv[1]) == 0) { + success = test_game_set_square(); + } else if (strcmp("game_get_state", argv[1]) == 0) { + success = test_game_get_state(); + } else if (strcmp("game_get_flags", argv[1]) == 0) { + success = test_game_get_flags(); + } else if (strcmp("game_is_marked", argv[1]) == 0) { + success = test_game_is_marked(); + } else if (strcmp("game_is_lighted", argv[1]) == 0) { + success = test_game_is_lighted(); + } else if (strcmp("game_has_error", argv[1]) == 0) { + success = test_game_has_error(); + } else if (strcmp("game_nb_rows", argv[1]) == 0) { + success = test_game_nb_rows(); + } else if (strcmp("game_nb_cols", argv[1]) == 0) { + success = test_game_nb_cols(); + } else if (strcmp("game_is_wrapping", argv[1]) == 0) { + success = test_game_is_wrapping(); + } else { + fprintf(stderr, "Error: test \"%s\" not found!\n", argv[1]); + exit(EXIT_FAILURE); + } + if (success) { + fprintf(stderr, "Test \"%s\" finished: SUCCESS\n", argv[1]); + return EXIT_SUCCESS; + } else { + fprintf(stderr, "Test \"%s\" finished: FAILURE\n", argv[1]); + return EXIT_FAILURE; + } +} \ No newline at end of file diff --git a/game_test_thoguet.c b/game_test_thoguet.c new file mode 100644 index 0000000..fd4222c --- /dev/null +++ b/game_test_thoguet.c @@ -0,0 +1,522 @@ +#include +#include +#include +#include +#include +#include +#include "game.h" +#include "game_aux.h" +#include "game_ext.h" + +#define SIZE_LIMIT_GAME 10 +// put usable square at the start of the list and S_BLANK at the first element +#define LIST_OF_SQUARE \ + { S_BLANK, S_LIGHTBULB, S_MARK, S_BLACK0, S_BLACK1, S_BLACK2, S_BLACK3, S_BLACK4, S_BLACKU, F_LIGHTED, F_ERROR } +#define USABLE_SQUARE 2 + +/* ********** DUMMY ********** */ +bool test_dummy(void) { + return EXIT_SUCCESS; +} + +/* ******* game_new_ext_empty ******* */ +/** + * @brief test creating every game between size 0 to SIZE_LIMIT_GAME + * + * @return EXIT_SUCCESS if every game has been created well + */ +bool test_game_new_empty_ext(void) { + for (uint height = 1; height <= SIZE_LIMIT_GAME; height++) { + for (uint width = 1; width <= SIZE_LIMIT_GAME; width++) { + for (int wrap = 0; wrap < 2; wrap++) { + game g = game_new_empty_ext(height, width, wrap); + assert(g); + game_delete(g); + g = NULL; + } + } + } + return EXIT_SUCCESS; +} + +/* ******* game_new_ext ******* */ +/** + * @brief test creating every game between size 0 to SIZE_LIMIT_GAME + fill it with every square of list + * + * @return EXIT_SUCCESS if every game has been created well and well filled + */ +bool test_game_new_ext(void) { + square list[] = LIST_OF_SQUARE; + for (uint height = 1; height <= SIZE_LIMIT_GAME; height++) { + for (uint width = 1; width <= SIZE_LIMIT_GAME; width++) { + square tab[height * width]; + for (uint index_list = 0; index_list < sizeof(list) / sizeof(list[0]); index_list++) { + for (uint index_tab = 0; index_tab < height * width; index_tab++) { + tab[index_tab] = list[index_list]; + } + for (int wrap = 0; wrap < 2; wrap++) { + printf("%d\n", index_list); + game g = game_new_ext(height, width, tab, wrap); + assert(g); + for (uint i = 0; i < game_nb_rows(g); i++) { + for (uint j = 0; j < game_nb_cols(g); j++) { + printf("list :%d, get square :%d\n", list[index_list], game_get_square(g, i, j)); + assert(list[index_list] == game_get_square(g, i, j)); + } + } + game_delete(g); + g = NULL; + } + } + } + } + return EXIT_SUCCESS; +} + +/* ******* game_is_over ******* */ +/** + * @brief test if game_is_over return true with default_solution, false with one flag F_ERROR, false with one S_BLANK and false with game_default + * + * @return EXIT_SUCCESS if there is no errors + */ +bool test_game_is_over(void) { + game g = game_default_solution(); + game_update_flags(g); + assert(game_is_over(g)); + game_set_square(g, 0, 0, (S_LIGHTBULB | F_LIGHTED | F_ERROR)); + assert(!game_is_over(g)); + game_update_flags(g); + game_set_square(g, 0, 1, S_BLANK); + assert(!game_is_over(g)); + game_delete(g); + g = game_default(); + game_update_flags(g); + assert(!game_is_over(g)); + game_delete(g); + return EXIT_SUCCESS; +} + +/* ******* game_restart ******* */ +/** + * @brief test if game_restart remove every S_LIGHTBULB, S_MARK, and F_LIGHTED but nothing else and if the history has been reset + * + * @return EXIT_SUCCESS if there is no error + */ +bool test_game_restart(void) { + game game_def = game_default(); + game game_def_solution = game_default_solution(); + game_set_square(game_def, 1, 5, S_BLACK4); + game_set_square(game_def_solution, 1, 5, S_BLACK4); + game_set_square(game_def, 3, 2, S_BLACK3); + game_set_square(game_def_solution, 3, 2, S_BLACK3); + game_play_move(game_def, 0, 1, S_LIGHTBULB); + game_play_move(game_def_solution, 0, 1, S_LIGHTBULB); + game_play_move(game_def, 2, 0, S_MARK); + game_play_move(game_def_solution, 2, 0, S_MARK); + game_play_move(game_def, 1, 1, S_LIGHTBULB); + game_play_move(game_def_solution, 1, 1, S_LIGHTBULB); + game_play_move(game_def, 1, 1, S_MARK); + game_play_move(game_def_solution, 1, 1, S_MARK); + game_undo(game_def); + game_undo(game_def_solution); + assert(!game_equal(game_def, game_def_solution)); + game_restart(game_def_solution); + game_restart(game_def); + assert(game_equal(game_def, game_def_solution)); + game_redo(game_def); + assert(game_equal(game_def, game_def_solution)); + game_undo(game_def); + assert(game_equal(game_def, game_def_solution)); + game_delete(game_def); + game_delete(game_def_solution); + return EXIT_SUCCESS; +} + +/* ********* game_new ********* */ +/** + * @brief test if created game with game_new are well filled by the tab + * + * @return EXIT_SUCCESS if there is no error + */ +bool test_game_new(void) { + square tab[DEFAULT_SIZE * DEFAULT_SIZE]; + square list[] = LIST_OF_SQUARE; + for (uint index_list = 0; index_list < sizeof(list) / sizeof(list[0]); index_list++) { + for (uint index_tab = 0; index_tab < DEFAULT_SIZE * DEFAULT_SIZE; index_tab++) { + tab[index_tab] = list[index_list]; + } + printf("%d\n", index_list); + game g = game_new(tab); + for (uint i = 0; i < DEFAULT_SIZE; i++) { + for (uint j = 0; j < DEFAULT_SIZE; j++) { + printf("list :%d, get square :%d\n", list[index_list], game_get_square(g, i, j)); + assert(list[index_list] == game_get_square(g, i, j)); + } + } + assert(g); + game_delete(g); + g = NULL; + } + return EXIT_SUCCESS; +} + +/* ******** game_print ******** */ +/** + * @brief test if there is no major problems by printing every square of list + * + * @return EXIT_SUCCESS if there is no error + */ +bool test_game_print(void) { + for (int height = 1; height <= SIZE_LIMIT_GAME; height++) { + for (int width = 1; width <= SIZE_LIMIT_GAME; width++) { + square tab[width * height]; + square list[] = LIST_OF_SQUARE; + game g; + for (uint index_list = 0; index_list < sizeof(list) / sizeof(list[0]); index_list++) { + for (uint index_tab = 0; index_tab < width * height; index_tab++) { + tab[index_tab] = list[index_list]; + } + printf("%d\n", list[index_list]); + g = game_new_ext(height, width, tab, false); + assert(g); + game_print(g); + game_delete(g); + } + } + } + return EXIT_SUCCESS; +} + +/* ******* game_get_square ******* */ +/** + * @brief test if get_square return the same as tab, used to create the game + * + * @return EXIT_SUCCESS if there is no error + */ +bool test_game_get_square(void) { + for (int height = 1; height <= SIZE_LIMIT_GAME; height++) { + for (int width = 1; width <= SIZE_LIMIT_GAME; width++) { + square tab[width * height]; + square list[] = LIST_OF_SQUARE; + for (uint index_list = 0; index_list < sizeof(list) / sizeof(list[0]); index_list++) { + for (uint index_tab = 0; index_tab < height * width; index_tab++) { + tab[index_tab] = list[index_list]; + game g = game_new_ext(height, width, tab, false); + printf("[%d, %d] %d %d\n", index_tab / game_nb_cols(g), index_tab % game_nb_cols(g), + game_get_square(g, index_tab / game_nb_cols(g), index_tab % game_nb_cols(g)), tab[index_tab]); + assert(game_get_square(g, index_tab / game_nb_cols(g), index_tab % game_nb_cols(g)) == tab[index_tab]); + game_delete(g); + } + } + } + } + return EXIT_SUCCESS; +} + +/* ******* game_default ******* */ +/** + * @brief test if the game_default is the same as tab (tab is game_default) + * + * @return EXIT_SUCCESS if there is no error + */ +bool test_game_default(void) { + game game_def = game_default(); + assert(game_def); + // clang-format off + square tab[49] = + { + /*X0 X1 X2 X3 X4 X5 X6*/ + /*Y 0*/ S_BLANK, S_BLANK, S_BLACK1, S_BLANK, S_BLANK, S_BLANK, S_BLANK, + /*Y 1*/ S_BLANK, S_BLANK, S_BLACK2, S_BLANK, S_BLANK, S_BLANK, S_BLANK, + /*Y 2*/ S_BLANK, S_BLANK, S_BLANK, S_BLANK, S_BLANK, S_BLACKU, S_BLACK2, + /*Y 3*/ S_BLANK, S_BLANK, S_BLANK, S_BLANK, S_BLANK, S_BLANK, S_BLANK, + /*Y 4*/ S_BLACK1, S_BLACKU, S_BLANK, S_BLANK, S_BLANK, S_BLANK, S_BLANK, + /*Y 5*/ S_BLANK, S_BLANK, S_BLANK, S_BLANK, S_BLACK2, S_BLANK, S_BLANK, + /*Y 6*/ S_BLANK, S_BLANK, S_BLANK, S_BLANK, S_BLACKU, S_BLANK, S_BLANK + }; + // clang-format on + game game_test = game_new(tab); + assert(game_equal(game_def, game_test)); + game_delete(game_def); + game_delete(game_test); + return EXIT_SUCCESS; +} + +/* *** game_default_solution *** */ +/** + * @brief test if game_default_solution is the same as game_default with moves needed to win + * + * @return EXIT_SUCCESS if there is no error + */ +bool test_game_default_solution(void) { + game game_def_solution = game_default_solution(); + game game_def = game_default(); + assert(game_def_solution); + // solution + game_set_square(game_def, 0, 0, S_LIGHTBULB); + game_set_square(game_def, 1, 1, S_LIGHTBULB); + game_set_square(game_def, 0, 3, S_LIGHTBULB); + game_set_square(game_def, 1, 6, S_LIGHTBULB); + game_set_square(game_def, 2, 2, S_LIGHTBULB); + game_set_square(game_def, 3, 6, S_LIGHTBULB); + game_set_square(game_def, 4, 4, S_LIGHTBULB); + game_set_square(game_def, 5, 0, S_LIGHTBULB); + game_set_square(game_def, 5, 5, S_LIGHTBULB); + game_set_square(game_def, 6, 1, S_LIGHTBULB); + game_update_flags(game_def); + assert(game_equal(game_def_solution, game_def)); + game_delete(game_def_solution); + game_delete(game_def); + return EXIT_SUCCESS; +} + +/* **** game_update_flags **** */ +/** + * @brief count the number of F_ERROR on the entire game g + * + * @param g the game to analyze + * @return uint the number of F_ERROR on the game g + */ +uint counterF_ERROR(game g) { + uint cpt = 0; + for (uint i = 0; i < game_nb_rows(g); i++) { + for (uint j = 0; j < game_nb_cols(g); j++) { + if (game_has_error(g, i, j)) + cpt++; + } + } + return cpt; +} +/* +Tests without the wrapping option + +test on a 2x2 game without *wrapping* +test on a 3x3 game without *wrapping* +test on a 5x3 game without *wrapping* +test flags *error* on specifics cases + +On the 2x2, 3x3 and 5x3 tests, we play these move to win and we test if the grid is correct + + 01 012 012 + -- --- --- +0 |2*| 0 |.w0| 0 |*..| +1 |*.| 1 |*1w| 1 |w*1| + -- 2 |..*| 2 |*2.| + (2x2) --- 3 |..*| + (3x3) 4 |.*.| + --- + (5x3) + +Tests of the wrapping option + +test on a 2x2 game with option *wrapping* (cas limite) +test on a 3x3 game with option *wrapping* +test on a 5x3 game with option *wrapping* +test of the flags *error* in the specifics cases of the option *wrapping* + +On the 2x2, 3x3 and 5x3 tests, we play these move to win and we test if the grid is correct + + 01 012 012 + -- --- --- +0 |4*| 0 |*w2| 0 |...| +1 |*.| 1 |.ww| 1 |w*1| + -- 2 |..*| 2 |*2.| + (2x2) --- 3 |..*| + (3x3) 4 |...| + --- + (5x3) +*/ +bool test_game_update_flags(void) { + square test2x2w[4] = {S_BLACK4, S_BLANK, S_BLANK, S_BLANK}; + square test3x3w[9] = {S_BLANK, S_BLACKU, S_BLACK2, S_BLANK, S_BLACKU, S_BLACKU, S_BLANK, S_BLANK, S_BLANK}; + square test2x2[4] = {S_BLACK2, S_BLANK, S_BLANK, S_BLANK}; + square test3x3[9] = {S_BLANK, S_BLACKU, S_BLACK0, S_BLANK, S_BLACK1, S_BLACKU, S_BLANK, S_BLANK, S_BLANK}; + square test5x3[15] = {S_BLANK, S_BLANK, S_BLANK, S_BLACKU, S_BLANK, S_BLACK1, S_BLANK, S_BLACK2, + S_BLANK, S_BLANK, S_BLANK, S_BLANK, S_BLANK, S_BLANK, S_BLANK}; + // with wrapping + game g2x2w = game_new_ext(2, 2, test2x2w, true); + game_play_move(g2x2w, 0, 1, S_LIGHTBULB); + game_play_move(g2x2w, 1, 0, S_LIGHTBULB); + assert(game_is_over(g2x2w)); + game g3x3w = game_new_ext(3, 3, test3x3w, true); + game_play_move(g3x3w, 0, 0, S_LIGHTBULB); + game_play_move(g3x3w, 2, 2, S_LIGHTBULB); + assert(game_is_over(g3x3w)); + game_delete(g3x3w); + game g5x3w = game_new_ext(5, 3, test5x3, true); + game_play_move(g5x3w, 1, 1, S_LIGHTBULB); + game_play_move(g5x3w, 2, 0, S_LIGHTBULB); + game_play_move(g5x3w, 3, 2, S_LIGHTBULB); + assert(game_is_over(g5x3w)); + game_restart(g2x2w); + game_restart(g5x3w); + game_play_move(g2x2w, 0, 1, S_MARK); + assert(!game_is_over(g2x2w)); + assert(game_has_error(g2x2w, 0, 0)); + assert(counterF_ERROR(g2x2w) == 1); + game_delete(g2x2w); + game_play_move(g5x3w, 0, 2, S_LIGHTBULB); + game_play_move(g5x3w, 2, 2, S_LIGHTBULB); + game_play_move(g5x3w, 1, 1, S_LIGHTBULB); + game_play_move(g5x3w, 2, 0, S_LIGHTBULB); + assert(!game_is_over(g5x3w)); + assert(game_has_error(g5x3w, 0, 2)); + assert(game_has_error(g5x3w, 1, 2)); + assert(game_has_error(g5x3w, 2, 2)); + assert(game_has_error(g5x3w, 2, 0)); + assert(game_has_error(g5x3w, 2, 1)); + assert(counterF_ERROR(g5x3w) == 5); + game_delete(g5x3w); + // without wrapping + game g2x2 = game_new_ext(2, 2, test2x2, false); + game_play_move(g2x2, 0, 1, S_LIGHTBULB); + game_play_move(g2x2, 1, 0, S_LIGHTBULB); + assert(game_is_over(g2x2)); + game g3x3 = game_new_ext(3, 3, test3x3, false); + game_play_move(g3x3, 1, 0, S_LIGHTBULB); + game_play_move(g3x3, 2, 2, S_LIGHTBULB); + assert(game_is_over(g3x3)); + game_delete(g3x3); + game g5x3 = game_new_ext(5, 3, test5x3, false); + game_play_move(g5x3, 0, 0, S_LIGHTBULB); + game_play_move(g5x3, 1, 1, S_LIGHTBULB); + game_play_move(g5x3, 2, 0, S_LIGHTBULB); + game_play_move(g5x3, 3, 2, S_LIGHTBULB); + game_play_move(g5x3, 4, 1, S_LIGHTBULB); + assert(game_is_over(g5x3)); + game_restart(g2x2); + game_restart(g5x3); + game_play_move(g2x2, 1, 0, S_MARK); + assert(!game_is_over(g2x2)); + assert(game_has_error(g2x2, 0, 0)); + assert(counterF_ERROR(g2x2) == 1); + game_delete(g2x2); + game_play_move(g5x3, 0, 2, S_LIGHTBULB); + game_play_move(g5x3, 1, 1, S_LIGHTBULB); + game_play_move(g5x3, 2, 0, S_LIGHTBULB); + game_play_move(g5x3, 2, 2, S_LIGHTBULB); + game_play_move(g5x3, 3, 2, S_LIGHTBULB); + game_play_move(g5x3, 4, 1, S_LIGHTBULB); + assert(!game_is_over(g5x3)); + assert(game_has_error(g5x3, 1, 2)); + assert(game_has_error(g5x3, 2, 1)); + assert(game_has_error(g5x3, 2, 2)); + assert(game_has_error(g5x3, 3, 2)); + assert(counterF_ERROR(g5x3) == 4); + game_delete(g5x3); + return EXIT_SUCCESS; +} + +/* ****** game_play_move ****** */ +/** + * @brief test if play move set square + update the flags + update history + * + * @return EXIT_SUCCESS if there is no error + */ +bool test_game_play_move(void) { + game game_play = game_new_empty(); + game game_set = game_new_empty(); + square list[] = LIST_OF_SQUARE; + for (uint index_list = 0; index_list <= USABLE_SQUARE; index_list++) { + game_play_move(game_play, 0, 0, list[index_list]); + game_set_square(game_set, 0, 0, list[index_list]); + game_update_flags(game_set); + assert(game_equal(game_play, game_set)); + } + // undo / redo + game_undo(game_play); + game_set_square(game_set, 0, 0, list[USABLE_SQUARE - 1]); // take the before last usable element of list + game_update_flags(game_set); + assert(game_equal(game_play, game_set)); + // delete + game_delete(game_play); + game_delete(game_set); + return EXIT_SUCCESS; +} + +/* ***** game_check_move ***** */ +/** + * @brief test if we can place usable square (here : S_BLANK, S_LIGHTBULB, S_MARK) and nothing else + if we can place something over the limits + * + * @return EXIT_SUCCESS if there is no error + */ +bool test_game_check_move(void) { + game g = game_new_empty(); + // put usable square at the start of the list + square list[] = LIST_OF_SQUARE; + // test limits + for (uint i = 0; i < SIZE_LIMIT_GAME + 10; i++) { + for (uint j = 0; j < SIZE_LIMIT_GAME + 10; j++) { + for (uint index_list = 0; index_list < sizeof(list) / sizeof(list[0]); index_list++) { + if (i >= game_nb_rows(g) || j >= game_nb_cols(g) || index_list > USABLE_SQUARE) + assert(!game_check_move(g, i, j, list[index_list])); + else + assert(game_check_move(g, i, j, list[index_list])); + } + } + } + // test if we can place something on a wall + for (uint i = 0; i < S_BLACKU - S_BLACK0; i++) { + game_set_square(g, 0, 0, S_BLACK + i); + for (uint index_list = 0; index_list <= USABLE_SQUARE; index_list++) { + assert(!game_check_move(g, 0, 0, list[index_list])); + } + } + game_delete(g); + return EXIT_SUCCESS; +} + +/* ********** USAGE ********** */ + +int usage(int argc, char* argv[]) { + fprintf(stderr, "Usage: %s [<...>]\n", argv[0]); + exit(EXIT_FAILURE); +} + +/* ********** MAIN ********** */ + +int main(int argc, char* argv[]) { + if (argc == 1) { + usage(argc, argv); + } + fprintf(stderr, "=> Start test \"%s\"\n", argv[1]); + bool ok = true; + if (strcmp("dummy", argv[1]) == 0) + ok = test_dummy(); + else if (strcmp("game_print", argv[1]) == 0) + ok = test_game_print(); + else if (strcmp("game_default", argv[1]) == 0) + ok = test_game_default(); + else if (strcmp("game_default_solution", argv[1]) == 0) + ok = test_game_default_solution(); + else if (strcmp("game_new", argv[1]) == 0) + ok = test_game_new(); + else if (strcmp("game_restart", argv[1]) == 0) + ok = test_game_restart(); + else if (strcmp("game_get_square", argv[1]) == 0) + ok = test_game_get_square(); + else if (strcmp("game_is_over", argv[1]) == 0) + ok = test_game_is_over(); + else if (strcmp("game_update_flags", argv[1]) == 0) + ok = test_game_update_flags(); + else if (strcmp("game_play_move", argv[1]) == 0) + ok = test_game_play_move(); + else if (strcmp("game_check_move", argv[1]) == 0) + ok = test_game_check_move(); + else if (strcmp("game_new_empty_ext", argv[1]) == 0) + ok = test_game_new_empty_ext(); + else if (strcmp("game_new_ext", argv[1]) == 0) + ok = test_game_new_ext(); + else { + fprintf(stderr, "Error: test \"%s\" not found!\n", argv[1]); + exit(EXIT_FAILURE); + } + // print test result + if (!ok) { + fprintf(stderr, "Test \"%s\" finished: SUCCESS\n", argv[1]); + return EXIT_SUCCESS; + } else { + fprintf(stderr, "Test \"%s\" finished: FAILURE\n", argv[1]); + return EXIT_FAILURE; + } +} \ No newline at end of file diff --git a/game_test_thperrier.c b/game_test_thperrier.c new file mode 100644 index 0000000..62c0f0e --- /dev/null +++ b/game_test_thperrier.c @@ -0,0 +1,323 @@ +#include +#include +#include +#include +#include +#include "game.h" +#include "game_aux.h" +#include "game_ext.h" + +#define TAB_SQUARE \ + { S_BLANK, S_LIGHTBULB, S_MARK, S_BLACK0, S_BLACK1, S_BLACK2, S_BLACK3, S_BLACK4, S_BLACKU } + +/* ********** DUMMY ********** */ +bool test_dummy() { + return true; +} + +/* ********** game_get_black_number ********** */ +bool test_game_get_black_number() { + square tab_square[] = TAB_SQUARE; + game g = game_new_empty(); + for (int i = 0; i < game_nb_rows(g); i++) { + for (int j = 0; j < game_nb_cols(g); j++) { + // check for each case given in tab_square if the function return the expected result + for (int tab_index = 0; tab_index < (sizeof(tab_square) / sizeof(tab_square[0])); tab_index++) { + game_set_square(g, i, j, tab_square[tab_index]); + if (tab_square[tab_index] == S_BLACKU) { + if (game_get_black_number(g, i, j) != -1) { + game_delete(g); + return false; + } + } else if (tab_square[tab_index] > S_MARK && game_get_black_number(g, i, j) != tab_square[tab_index] - S_BLACK) { + game_delete(g); + return false; + } + } + } + } + game_delete(g); + return true; +} + +/* ********** game_is_black ********** */ +bool test_game_is_black() { + square tab_square[] = TAB_SQUARE; + game g = game_new_empty(); + for (int i = 0; i < game_nb_rows(g); i++) { + for (int j = 0; j < game_nb_cols(g); j++) { + // check for each case given in tab_square if the function return the expected result + for (int tab_index = 0; tab_index < (sizeof(tab_square) / sizeof(tab_square[0])); tab_index++) { + game_set_square(g, i, j, tab_square[tab_index]); + if (tab_square[tab_index] <= S_MARK) { + if (game_is_black(g, i, j)) { + game_delete(g); + return false; + } + } else { + if (!((game_get_square(g, i, j) == tab_square[tab_index]) && game_is_black(g, i, j))) { + game_delete(g); + return false; + } + } + } + } + } + + game_delete(g); + return true; +} + +/* ********** game_is_lightbulb ********** */ +bool test_game_is_lightbulb() { + square tab_square[] = TAB_SQUARE; + game g = game_new_empty(); + for (int i = 0; i < game_nb_rows(g); i++) { + for (int j = 0; j < game_nb_cols(g); j++) { + // check for each case given in tab_square if the function return the expected result + for (int tab_index = 0; tab_index < (sizeof(tab_square) / sizeof(tab_square[0])); tab_index++) { + game_set_square(g, i, j, tab_square[tab_index]); + if (tab_square[tab_index] == S_LIGHTBULB) { + if (!game_is_lightbulb(g, i, j)) { + game_delete(g); + return false; + } + } else if (game_is_lightbulb(g, 0, 0)) { + game_delete(g); + return false; + } + } + } + } + game_delete(g); + return true; +} + +/* ********** game_is_blank ********** */ +bool test_game_is_blank() { + square tab_square[] = {S_LIGHTBULB, S_MARK, S_BLACK0, S_BLACK1, S_BLACK2, S_BLACK3, S_BLACK4, S_BLACKU}; + game g = game_new_empty(); + game_set_square(g, 0, 0, S_BLANK); + if (!((game_get_square(g, 0, 0) == S_BLANK) && game_is_blank(g, 0, 0))) { + game_delete(g); + return false; + } + for (int i = 0; i < game_nb_rows(g); i++) { + for (int j = 0; j < game_nb_cols(g); j++) { + // check for each case given in tab_square if the function return the expected result + for (int tab_index = 0; tab_index < (sizeof(tab_square) / sizeof(tab_square[0])); tab_index++) { + game_set_square(g, i, j, tab_square[tab_index]); + if (game_is_blank(g, i, j)) { + game_delete(g); + return false; + } + } + } + } + game_delete(g); + return true; +} + +/* ********** game_delete ********** */ +bool test_game_delete() { + game g = game_new_empty(); + assert(g); + game_delete(g); + return true; +} + +/* ********** game_equal ********** */ +bool test_game_equal() { + square tab_square[] = {S_BLANK, S_LIGHTBULB, S_MARK, S_BLACK0, S_BLACK1, S_BLACK2, S_BLACK3, S_BLACK4, S_BLACKU}; + bool wrap[] = {false, false, true, false, false, true, true, true}; + bool equal = true; + uint index_tab = 0; + for (int wrap_index = 0; wrap_index < sizeof(wrap) / sizeof(wrap[0]); wrap_index += 2) { + game g1 = game_new_empty_ext(DEFAULT_SIZE, DEFAULT_SIZE, wrap[wrap_index]); + game g2 = game_new_empty_ext(DEFAULT_SIZE, DEFAULT_SIZE, wrap[wrap_index + 1]); + for (int i = 0; i < game_nb_rows(g1); i++) { + for (int j = 0; j < game_nb_cols(g1); j++) { + // check if index_tab reach end of tab + if (index_tab == sizeof(tab_square) / sizeof(tab_square[0])) { + index_tab = 0; + } + game_set_square(g1, i, j, tab_square[index_tab]); + game_set_square(g2, i, j, tab_square[index_tab]); + index_tab++; + } + } + // check if the two games have the same height, width, and if the two have the wrapping option or not + if (game_nb_rows(g1) != game_nb_rows(g2) || game_nb_cols(g1) != game_nb_cols(g2) || game_is_wrapping(g1) != game_is_wrapping(g2)) { + equal = false; + } + // check if each square is the same on each game + else { + for (int i = 0; i < game_nb_rows(g1); i++) { + for (int j = 0; j < game_nb_cols(g1); j++) { + if (game_get_square(g1, i, j) != game_get_square(g2, i, j)) { + equal = false; + } + } + } + } + // check if game_equal return the expected result + equal = (equal == game_equal(g1, g2)); + game_set_square(g1, 0, 0, S_LIGHTBULB); + game_update_flags(g1); + game_update_flags(g2); + assert(!game_equal(g1, g2)); + game_delete(g1); + game_delete(g2); + } + return equal; +} + +/* ********** game_new_empty ********** */ +bool test_game_new_empty() { + game g = game_new_empty(); + // check if each square is a S_BLANK + for (int i = 0; i < game_nb_rows(g); i++) { + for (int j = 0; j < game_nb_cols(g); j++) { + if (game_get_square(g, i, j) != S_BLANK) { + game_delete(g); + return false; + } + } + } + game_delete(g); + return true; +} + +/* ********** game_copy ********** */ +bool test_game_copy() { + square tab_square[] = {S_BLANK, S_LIGHTBULB, S_MARK, S_BLACK0, S_BLACK1, S_BLACK2, S_BLACK3, S_BLACK4, S_BLACKU}; + uint index_tab = 0; + for (int wrap = 0; wrap < 2 /*we only need to test two versions (with and without wrapping)*/; wrap++) { + game g1 = game_new_empty_ext(DEFAULT_SIZE, DEFAULT_SIZE, wrap); + for (int i = 0; i < game_nb_rows(g1); i++) { + for (int j = 0; j < game_nb_cols(g1); j++) { + // check if index_tab reach end of tab + if (index_tab == sizeof(tab_square) / sizeof(tab_square[0])) { + index_tab = 0; + } + game_set_square(g1, i, j, tab_square[index_tab]); + index_tab++; + } + } + game g2 = game_copy(g1); + // check if g2 is a correct copy of g1 + if (!game_equal(g1, g2)) { + game_delete(g1); + game_delete(g2); + return false; + } + game_delete(g1); + game_delete(g2); + } + return true; +} + +/* ********** game_undo ********** */ +bool test_game_undo() { + game g1 = game_new_empty(); + game g2 = game_copy(g1); + // check if you can undo without playing a move + game_undo(g2); + if (!game_equal(g1, g2)) { + game_delete(g1); + game_delete(g2); + return false; + } + game_play_move(g1, 0, 0, S_LIGHTBULB); + game_play_move(g1, 1, 1, S_MARK); + game g3 = game_copy(g1); + // check if the function undo correctly + game_play_move(g3, 2, 2, S_LIGHTBULB); + game_undo(g3); + if (!game_equal(g1, g3)) { + game_delete(g1); + game_delete(g2); + game_delete(g3); + return false; + } + game_delete(g1); + game_delete(g2); + game_delete(g3); + return true; +} + +/* ********** game_redo ********** */ +bool test_game_redo() { + game g1 = game_new_empty(); + game_play_move(g1, 0, 0, S_LIGHTBULB); + game_play_move(g1, 1, 1, S_MARK); + game g2 = game_copy(g1); + // check if you can redo a non existant move + game_undo(g1); + game_redo(g1); + game_redo(g1); + if (!game_equal(g1, g2)) { + game_delete(g1); + game_delete(g2); + return false; + } + game_play_move(g1, 2, 2, S_LIGHTBULB); + game_play_move(g2, 2, 2, S_LIGHTBULB); + // check if the function redo correctly + game_undo(g1); + game_redo(g1); + if (!game_equal(g1, g2)) { + game_delete(g1); + game_delete(g2); + return false; + } + game_delete(g1); + game_delete(g2); + return true; +} + +/* ********** usage ********** */ +void usage(int argc, char* argv[]) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(EXIT_FAILURE); +} +/* ********** main ********** */ +int main(int argc, char* argv[]) { + bool success = false; + if (argc != 2) { + usage(argc, argv); + } else if (strcmp("dummy", argv[1]) == 0) { + success = test_dummy(); + } else if (strcmp("game_new_empty", argv[1]) == 0) { + success = test_game_new_empty(); + } else if (strcmp("game_copy", argv[1]) == 0) { + success = test_game_copy(); + } else if (strcmp("game_equal", argv[1]) == 0) { + success = test_game_equal(); + } else if (strcmp("game_delete", argv[1]) == 0) { + success = test_game_delete(); + } else if (strcmp("game_is_blank", argv[1]) == 0) { + success = test_game_is_blank(); + } else if (strcmp("game_is_lightbulb", argv[1]) == 0) { + success = test_game_is_lightbulb(); + } else if (strcmp("game_is_black", argv[1]) == 0) { + success = test_game_is_black(); + } else if (strcmp("game_get_black_number", argv[1]) == 0) { + success = test_game_get_black_number(); + } else if (strcmp("game_undo", argv[1]) == 0) { + success = test_game_undo(); + } else if (strcmp("game_redo", argv[1]) == 0) { + success = test_game_redo(); + } else { + fprintf(stderr, "test \"%s\" is not a valid test name.\n", argv[1]); + return EXIT_FAILURE; + } + if (success) { + fprintf(stderr, "test \"%s\" finished successfully\n", argv[1]); + return EXIT_SUCCESS; + } else { + fprintf(stderr, "test \"%s\" error\n", argv[1]); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/game_text.c b/game_text.c new file mode 100644 index 0000000..6e7e089 --- /dev/null +++ b/game_text.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include "game.h" +#include "game_aux.h" +#include "game_ext.h" + +#define NBCHARERR 4 +#define CHAR0ASCII 48 + +/** + * @brief Edit the stringError with all errors of g + * @param g the game to analyze + * @param stringError A pointer toward a string allocated before + * @pre @p stringError size must be at least height * width * NBCHARERR + * @return true if there is an error on the game g and the stringError has been modified + */ +bool checkerrors(cgame g, char* stringError) { + if (stringError == NULL || g == NULL) { + return -1; + } + uint index_taberr = 0; + // initialize stringError + stringError[0] = '\0'; + for (int i = 0; i < game_nb_rows(g); i++) { + for (int j = 0; j < game_nb_cols(g); j++) { + // check each case + if (game_has_error(g, i, j)) { + // if error append stringError with coordinate of the error, + stringError[index_taberr] = i + CHAR0ASCII; + index_taberr++; + stringError[index_taberr] = ':'; + index_taberr++; + stringError[index_taberr] = j + CHAR0ASCII; + index_taberr++; + stringError[index_taberr] = ' '; + index_taberr++; + } + } + } + // if stringError changed return true + if (stringError[0] != '\0') { + stringError[index_taberr] = '\0'; + return true; + } + return false; +} + +/** + * @brief scan the coordinate and check if the move is possible if yes play it + * @param g the game to check and play + * @param s the square to update + */ +void checkPlayMove(game g, square s) { + uint i; + uint j; + if (scanf(" %u %u", &i, &j) == 2) { + if (game_check_move(g, i, j, s)) + game_play_move(g, i, j, s); + else + printf("Coordonnées invalide.\n"); + game_print(g); + return; + } + printf("Commande inconnue.\n"); +} + +void printhelp(void) { + printf("Liste des différentes commandes :\n"); + printf("h : Affiche cette aide.\n"); + printf("r : Réinitialise la partie.\n"); + printf("q : Quitter la partie (abandon).\n"); + printf("z : Annulez le coup (undo).\n"); + printf("y : Annulez l'annulation du coup (redo).\n"); + printf("l : Place une ampoule (*) à la position (,).\n"); + printf("m : Place une marque (-) à la position (,).\n"); + printf("b : Place une case vide ( ) à la position (,).\n"); +} + +void usage(int argc, char* argv[]) { + printf("Usage : %s => Joue la partie par défaut\n", argv[0]); + exit(EXIT_FAILURE); +} + +int main(int argc, char* argv[]) { + game g; + if (argc == 1) + // Create new game + g = game_default(); + else + usage(argc, argv); + // init string for errors + char stringError[game_nb_rows(g) * game_nb_cols(g) * NBCHARERR]; + char c; + // print game + game_print(g); + // test if game is over + while (!game_is_over(g)) { + // print errors + if (checkerrors(g, stringError)) + printf("Il y a un problème case(s): %s\n", stringError); + printf("Entrez une commande (h pour afficher l'aide): "); + // scan the first command without args + if (scanf(" %c", &c)) { + switch (c) { + case 'h': + printhelp(); + break; + case 'r': + game_restart(g); + printf("Partie réinitialisé avec succes !\n"); + game_print(g); + break; + case 'q': + printf("Solution: \n"); + game solution = game_default_solution(); + game_print(solution); + game_delete(solution); + game_delete(g); + printf("shame\n"); + return EXIT_SUCCESS; + break; + case 'z': + game_undo(g); + game_print(g); + break; + case 'y': + game_redo(g); + game_print(g); + break; + case 'l': + checkPlayMove(g, S_LIGHTBULB); + break; + case 'm': + checkPlayMove(g, S_MARK); + break; + case 'b': + checkPlayMove(g, S_BLANK); + break; + default: + printf("Commande inconnue.\n"); + } + } else { + printf("Commande inconnue.\n"); + } + } + printf("congratulation\n"); + game_delete(g); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/history.c b/history.c new file mode 100644 index 0000000..9303f56 --- /dev/null +++ b/history.c @@ -0,0 +1,218 @@ +#include "history.h" +#include +#include + +void memoryError(void) { + fprintf(stderr, "Not enought memory\n"); + exit(EXIT_FAILURE); +} + +void errPointer(void) { + fprintf(stderr, "Error on the given pointer\n"); + exit(EXIT_FAILURE); +} + +history history_alloc(void) { + history h = (history)malloc(sizeof(struct s_history)); + if (h == NULL) + memoryError(); + h->i = 0; + h->j = 0; + h->next = NULL; + h->prev = NULL; + return h; +} + +void history_free(history h) { + free(h); + h = NULL; +} + +history history_create_empty(void) { + return NULL; +} + +bool history_is_empty(history h) { + return h == NULL; +} + +history history_next(history h) { + if (history_is_empty(h)) + errPointer(); + return h->next; +} + +history history_prev(history h) { + if (history_is_empty(h)) + errPointer(); + return h->prev; +} + +square history_state(history h) { + if (history_is_empty(h)) + errPointer(); + return h->state; +} + +uint history_i(history h) { + if (history_is_empty(h)) + errPointer(); + return h->i; +} + +uint history_j(history h) { + if (history_is_empty(h)) + errPointer(); + return h->j; +} + +history history_last(history h) { + if (history_is_empty(h)) + errPointer(); + if (history_is_empty(history_next(h))) + return h; + return history_last(history_next(h)); +} + +history history_first(history h) { + if (history_is_empty(h)) + errPointer(); + if (history_is_empty(history_prev(h))) + return h; + return history_first(history_prev(h)); +} + +history history_insert_first(history h, square state, uint i, uint j) { + history new = history_alloc(); + new->state = state; + new->i = i; + new->j = j; + new->next = h; + if (!history_is_empty(h)) { + h = history_first(h); + h->prev = new; + } + return new; +} + +history history_insert_after(history h, history p, square state, uint i, uint j) { + if (history_is_empty(h) || history_is_empty(p)) + errPointer(); + history new = history_alloc(); + new->state = state; + new->i = i; + new->j = j; + new->next = p->next; + p->next = new; + new->prev = p; + if (!history_is_empty(history_next(new))) + new->next->prev = new; + return h; +} + +history history_delete_after(history h, history p) { + if (history_is_empty(p)) + errPointer(); + if (!history_is_empty(history_next(p))) { + history to_be_deleted = p->next; + if (!history_is_empty(history_next(to_be_deleted))) { + to_be_deleted->next->prev = p; + } + p->next = to_be_deleted->next; + history_free(to_be_deleted); + } + return h; +} + +history history_insert_before(history h, history p, square state, uint i, uint j) { + if (history_is_empty(h) || history_is_empty(p)) + errPointer(); + history new = history_alloc(); + new->state = state; + new->i = i; + new->j = j; + new->next = p; + new->prev = p->prev; + p->prev = new; + if (!history_is_empty(history_prev(new))) + new->prev->next = new; + return h; +} + +history history_delete_before(history h, history p) { + if (history_is_empty(h) || history_is_empty(p)) + errPointer(); + if (!history_is_empty(history_prev(p))) { + history to_be_deleted = p->prev; + if (!history_is_empty(history_prev(to_be_deleted))) { + to_be_deleted->prev->next = p; + } + p->prev = to_be_deleted->prev; + history_free(to_be_deleted); + } + return h; +} + +history history_delete_first(history h) { + if (!history_is_empty(h)) { + h = history_first(h); + history tmp = h->next; + history_free(h); + h = tmp; + if (!history_is_empty(h)) { + h->prev = NULL; + } + } + return h; +} + +history history_append(history h, square state, uint i, uint j) { + if (history_is_empty(h)) + return history_insert_first(h, state, i, j); + if (history_is_empty(history_next(h))) + return history_insert_after(h, h, state, i, j); + history_append(history_next(h), state, i, j); + return h; +} + +history history_delete_all_after(history h, history p) { + if (history_is_empty(p)) + return h; + if (history_is_empty(history_next(p))) + return h; + history_delete_all_after(h, history_next(p)); + return history_delete_after(h, p); +} + +void history_delete_entire_history(history h) { + if (history_is_empty(h)) + return; + if (history_is_empty(history_prev(h))) + return history_free(history_delete_all_after(h, h)); + return history_delete_entire_history(history_prev(h)); +} + +history history_copy(history h) { + history copy_h = h; + if (history_is_empty(h)) { + return h; + } + h = history_first(h); + uint index_h = 0; + while (h != copy_h) { + h = history_next(h); + index_h++; + } + h = history_first(h); + history h2 = history_insert_first(history_create_empty(), h->state, h->i, h->j); + while (!history_is_empty(history_next(h))) { + h = history_next(h); + h2 = history_insert_after(h2, h2, h->state, h->i, h->j); + h2 = history_next(h2); + } + h2 = history_first(h2); + for (uint i = 0; i < index_h; i++) { + h2 = history_next(h2); + } + return h2; +} \ No newline at end of file diff --git a/history.h b/history.h new file mode 100644 index 0000000..b8e7396 --- /dev/null +++ b/history.h @@ -0,0 +1,219 @@ +#ifndef __HISTORY_H__ +#define __HISTORY_H__ +#include + +typedef struct s_history* history; + +#include "game.h" + +struct s_history { + square state; + uint i; + uint j; + struct s_history* next; + struct s_history* prev; +}; + +/** + * @brief fprintf not enought memory + exit + **/ +void memoryError(void); + +/** + * @brief fprintf error on the given pointer + exit + */ +void errPointer(void); + +/** + * @brief Alloc a new empty history. + * @return The allocated history. + **/ +history history_alloc(); + +/** + * @brief Free the given history. + * @param h The history to be free. + **/ +void history_free(history h); + +/** + * @brief Create a empty history object + * + * @return An empty history + **/ +history history_create_empty(void); + +/** + * @brief Delete every element of h (before and after) + * + * @param h the head of the history + */ +void history_delete_entire_history(history h); + +/** + * @brief Test if the given history is empty or not. + * + * @param h the history to test. + * @pre @p h must be a valid pointer toward an history . + * @return true if the history is empty + * @return false if the history isn't empty + */ +bool history_is_empty(history h); + +/** + * @brief Gets the next element of the given history. + * + * @param h An history. + * @pre @p h must be a valid pointer toward an history . + * @return the next of the given history. + */ +history history_next(history h); + +/** + * @brief Gets the previous element of the given history. + * + * @param h An history. + * @pre @p h must be a valid pointer toward an history . + * @return the previous element of the given history. + */ +history history_prev(history h); + +/** + * @brief Gets the state of the given history + * + * @param h An history. + * @pre @p h must be a valid pointer toward an history . + * @return the state of the given history + */ +square history_state(history h); + +/** + * @brief Get the coordinates i of the given history. + * + * @param h An history. + * @pre @p h must be a valid pointer toward an history . + * @return the coordinates i. + */ +uint history_i(history h); + +/** + * @brief Get the coordinates j of the given history. + * + * @param h An history. + * @pre @p h must be a valid pointer toward an history . + * @return the coordinates j. + */ +uint history_j(history h); + +/** + * @brief Insert a new element a the beginning of the history. + * + * @param h An history (empty or not) + * @param state state of the new element + * @param i the coordinate i of the new element + * @param j the coordinate j of the new element + * @return The new first element of history + */ +history history_insert_first(history h, square state, uint i, uint j); +/** + * @brief Delete the first element of the history + * + * @param h An history + * @return the new first element of the history + */ +history history_delete_first(history h); + +/** + * @brief Insert a new element after p + * + * @param h An history + * @param p Where we insert after the new element + * @param state The state of the new element + * @param i the coordinate i of the new element + * @param j the coordinate j of the new element + * @pre @p h must be a valid pointer toward an history. + * @pre @p p must be a valid pointer toward an history element. + * @return h + */ +history history_insert_after(history h, history p, square state, uint i, uint j); + +/** + * @brief Delete the element after p + * + * @param h An history + * @param p where we delete after + * @pre @p h must be a valid pointer toward an history. + * @pre @p p must be a valid pointer toward an history element. + * @return h + */ +history history_delete_after(history h, history p); + +/** + * @brief Insert a new element before p + * + * @param h An history + * @param p Where we insert before the new element + * @param state The state of the new element + * @param i the coordinate i of the new element + * @param j the coordinate j of the new element + * @pre @p h must be a valid pointer toward an history. + * @pre @p p must be a valid pointer toward an history element. + * @return h + */ +history history_insert_before(history h, history p, square state, uint i, uint j); + +/** + * @brief Delete the element before p + * + * @param h An history + * @param p where we delete before + * @pre @p h must be a valid pointer toward an history. + * @pre @p p must be a valid pointer toward an history element. + * @return h + */ +history history_delete_before(history h, history p); + +/** + * @brief Gets the last element of the history + * + * @param h An history + * @return The last element of the given history + */ +history history_last(history h); + +/** + * @brief Gets the first element of the history + * + * @param h An history + * @return The first element of the history. + */ +history history_first(history h); + +/** + * @brief Add an element at the end of the list. + * + * @param h an history + * @param state the state of the new element + * @param i the coordinate i of the new element + * @param j the coordinate j of the new element + * @return h + */ +history history_append(history h, square state, uint i, uint j); + +/** + * @brief Delete all the elements after p + * + * @param h an history + * @param p an element of h + * @return h + */ +history history_delete_all_after(history h, history p); + +/** + * @brief Copy history + * + * @param h an history + * @return the new history + */ +history history_copy(history h); +#endif \ No newline at end of file diff --git a/history_test.c b/history_test.c new file mode 100644 index 0000000..11b33c5 --- /dev/null +++ b/history_test.c @@ -0,0 +1,235 @@ +#include "history.h" +#include +#include +#include +#include +#include + +bool test_history_alloc(void) { + history new = history_alloc(); + assert(new); + history_free(new); + return true; +} + +bool test_history_create_empty(void) { + assert(history_create_empty() == NULL); + return true; +} + +bool test_history_delete_entire_history(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + history_insert_after(new, new, S_MARK, 3, 3); + assert(new->next); + history_delete_entire_history(new); + return true; +} + +bool test_history_is_empty(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + assert(!history_is_empty(new)); + assert(history_is_empty(history_create_empty())); + history_free(new); + return true; +} + +bool test_history_next(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + new = history_insert_after(new, new, S_BLACKU, 2, 6); + assert(new->next == history_next(new)); + history_delete_entire_history(new); + return true; +} + +bool test_history_prev(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + new = history_insert_after(new, new, S_BLACKU, 2, 6); + new = history_next(new); + assert(new->prev == history_prev(new)); + history_delete_entire_history(new); + return true; +} + +bool test_history_insert_first(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + history_free(new); + return true; +} + +bool test_history_delete_first(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + new = history_insert_after(new, new, S_BLACK3, 2, 5); + new = history_next(new); + history_delete_first(new); + assert(new->prev == NULL); + history_delete_entire_history(new); + return true; +} + +bool test_history_insert_after(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + history newafter = history_alloc(); + newafter->prev = new; + new->next = newafter; + history_insert_after(new, new, S_BLACK2, 4, 3); + assert(new->next->next == newafter); + assert(new == newafter->prev->prev); + assert(new->next->state == S_BLACK2); + assert(new->next->i == 4); + assert(new->next->j == 3); + history_delete_entire_history(new); + return true; +} + +bool test_history_delete_after(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + history newafter = history_alloc(); + newafter->prev = new; + new->next = newafter; + history_insert_after(new, new, S_BLACK2, 4, 3); + history_delete_after(new, new); + assert(new->next == newafter); + assert(new == newafter->prev); + history_delete_entire_history(new); + return true; +} + +bool test_history_insert_before(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + history newafter = history_alloc(); + newafter->prev = new; + new->next = newafter; + history_insert_before(new, newafter, S_BLACK2, 4, 3); + assert(new->next->next == newafter); + assert(new == newafter->prev->prev); + assert(new->next->state == S_BLACK2); + assert(new->next->i == 4); + assert(new->next->j == 3); + history_delete_entire_history(new); + return true; +} + +bool test_history_delete_before(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + history newafter = history_alloc(); + newafter->prev = new; + new->next = newafter; + history_insert_after(new, new, S_BLACK2, 4, 3); + history_delete_before(new, newafter); + assert(new->next == newafter); + assert(new == newafter->prev); + history_delete_entire_history(new); + return true; +} + +bool test_history_last(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + history newafter = history_alloc(); + newafter->prev = new; + new->next = newafter; + assert(history_last(new) == newafter); + history_delete_entire_history(new); + return true; +} + +bool test_history_first(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + history newafter = history_alloc(); + newafter->prev = new; + new->next = newafter; + assert(history_first(newafter) == new); + history_delete_entire_history(new); + return true; +} + +bool test_history_append(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + history_append(new, S_BLACK3, 2, 4); + history last = history_last(new); + assert(new->next == last); + assert(last->state == S_BLACK3); + assert(last->i == 2); + assert(last->j == 4); + history_delete_entire_history(new); + return true; +} + +bool test_history_copy(void) { + history new = history_insert_first(history_create_empty(), S_LIGHTBULB, 4, 3); + assert(new); + new = history_insert_after(new, new, S_BLANK, 5, 8); + history h2 = history_copy(new); + assert(history_state(h2) == history_state(new)); + assert(history_state(history_next(h2)) == history_state(history_next(new))); + history_delete_entire_history(h2); + history_delete_entire_history(new); + return true; +} + +int usage(int argc, char* argv[]) { + fprintf(stderr, "Usage: %s [<...>]\n", argv[0]); + exit(EXIT_FAILURE); +} + +int main(int argc, char* argv[]) { + if (argc == 1) { + usage(argc, argv); + } + fprintf(stderr, "=> Start test \"%s\"\n", argv[1]); + bool ok = true; + if (strcmp("history_alloc", argv[1]) == 0) + ok = test_history_alloc(); + else if (strcmp("history_create_empty", argv[1]) == 0) + ok = test_history_create_empty(); + else if (strcmp("history_delete_entire_history", argv[1]) == 0) + ok = test_history_delete_entire_history(); + else if (strcmp("history_is_empty", argv[1]) == 0) + ok = test_history_is_empty(); + else if (strcmp("history_next", argv[1]) == 0) + ok = test_history_next(); + else if (strcmp("history_prev", argv[1]) == 0) + ok = test_history_prev(); + else if (strcmp("history_insert_first", argv[1]) == 0) + ok = test_history_insert_first(); + else if (strcmp("history_delete_first", argv[1]) == 0) + ok = test_history_delete_first(); + else if (strcmp("history_insert_after", argv[1]) == 0) + ok = test_history_insert_after(); + else if (strcmp("history_delete_after", argv[1]) == 0) + ok = test_history_delete_after(); + else if (strcmp("history_insert_before", argv[1]) == 0) + ok = test_history_insert_before(); + else if (strcmp("history_delete_before", argv[1]) == 0) + ok = test_history_delete_before(); + else if (strcmp("history_last", argv[1]) == 0) + ok = test_history_last(); + else if (strcmp("history_first", argv[1]) == 0) + ok = test_history_first(); + else if (strcmp("history_append", argv[1]) == 0) + ok = test_history_append(); + else if (strcmp("history_copy", argv[1]) == 0) + ok = test_history_copy(); + else { + fprintf(stderr, "Error: test \"%s\" not found!\n", argv[1]); + exit(EXIT_FAILURE); + } + // print test result + if (ok) { + fprintf(stderr, "Test \"%s\" finished: SUCCESS\n", argv[1]); + return EXIT_SUCCESS; + } else { + fprintf(stderr, "Test \"%s\" finished: FAILURE\n", argv[1]); + return EXIT_FAILURE; + } +} \ No newline at end of file