From 4da59836bebafae219b424e770d5c879315482fd Mon Sep 17 00:00:00 2001 From: Piotr Kozak Date: Tue, 26 May 2026 13:38:15 +0200 Subject: [PATCH] Added unit tests --- .gitignore | 1 + Makefile | 10 +- main.c | 15 ++- munit.c | 370 ++++++++++++++++++++++++++++++++++++++++++++++++++++ munit_tests | Bin 0 -> 50328 bytes ui.c | 22 +++- ui.h | 2 +- 7 files changed, 409 insertions(+), 11 deletions(-) create mode 100644 munit.c create mode 100755 munit_tests diff --git a/.gitignore b/.gitignore index 8fbd6e6..ebae9ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ main save.dat +munit/ diff --git a/Makefile b/Makefile index 8b68f6a..c437fc4 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,8 @@ LIBS = -lraylib -lm -lpthread -ldl TARGET = main SRC = main.c game.c ui.c +TEST_TARGET = munit_tests +TEST_SRC = munit.c game.c munit/munit.c all: $(TARGET) @@ -16,4 +18,10 @@ clean: run: $(TARGET) ./$(TARGET) -.PHONY: all clean run +test: $(TEST_TARGET) + ./$(TEST_TARGET) + +$(TEST_TARGET): $(TEST_SRC) + $(CC) $(CFLAGS) $(TEST_SRC) -o $(TEST_TARGET) $(LIBS) + +.PHONY: all clean run test diff --git a/main.c b/main.c index 821807d..95b0bf8 100644 --- a/main.c +++ b/main.c @@ -25,12 +25,21 @@ int main() { SetConfigFlags(FLAG_WINDOW_HIGHDPI); InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "2048"); - SetTargetFPS(30); // So my laptop doesn't burn + SetTargetFPS(90); // We have event blocking so we can affort higher FPS + EnableEventWaiting(); while (!WindowShouldClose()) { - ClearBackground(RAYWHITE); BeginDrawing(); - render(&game_state); + ClearBackground(RAYWHITE); + + // If we are not animating, we can wait for input events instead of continuously rendering + bool animating = render(&game_state); + if (animating) { + DisableEventWaiting(); + } else { + EnableEventWaiting(); + } + EndDrawing(); handle_mouse_input(&game_state); handle_keyboard_input(&game_state); diff --git a/munit.c b/munit.c new file mode 100644 index 0000000..7396aa3 --- /dev/null +++ b/munit.c @@ -0,0 +1,370 @@ +#include "munit/munit.h" +#include "game.h" + +#include + +static void clear_tiles(GameState_t *state) { + for (int y = 0; y < BOARD_HEIGHT; y++) { + for (int x = 0; x < BOARD_WIDTH; x++) { + state->board[y][x] = 0; + } + } +} + +static void *test_setup(const MunitParameter params[], void *user_data) { + (void)params; + (void)user_data; + + GameState_t *state = (GameState_t *)malloc(sizeof(GameState_t)); + munit_assert_not_null(state); + + reset_board(state); + clear_tiles(state); + return state; +} + +static void test_tear_down(void *fixture) { + free(fixture); +} + +static MunitResult test_merge_left_simple(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][0] = 2; + state->board[0][1] = 2; + + munit_assert_true(move(state, LEFT)); + munit_assert_int(state->board[0][0], ==, 4); + munit_assert_int(state->board[0][1], ==, 0); + munit_assert_uint(state->score, ==, 4); + munit_assert_uint(state->moves_made, ==, 1); + + return MUNIT_OK; +} + +static MunitResult test_merge_left_through_gap(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][0] = 2; + state->board[0][2] = 2; + + munit_assert_true(move(state, LEFT)); + munit_assert_int(state->board[0][0], ==, 4); + munit_assert_int(state->board[0][1], ==, 0); + munit_assert_int(state->board[0][2], ==, 0); + munit_assert_int(state->board[0][3], ==, 0); + + return MUNIT_OK; +} + +static MunitResult test_merge_left_double_pair(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][0] = 2; + state->board[0][1] = 2; + state->board[0][2] = 4; + state->board[0][3] = 4; + + munit_assert_true(move(state, LEFT)); + munit_assert_int(state->board[0][0], ==, 4); + munit_assert_int(state->board[0][1], ==, 8); + munit_assert_int(state->board[0][2], ==, 0); + munit_assert_int(state->board[0][3], ==, 0); + munit_assert_uint(state->score, ==, 12); + + return MUNIT_OK; +} + +static MunitResult test_merge_left_no_double_merge_single_move(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][0] = 2; + state->board[0][1] = 2; + state->board[0][2] = 2; + + munit_assert_true(move(state, LEFT)); + munit_assert_int(state->board[0][0], ==, 4); + munit_assert_int(state->board[0][1], ==, 2); + munit_assert_int(state->board[0][2], ==, 0); + munit_assert_int(state->board[0][3], ==, 0); + munit_assert_uint(state->score, ==, 4); + + return MUNIT_OK; +} + +static MunitResult test_move_left_no_merge_when_different_values(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][1] = 2; + state->board[0][2] = 4; + + munit_assert_true(move(state, LEFT)); + munit_assert_int(state->board[0][0], ==, 2); + munit_assert_int(state->board[0][1], ==, 4); + munit_assert_int(state->board[0][2], ==, 0); + munit_assert_int(state->board[0][3], ==, 0); + munit_assert_uint(state->score, ==, 0); + + return MUNIT_OK; +} + +static MunitResult test_move_returns_false_when_nothing_changes(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][0] = 2; + state->board[0][1] = 4; + state->board[0][2] = 8; + state->board[0][3] = 16; + + munit_assert_false(move(state, LEFT)); + munit_assert_uint(state->moves_made, ==, 0); + munit_assert_uint(state->score, ==, 0); + + return MUNIT_OK; +} + +static MunitResult test_merge_right_simple(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[1][2] = 2; + state->board[1][3] = 2; + + munit_assert_true(move(state, RIGHT)); + munit_assert_int(state->board[1][0], ==, 0); + munit_assert_int(state->board[1][1], ==, 0); + munit_assert_int(state->board[1][2], ==, 0); + munit_assert_int(state->board[1][3], ==, 4); + + return MUNIT_OK; +} + +static MunitResult test_merge_right_through_gap(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[1][1] = 2; + state->board[1][3] = 2; + + munit_assert_true(move(state, RIGHT)); + munit_assert_int(state->board[1][0], ==, 0); + munit_assert_int(state->board[1][1], ==, 0); + munit_assert_int(state->board[1][2], ==, 0); + munit_assert_int(state->board[1][3], ==, 4); + + return MUNIT_OK; +} + +static MunitResult test_merge_right_double_pair(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[1][0] = 2; + state->board[1][1] = 2; + state->board[1][2] = 4; + state->board[1][3] = 4; + + munit_assert_true(move(state, RIGHT)); + munit_assert_int(state->board[1][0], ==, 0); + munit_assert_int(state->board[1][1], ==, 0); + munit_assert_int(state->board[1][2], ==, 4); + munit_assert_int(state->board[1][3], ==, 8); + munit_assert_uint(state->score, ==, 12); + + return MUNIT_OK; +} + +static MunitResult test_merge_right_no_double_merge_single_move(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[1][1] = 2; + state->board[1][2] = 2; + state->board[1][3] = 2; + + munit_assert_true(move(state, RIGHT)); + munit_assert_int(state->board[1][0], ==, 0); + munit_assert_int(state->board[1][1], ==, 0); + munit_assert_int(state->board[1][2], ==, 2); + munit_assert_int(state->board[1][3], ==, 4); + munit_assert_uint(state->score, ==, 4); + + return MUNIT_OK; +} + +static MunitResult test_merge_up_simple(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][0] = 2; + state->board[1][0] = 2; + + munit_assert_true(move(state, UP)); + munit_assert_int(state->board[0][0], ==, 4); + munit_assert_int(state->board[1][0], ==, 0); + munit_assert_int(state->board[2][0], ==, 0); + munit_assert_int(state->board[3][0], ==, 0); + + return MUNIT_OK; +} + +static MunitResult test_merge_up_through_gap(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][1] = 2; + state->board[2][1] = 2; + + munit_assert_true(move(state, UP)); + munit_assert_int(state->board[0][1], ==, 4); + munit_assert_int(state->board[1][1], ==, 0); + munit_assert_int(state->board[2][1], ==, 0); + munit_assert_int(state->board[3][1], ==, 0); + + return MUNIT_OK; +} + +static MunitResult test_merge_up_double_pair(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][1] = 2; + state->board[1][1] = 2; + state->board[2][1] = 4; + state->board[3][1] = 4; + + munit_assert_true(move(state, UP)); + munit_assert_int(state->board[0][1], ==, 4); + munit_assert_int(state->board[1][1], ==, 8); + munit_assert_int(state->board[2][1], ==, 0); + munit_assert_int(state->board[3][1], ==, 0); + munit_assert_uint(state->score, ==, 12); + + return MUNIT_OK; +} + +static MunitResult test_merge_up_no_double_merge_single_move(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][1] = 2; + state->board[1][1] = 2; + state->board[2][1] = 2; + + munit_assert_true(move(state, UP)); + munit_assert_int(state->board[0][1], ==, 4); + munit_assert_int(state->board[1][1], ==, 2); + munit_assert_int(state->board[2][1], ==, 0); + munit_assert_int(state->board[3][1], ==, 0); + munit_assert_uint(state->score, ==, 4); + + return MUNIT_OK; +} + +static MunitResult test_merge_down_simple(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[1][2] = 2; + state->board[2][2] = 2; + + munit_assert_true(move(state, DOWN)); + munit_assert_int(state->board[0][2], ==, 0); + munit_assert_int(state->board[1][2], ==, 0); + munit_assert_int(state->board[2][2], ==, 0); + munit_assert_int(state->board[3][2], ==, 4); + + return MUNIT_OK; +} + +static MunitResult test_merge_down_through_gap(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][2] = 2; + state->board[2][2] = 2; + + munit_assert_true(move(state, DOWN)); + munit_assert_int(state->board[0][2], ==, 0); + munit_assert_int(state->board[1][2], ==, 0); + munit_assert_int(state->board[2][2], ==, 0); + munit_assert_int(state->board[3][2], ==, 4); + + return MUNIT_OK; +} + +static MunitResult test_merge_down_double_pair(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[0][2] = 2; + state->board[1][2] = 2; + state->board[2][2] = 4; + state->board[3][2] = 4; + + munit_assert_true(move(state, DOWN)); + munit_assert_int(state->board[0][2], ==, 0); + munit_assert_int(state->board[1][2], ==, 0); + munit_assert_int(state->board[2][2], ==, 4); + munit_assert_int(state->board[3][2], ==, 8); + munit_assert_uint(state->score, ==, 12); + + return MUNIT_OK; +} + +static MunitResult test_merge_down_no_double_merge_single_move(const MunitParameter params[], void *fixture) { + (void)params; + GameState_t *state = (GameState_t *)fixture; + + state->board[1][2] = 2; + state->board[2][2] = 2; + state->board[3][2] = 2; + + munit_assert_true(move(state, DOWN)); + munit_assert_int(state->board[0][2], ==, 0); + munit_assert_int(state->board[1][2], ==, 0); + munit_assert_int(state->board[2][2], ==, 2); + munit_assert_int(state->board[3][2], ==, 4); + munit_assert_uint(state->score, ==, 4); + + return MUNIT_OK; +} + +static MunitTest tests[] = { + {(char *)"/merge/left/simple", test_merge_left_simple, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/left/through-gap", test_merge_left_through_gap, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/left/double-pair", test_merge_left_double_pair, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/left/no-double-merge", test_merge_left_no_double_merge_single_move, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/left/different-values-no-merge", test_move_left_no_merge_when_different_values, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/move/no-change", test_move_returns_false_when_nothing_changes, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/right/simple", test_merge_right_simple, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/right/through-gap", test_merge_right_through_gap, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/right/double-pair", test_merge_right_double_pair, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/right/no-double-merge", test_merge_right_no_double_merge_single_move, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/up/simple", test_merge_up_simple, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/up/through-gap", test_merge_up_through_gap, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/up/double-pair", test_merge_up_double_pair, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/up/no-double-merge", test_merge_up_no_double_merge_single_move, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/down/simple", test_merge_down_simple, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/down/through-gap", test_merge_down_through_gap, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/down/double-pair", test_merge_down_double_pair, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {(char *)"/merge/down/no-double-merge", test_merge_down_no_double_merge_single_move, test_setup, test_tear_down, MUNIT_TEST_OPTION_NONE, NULL}, + {NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}}; + +static const MunitSuite suite = { + (char *)"", + tests, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; + +int main(int argc, char *argv[MUNIT_ARRAY_PARAM(argc + 1)]) { + return munit_suite_main(&suite, NULL, argc, argv); +} diff --git a/munit_tests b/munit_tests new file mode 100755 index 0000000000000000000000000000000000000000..94b7b86058e7c2cdf085bb3acd04432e3a47c0fd GIT binary patch literal 50328 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�AP6C>j(%;wT~z zju8hW{TzUns}NBJ20I+;196Ba;}9>wA>IlW=Vfs4gV+e3FJWL{m<$$&h#`|paft5# zxgS{}lzke9`a2wu@(R7&cnKDlWzcR0*p;sm7=sAWi$C=PKo9O71-Aon2ZHJDLu zIMgfRa8DRmJuia*G(SR<1w#f@ya3dYW?*0d&*w2PFqA;W6`<`&*tk{;SRCdG1U&a7ZfjyDaj<#|9O6@Ogj)~}^H<@>cd0nkGXy6Wl^E*f zGsMRyCS}H#B&I`_>*ysz==j{k%B0lzrRsLJAkoXq4@ zz2yA7;*x@*`~p1#s6UP^poNqla8G0X_C5P01mR83BPdRjba6?#!#Vh$EFa`MyT z!3%NYp-XF#^`%yn=@ldvmB4L8sD>=t0k1%c2d~tL&&n-`Pby7|2d~42nt~z&6$85{ zwL&jBKPLq$1Xh+7k17ZXg7~7+yu8$+c+m1zs9I#G9%_`Ljh$a=VVh#+JkR6J;nAck6~Lh!;lbaSAK z?czbJ64B+Liwcp1OACm#xU>M~K6GnK3*vEDT3Qf~X(f1NBC)nY6yxwOL=uPH5J?p4 z^UG4>i&9HUi}H%&!Fy-o%QI3zAzzY_nU@|9S%z5*4Q-HO(54rVOCd1=Rt#TI4oaS- zsW8=`^^fr|^Z*6R;qVq%4%t^Fsfk746a;a1acW6v0Yh?8Nm6Qh zW?ryfK0``sQEGZ-aY<@Xd`WJ6a!!6;YB586d`fl*Ls=Mo>!5FhXE7aH&C0TuLc31WzMiS%>y^>k*4clYsk za`cJ!cXJDN4T%qNbn zPc$RS(r`Esq#Z?VaY<20erXPhSiGlyd~r!iW?npKmk>jId~!u1C;}35GOJQiR2G*M z<)ote5>lXoOES>Lj^gG8!SMc}eCz6dS}3P^}4U>Q)U z%8;0pUxXTT@$o6e`SBTvc_}%m48;YB<$3WXnK`M*!IYU-5?=&f;hdb9larqew*eFz zAP;3iRf7x0_}s+Iy!hnO;*$JahWPlL%%tS_;*!Lo60ihZudxY3d17WsK_+UnK^$BJ zs?SO@^Gb}37>ZL%K*=BnHN=v0@{_YcVFKEu1UJ;soFS#OzzEI<;A_Q6+jUo^q56U?p9~YG{#HZ$^FqGsLfC3Fg6)4Ei z5^YgxB5J(H7pInFp(%=wPbw~k8lF;$n(84Q0;dy5Ay=r%}p+-1Vt{~EKoMfEKV#bse}uGBhfR& zHy%-Rhxj^!3hoflHZf2(hO81t)e)bbo1X{qFQ_;L82~H!QM9Ll7ptS`1%)VzyG$4u z^olEUOA?b9^gyK(n9hK)zzx9y2EFvWQoW4C;tU48l*+u~%3KItQUsAHOD!tS%+EvN z#1}E>6{Y4Rf;2!`1vw=QdZ6lpL9ZaEL@zzRgh8()wW5SU4^-VS=oRIIk_m%eY6i4| z$w(|E&(-aQ;SL}p+b&Mp1Pp+vR+A2aWTXONQDG37{&$}4sw!SQ9juD ziMg4{44};hi41yRji3#bsd>! zj1{IIwjKdSS3os@hXs-5{b792@WZ$N|MOw`Ve1!QG;DYjrXDt~0doWBphVEx1(<%= z`UV)S0dgF4Jp{-pF#WJ~2B7Ikke6ZlVe2JeG;DnY%s!AAFgAz=EwulSnqU1 z59a@5sD3AqX3+cvbiD>_-3DxZ2S`6?%?iwZnEqVwyb@?m2xb5(4N6~75ikWZ2h4yH zpkY=}n8MblC_o3`VKjJ6J5()*0xVKi*r4@?8P|3SBYf%L=T4>q0)qjx~pufX)f z{14-U(g(7B*tk242JQWTsRhyK_RoX*A7+0B$Up`L1{gg7x;_V_7ldKq24aKo3I+zy zl1`B(;cHi6Br9DwRifJQ8+{6MxJmX6BN?1!!IQUE0k&^#_Eae*{K zG0fdC8g!mHND0jS@bO0Qoq`Mud*Bif1JK>i3|-I#QwE#&+Ors<9vZC>4%Dr14k+%y z^LLQ-^2p*a{pfQrAh&|lA=A(x0tv(M1*rWN&;{&6xO!XB;}h6ll&0=68q)glqz33= 1.0f) { - new_tile_animation_progress = 0.3f; + new_tile_animation_progress = 0.0f; new_tile = (Vector2){-1, -1}; // Reset after animation completes is_animating = false; } } // Main render loop, responsible ONLY for redering -void render(GameState_t *game_state) { +bool render(GameState_t *game_state) { if (is_animating) { animate_new_tile(); } @@ -163,7 +172,7 @@ void render(GameState_t *game_state) { } if (is_animating && new_tile.x == x && new_tile.y == y) { - float scale = new_tile_animation_progress; // Scale from 0 to 1 + float scale = 1.0 - 0.2 * sin(new_tile_animation_progress * PI); int scaled_size = (int)(TILE_SIZE * scale); int offset = (TILE_SIZE - scaled_size) / 2; DrawRectangle(pos_x + offset, pos_y + offset, scaled_size, scaled_size, tile_colors[color_index]); @@ -207,4 +216,5 @@ void render(GameState_t *game_state) { DrawRectangle(400 - 100, 450, 200, 40, RED); DrawText("Try again", 400 - MeasureText("Try again", 20) / 2, 455 + 4, 20, WHITE); } + return is_animating; } diff --git a/ui.h b/ui.h index 4e38c5e..01a87e9 100644 --- a/ui.h +++ b/ui.h @@ -9,6 +9,6 @@ #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT 600 -void render(GameState_t *game_state); +bool render(GameState_t *game_state); void handle_mouse_input(GameState_t *game_state); void handle_keyboard_input(GameState_t *game_state);