Skip to content

Commit

Permalink
Adding filesaving and restoring functionality, bugfixes, and a bunch
Browse files Browse the repository at this point in the history
more
This is a big one.
* utils.h and utils.c created so game driver and unit tests can share
  same helper functions (DRY)
    * sleep_millis and print_board_state moved to utils
* created debug flags for debug window in game, and made this and
  several other things into CMake options
* options like DEBUG_T_WIN moved to CMake
* changed gamesave function to save board in MUCH easier to parse format
* used inih library to write parser for saved game states.
* wrote tests for parser to verify correct functionality
* added first saved game state to ./test/files/ for unit testing
  purposes - this way I can run tests against actual saved game states.
* broke up check_and_spawn_new_piece with new function
  check_and_clear_rows to make testing easier, and keep function length
  under 1 pg
* wrote first test for check_and_clear_rows

* clearing rows now works in-game!
* fixed bug where highest row would be changed to whatever the highest
  row of the last active_piece was
* fixed check_game_over condition in tg_tick() to prevent exiting when
  game *isn't* over
* updated issues with piece positioning that was causing weird in-game
  rotations
  • Loading branch information
0xjmux committed Mar 21, 2024
1 parent 7e75c85 commit 6f04a3b
Show file tree
Hide file tree
Showing 18 changed files with 581 additions and 226 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:

env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
BUILD_TYPE: Debug

jobs:
build:
Expand All @@ -27,9 +27,9 @@ jobs:
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
#run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DTARGET_GROUP=test
run: cmake -B ${{github.workspace}}/build -DTARGET_GROUP=test -DTETRIS_UNIT_TEST_MACRO=ON
# build cmake in build dir with CI unit test flag turned on
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DTARGET_GROUP=test
# run: cmake -B ${{github.workspace}}/build -DTARGET_GROUP=test -DTETRIS_UNIT_TEST_MACRO=ON
# build cmake in build dir with CI unit test flag turned on - disable macro bc causes some tests to not run

- name: Build
# Build your program with the given configuration
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ a.out

# debugging files
gdb*
*.ini
gamestate.ini
*.log
debug-cores/


suite_2.c

# cpp check
*cppcheck-build-dir
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ if(TARGET_GROUP STREQUAL production)
include_directories(include)
add_subdirectory(src)
elseif(TARGET_GROUP STREQUAL test)
enable_testing()
include(CTest)

add_subdirectory(extern)
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ The game was branched off to it's own repository because the embedded project so
* [ ] Finish tetris game logic
* [X] Set up CI/CD with GH actions
* [ ] fix gameover state not detected
* [ ] fix full lines not being removed
* [X] fix full lines not being removed
* [ ] fix duplicate colors
* [ ] fix crash on trying to clear line
* [ ] figure out what's causing a bunch of out-of-bounds numbers to show up in row 1 of board - i think this is a type issue with uint & int types!
* [ ] fix J piece rotation


> [!WARNING]
Expand Down
4 changes: 2 additions & 2 deletions include/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@

void nc_init_colors(WINDOW *w);

void print_board_state(TetrisBoard tb, FILE *file);
void save_game_state(TetrisGame *tg);

void save_board_to_file(FILE *file, TetrisBoard tb);
// void old_save_board_to_file(FILE *file, TetrisBoard tb);
void ini_save_board_to_file(FILE *file, TetrisBoard tb);

#endif
1 change: 0 additions & 1 deletion include/driver_tetris.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ void update_score(WINDOW *w, TetrisGame *tg);
// Debug functions
void print_keypress(enum player_move move);

void sleep_millis(uint16_t millis);

// void refresh_debug_var_window(WINDOW *w, enum player_move move);
void refresh_debug_var_window(WINDOW *w);
Expand Down
32 changes: 32 additions & 0 deletions include/utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef UTILS_H
#define UTILS_H

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <time.h> // sleep, timespec
#include <sys/time.h>

#include <assert.h>
#include <ncurses.h>

#include "ini.h"

#include "tetris.h"
#include "display.h"


bool restore_game_state(TetrisGame *tg, const char* filename, FILE *gamelog);


void reconstruct_board_from_str_row(TetrisBoard *tb, const char *name, const char *value);
void print_board_state(TetrisBoard tb, FILE *file);

void sleep_millis(uint16_t millis);




#endif
7 changes: 7 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,19 @@ SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
add_executable(tetris_driver
driver_tetris.c
display.c
utils.c
)
target_include_directories(tetris_driver PUBLIC ${PROJECT_SOURCE_DIR}/include)
# message("building driver with proj source dir ${PROJECT_SOURCE_DIR}")

find_library(NCURSES_FOUND ncurses REQUIRED)

OPTION(DEBUG_WINDOW "Enable window with game state information for debugging" ON)
IF(DEBUG_WINDOW)
target_compile_definitions(tetris_driver PUBLIC DEBUG_T_WIN=1)
ENDIF()


# find ini library (needed for saving game state to disk)
OPTION(INI_LIB_INCLUDE_OPTION "Include inih library for saving game state to disk" ON)
IF(INI_LIB_INCLUDE_OPTION)
Expand Down
90 changes: 20 additions & 70 deletions src/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "display.h"
#include "tetris.h"
#include "utils.h"


/**
Expand Down Expand Up @@ -41,20 +42,16 @@ void save_game_state(TetrisGame *tg) {

// save TetrisGame state
fprintf(savefile, "\n\n[TETRIS_GAME_STRUCT]\n");
fprintf(savefile, "active_board = ");
save_board_to_file(savefile, tg->active_board);
fprintf(savefile, "active_board.highest_occupied_cell = %d\n", tg->active_board.highest_occupied_cell);
fprintf(savefile, "\nboard = ");
save_board_to_file(savefile, tg->board);
fprintf(savefile, "board.highest_occupied_cell = %d\n", tg->board.highest_occupied_cell);
fprintf(savefile, "active_board_highest_occupied_cell = %d\n", tg->active_board.highest_occupied_cell);
fprintf(savefile, "board_highest_occupied_cell = %d\n", tg->board.highest_occupied_cell);
fprintf(savefile, "\n");
fflush(savefile);

fprintf(savefile, "game_over = %d\n", tg->game_over);
fprintf(savefile, "score = %d\n", tg->score);
fprintf(savefile, "level = %d\n", tg->level);
fprintf(savefile, "gravity_tick_rate_usec = %d\n", tg->gravity_tick_rate_usec);
fprintf(savefile, "last_gravity_tick_usec = {%ld,%ld}\n", tg->last_gravity_tick_usec.tv_sec, tg->last_gravity_tick_usec.tv_usec);
fprintf(savefile, "last_gravity_tick_usec = %ld,%ld\n", tg->last_gravity_tick_usec.tv_sec, tg->last_gravity_tick_usec.tv_usec);


fprintf(savefile, "\n[ACTIVE_PIECE]\n");
Expand All @@ -64,85 +61,38 @@ void save_game_state(TetrisGame *tg) {
fprintf(savefile, "orientation = %d\n", tg->active_piece.orientation);
fprintf(savefile, "falling = %d\n", tg->active_piece.falling);

printf("game state saved!\n");

fclose(savefile);
fprintf(savefile, "\n[active_board]\n");
ini_save_board_to_file(savefile, tg->active_board);

}
fprintf(savefile, "\n[board]\n");
ini_save_board_to_file(savefile, tg->board);

/**
* Print current board state to console
* @param TetrisBoard
* @param *file to print to - NULL for default
*/
void print_board_state(TetrisBoard tb, FILE *file) {
if (file == NULL)
file = gamelog;
// draw existing pieces on board
fprintf(file, "Highest occupied cell: %d\n ", tb.highest_occupied_cell);
fprintf(file, " ");
for (int i = 0; i < TETRIS_COLS; i++)
fprintf(file, "%-2d ",i);
fprintf(file, "\n ");

for (int i = 0; i < TETRIS_COLS; i++)
fprintf(file, "----");
fprintf(file, "----\n");
printf("\ngame state saved!\n");

fclose(savefile);

for (int i = 0; i < TETRIS_ROWS; i++) {
fprintf(file, "%-3d| ", i);
for (int j = 0; j < TETRIS_COLS; j++) {
if (tb.board[i][j] >= 0) {
fprintf(file, "%-3d ", tb.board[i][j]);
}
else {
fprintf(file, " ");
}
}
fprintf(file, "|\n");
}
fflush(file);
}



/**
* Handler for reading .ini file
* Save board to ini file in easier to read format.
* Each board will have its own section to make this less painful
*/
// static int handler(void* user, const char* section, const char* name,
// const char* value)
// {
// TetrisGame *tg = (TetrisGame*)user;

// #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
// if (MATCH("protocol", "version")) {
// pconfig->version = atoi(value);
// } else if (MATCH("user", "name")) {
// pconfig->name = strdup(value);
// } else if (MATCH("user", "email")) {
// pconfig->email = strdup(value);
// } else {
// return 0; /* unknown section/name, error */
// }
// return 1;
// }



void save_board_to_file(FILE *file, TetrisBoard tb) {
fprintf(file, "{");
void ini_save_board_to_file(FILE *file, TetrisBoard tb) {

for (int i = 0; i < TETRIS_ROWS; i++) {
// fprintf(file, "%-3d| ", i);
fprintf(file, "{");
fprintf(file, "row_%d = ", i);
for (int j = 0; j < TETRIS_COLS - 1; j++) {
fprintf(file, "%d,", tb.board[i][j]);
}
fprintf(file, "%d}", tb.board[i][TETRIS_COLS - 1]);
fprintf(file, "%d\n", tb.board[i][TETRIS_COLS - 1]);

// dont print comma on last row
if (i < TETRIS_ROWS - 1)
fprintf(file,", ");
}
fprintf(file, "}\n");
// fprintf(file, "}\n");
fflush(file);
}


18 changes: 4 additions & 14 deletions src/driver_tetris.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@

#include "driver_tetris.h"
#include "display.h"
#include "utils.h"
#include "tetris.h"

// show extra window with debugging information
#define DEBUG_T_WIN 1
// #define DEBUG_T_WIN 1
#ifdef DEBUG_T_WIN
WINDOW *dbg_win;
#endif
Expand Down Expand Up @@ -74,13 +75,12 @@ int main(void) {
fprintf(gamelog, "========================================\n");
fflush(gamelog);
#endif
bool gamestate = true;

// while game is running and player hasn't tried to quit
while (!tg->game_over && move != T_QUIT) {


gamestate = tg_tick(tg, move);
tg_tick(tg, move);
// IMPLEMENT WHAT HAPPENS ON GAME OVER!!

// display board
Expand Down Expand Up @@ -177,7 +177,7 @@ void display_board(WINDOW *w, TetrisBoard *tb) {
wrefresh(w);
#ifdef DEBUG_T
fprintf(gamelog, "display_board()\n");
// print_board_state(*tb, NULL);
print_board_state(*tb, gamelog);
fflush(gamelog);
#endif

Expand Down Expand Up @@ -226,16 +226,6 @@ void print_keypress(enum player_move move) {

}

/**
* POSIX sleep for `millis` milliseconds
* `man 2 nanosleep`
*/
void sleep_millis(uint16_t millis) {
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = millis * 1000; // * 1000;
nanosleep(&ts, NULL);
}


void refresh_debug_var_window(WINDOW *w) {
Expand Down
Loading

0 comments on commit 6f04a3b

Please sign in to comment.