From c2178fd2b03041ff7535d446ecbb4c37ff2be2e4 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Wed, 23 Dec 2020 00:00:58 +0100 Subject: [PATCH] dynamic tiles, tile animations, door logic, spawn and victory --- CMakeLists.txt | 5 +- MystNB.g1a | Bin 34840 -> 39176 bytes assets-fx/map/lv1.txt | 8 +- assets-fx/tilesheet.png | Bin 0 -> 860 bytes src/animation.c | 81 ++++++++++++- src/animation.h | 8 ++ src/engine.c | 245 ++++++++++++++++++++++++++++++++++++++-- src/engine.h | 44 +++++++- src/game.c | 116 +++++++++++++++++++ src/game.h | 9 ++ src/main.c | 83 ++------------ 11 files changed, 503 insertions(+), 96 deletions(-) create mode 100644 assets-fx/tilesheet.png create mode 100644 src/game.c create mode 100644 src/game.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e54d50..475d9d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,9 +11,9 @@ find_package(Gint 2.1 REQUIRED) set(SOURCES src/animation.c - src/main.c src/engine.c - # ... + src/game.c + src/main.c ) # Shared assets, fx-9860G-only assets and fx-CG-50-only assets set(ASSETS @@ -22,6 +22,7 @@ set(ASSETS set(ASSETS_fx assets-fx/levels.png assets-fx/lv1.png + assets-fx/tilesheet.png assets-fx/title.png assets-fx/spritesheet.png assets-fx/font_mystere.png diff --git a/MystNB.g1a b/MystNB.g1a index f4a7e2a1f8e63dc6c1c7d5d879efcbe442b6e8c4..9a09d711affa04a3dfaaa6ae89d8cc972817ae4d 100644 GIT binary patch delta 7019 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 I)$2F@A4|T3M*si- delta 4274 zcmZt}4NRNI^}f%>*!~PL#&*n4u;Ty;KgEY}DFH%_NmBw8^A|!BmY84&B_vG~(q)Vm z798rTA($LW&>BUoMG?!`VEv*hs#qIUQ502dvLn{D8LLPI1Wl+#14XQ(^WOQxK&pM{ z@!q}n?%lihb9Z)s#142^&8?9?je6X_?os%^hrgTu_4|MR#MGzBVDKOJ|CV9hI)0f+ zV>+H6k}Bm@2qXBqm3}WEEE|_9rDF&&od|d95R!~Zl`NVyP#yU2I|H-m^ajmr&7Zkj$e-S^hpjeoD7bXvKNOTPLkXFfNBeD$;J zm-Qrbi~dmU6I8;@#yda!+u=Ep>B?+kG1tmwd3vRfLrkj14}H~7Qr`@nO~~h*AO3=I z5Vx%NQ5*Ml#eNRSyyaH-f8}DGBHhr-eamF+?3FnpM?X97|MKN0TnQQ765FBa9dRx_ zvdvj$Q4!Z_ZCdhL&u2Ymcd499k43tOS*Eqn%IU-gDU+m&bT0X_=M=L@uX&90TU%50 zx*|^B?aORfdX(v0(np=!<$FG#)8F!1X8Z^LeKpU@%_3_=ugEF#rRRey?`PJ1uwB=q ze~gmBzVEr~rjC5BRZV?;%;?n(OWve&N-H;GH$3RkIu!16Jz?%QdvtiLM?S~)X|b~R zyN0Df@tg?7j6OoLIpmFVE;V#n!G>>}b7?k;S{wZ{l8ydrjzJgehEcZ&(iHs_fD_^N z350jL5##|?Z)zg^jdY~Yu!l$?}_b+%}-x@inykysYSqo<(14-)OJa`81ETv?}Vlx9fVP z-^x1__QaN_C!Pk4S4JFHiFS&jLwDB3u+fL38z50t$BXpptgv! zjE{a(*NtRC6l5b1g$@Wqh}YB&epiBVaZ}|qvL{lH9rbPzljLUDB(tur!p$D6Fvn_+ zX<9;(5}TN$dRuzUfnTN9Hki-0m+fW_hD763RO@DX<(a47{fAfX?3!hQdmR0%F47z7 z?4s4Q%NXS9B1gZNX5`cx8tM==F(}WI^AR}=BB|PfFxp~L2>l&^ani1xJEU$!c+0nt zi50+*6*i!Pu(AZ#5q)S%t|S}-6qgv zd}|vU=OJ0TPgu@PmUSNh;s>JI7?$%Abxa#Yo|qf#HZQ@krL1g>?~HxONWy0j4)gFe zaoLQ7^MY5MWI+FBBKH&)P(Ufw!lDphT25*h3nc_RD4T>qVlf=a&YM73LHD5$RKuJ@ z#Rz%9HwX;%Q?cQ*^uZ?j9+@yyCOts7u|HsNi_I!nFhZ>+jCh$z)|x_t8|I-_Na6N< zHcen5Q@82rfb8oKyOH*t!TsKU?1@5=(q7Ssz2b&~NwFOgg=Dl8s3Wj7*bA8PZqH^l zokU@zh%Yo&i{iGz!K<6{!i-5VZFry95VJN9M^%yWgiIFDa)Oyna*V>L}Ox_!Y7I^>}4IQhpf{`a|8RC0+`;k>uDbE+`=YIAsjOZJ-uo-`a(KZ zO=phRH&*6zv;Gcfxq!zwwuCs7-V0mTgo7OMHOV%HEpw3TDRC5yrx+=kPSH}dlJZO0 z1gtj+;3e&;y&EH5{aiPbwhc1;)(S`=XO_FdjdKzQXCT}%hVV%x!sh!RSC9v3IkJgR zzyHU8J%uSHEMR%41kMbwLH3$%NX(F|;}(A2q-FDHmFSzOSt_QJ)+SG?r%304+=$Mq z#eI+=nqb)wWMkRKn$u*lalfmTz5@t=(X+9O6FarkH#bnidIN;5Of6j~? z*@k>Lqgqb=5h|8QEoYgp_;$+`Mv{C941Gn^nV*Hhsaoqq_-C-~BbUf@<_jUf!FTxB z7tV;nh5SIC;FwHFCD*gRkAPgnmeAP8x&m&yyKsehAHQ<38Wg2)hw{G3~9m20l5w3@t zsarufr-^u8%F)bIDZ;rez=6gBOZZha8BiWFW)Fdp+d`CR!Da7-gG~Wr1JK}Hc0W-^ z7{jw&Yb+>v3usPy!<(vw@U84e2OAV z;U^L+IA+Zv%Y`3FtXV*o@FV+^85O1T#R$I`3%t_^gZ6yUM{#+pKl=TC1OP8SKfJWi zn%X_8#@{JE!Wik4yw#t|KyNsZIc!@XS+9ISzZLPqB~HR9^$Or!Ne1HWIrs`8IEwHXu00*$BD&(?wg`^&+lu@Prz{q`|K(-;9JYe4#@u}6N481tp4?a|t z?h*oDOpnOG1mdW9K|@!k#^~zQ3ovRw&yUpHU{thp%7!TqAh0xFd*~D!SL_n*%x7ib zW+!!(pRc{lXzAQy+5})PpeM|~bmUf0_EX!W#PNsZ^(EK$=Vh#GhiDN*KG-BhcZf1M zQ$<@h&O=p%-YHrjin?a@)h7OY-4`NN*EqZxFM(6w#33c%2~ySYrKD>J$lC-rj`35C zS|;wO72%@}0Pz3lAi!M!FTf(4ga%UfVv$NZ09&~Zr`0lGP%-~NMqb?a|JceVTeKNY zA?c8CTA9BA?Fl+FFsr30Eu4z!K^9>DK!%&rqRsa=X~V@4eEUd;_{FAUOe1|F)2DIt zZG>Ia7Z}qgbJsk;3h)#F2B6ykL%q4~0l*eqQvjgjTBWaNXfM#~g1IgW0Ho1X3s4Af z8eojvX^yiU6HApc?o z-8h`rok9CR$HUGL!WS$7aRzKFgI){5hcgT)5lH;jazL!b2y4rNQlQmHO2rp2h>Nkg z9dbi|S_&Ud-;3zkg5MUyP2L`4uVbulftSJJ#@OHv{Bp%_E%fyz(0*=9R8bW#MgZgxfB`Z=@=K7BbbN-=;(Gvl(<5#sIL6 zfdvwxyKEBeNHg^H{c@{l?UM)}!mr1Ikp+J0a<*9Vwh}g`=3_}4ljclC*jWhB3;>HY zf~?U9^j&~egd0i#27#9i;Nb7|zrqsDwFJopHN;L$tkzMLz)y(=hNZ^VZgiES|Lxjs60h;oI-!t?v$dc0l08TljNQ5t{ JN$$YJ{{XWQq`&|G diff --git a/assets-fx/map/lv1.txt b/assets-fx/map/lv1.txt index 3de9215..4fa6531 100644 --- a/assets-fx/map/lv1.txt +++ b/assets-fx/map/lv1.txt @@ -1,10 +1,10 @@ ##### ##### # ##### # -# a a # -##A### ###A## -# a a # +# b a # +## ### ### ## +# a b # # ~ ##### @ # ##### ##### a: #. -A: #. +b: .# diff --git a/assets-fx/tilesheet.png b/assets-fx/tilesheet.png new file mode 100644 index 0000000000000000000000000000000000000000..1d6b5e35fde1f268eca897ae38c416974a5b98ff GIT binary patch literal 860 zcmV-i1Ec(jP)z7ZC^@{{&uz0000PbVXQnLvL+uWo~o;Lvm$dbY)~9 zcWHEJAV*0}P*;Ht7XSbP)=5M`RCwC$oZE7%FbqXeCjbA-Ixj$VgfD_PU^{EgWLm%l z;_av3F2f9Ud7ezWyOZh9UOF~F3Tc?ZSeit zLzpwptlpQ1s2K=|`?P~wI}=emaxl&u*ugk+Vh7{QfgSvXY*rdZ1nS^V>fjS~rr35l z5u@j8-40x6H65ikjIQBTHn;TN4)P&dGvQR+vEJK3Dao>qNI7PdcK&aEIk`JzztOC> z_jYi@LjV(i^$?97*hM7uR8b)x{B(jDyc;$5m=n4Sz)&^U}vMq+6QrP zV#+Wjv~@c!pREqIbI40&&R9ftr{nT*vS?lYrW4hL^+Vglb7tt-!57@F3$ts}*1^{4 z)e?=;Y%m80U4D3k$ODJ=S@feC`=kv5A=-wzE%f<)owTz{gw&K_C|Pl^nwcpz9qb+R zbUUAoi%rguGcT|NCop<%2RR34I_cn~&f7uF!Ig&yy|;s$gSXI^TJvNUyY=1C2bEgyoLVQ< zVr1dWd;0H{6L%XuxkYfFjx3ycPyfA2B9_F5L9*!7!*?$-jLX}7wjRO=PzOWL4xS}j z{I%@38wa<@7F*;8cjXejRTr;^M|`-`BKu3{d%Dv_q{$bzon7#MsyX}<>2dy^1-=7PR$X~1Nb$nGQAc78B?={E*+bYCrs#t#)nI!-K00000008(w!V}!A%^FzXB{m6img = anim_frame(&anim_player, data->dir + 4, data->frame / 2); return 1; } + +//--- +// Tile animation functions +//--- + +int anim_tile_start(struct anim_data *data, int init) +{ + data->function = anim_tile_start; + data->frame = init ? 0 : (data->frame + 1) % 5; + data->duration = (data->frame == 0 ? 1000 : 150); + data->img = anim_frame(&anim_tiles, data->frame, 0); + return 0; +} + +int anim_tile_end(struct anim_data *data, int init) +{ + data->function = anim_tile_end; + data->frame = init ? 0 : (data->frame + 1) % 5; + data->duration = (data->frame == 0 ? 1000 : 150); + data->img = anim_frame(&anim_tiles, data->frame, 1); + return 0; +} + +int anim_door_closed(struct anim_data *data, GUNUSED int init) +{ + /* Init takes [data->dir] as input */ + data->function = anim_door_closed; + data->frame = 0 + 16 * (data->dir == DIR_LEFT); + data->duration = 1000; + data->img = anim_frame(&anim_tiles, data->frame & 15, + 2 + (data->frame >= 16)); + return 0; +} + +int anim_door_opening(struct anim_data *data, int init) +{ + data->function = anim_door_opening; + data->frame = init ? 1 + 16 * (data->dir == DIR_LEFT): data->frame + 1; + + if((data->frame & 15) == 6) + return anim_door_open(data, 1); + + data->duration = 75; + data->img = anim_frame(&anim_tiles, data->frame & 15, + 2 + (data->frame >= 16)); + return 1; + +} + +int anim_door_open(struct anim_data *data, GUNUSED int init) +{ + data->function = anim_door_open; + data->frame = 6 + 16 * (data->dir == DIR_LEFT); + data->duration = 1000; + data->img = anim_frame(&anim_tiles, data->frame & 15, + 2 + (data->frame >= 16)); + return 0; +} + +int anim_door_closing(struct anim_data *data, int init) +{ + data->function = anim_door_closing; + data->frame = init ? 7 + 16 * (data->dir == DIR_LEFT): data->frame + 1; + + if((data->frame & 15) == 11) + return anim_door_closed(data, 1); + + data->duration = 75; + data->img = anim_frame(&anim_tiles, data->frame & 15, + 2 + (data->frame >= 16)); + return 1; +} diff --git a/src/animation.h b/src/animation.h index 77e388f..d604599 100644 --- a/src/animation.h +++ b/src/animation.h @@ -7,9 +7,17 @@ struct anim_data; /* anim_function_t: Update function for each animation */ typedef int (anim_function_t)(struct anim_data *data, int init); + anim_function_t anim_player_idle; anim_function_t anim_player_walking; +anim_function_t anim_tile_start; +anim_function_t anim_tile_end; +anim_function_t anim_door_closed; +anim_function_t anim_door_opening; +anim_function_t anim_door_open; +anim_function_t anim_door_closing; + /* struct anim_frame: Subrectangle of an animation sheet */ struct anim_frame { diff --git a/src/engine.c b/src/engine.c index 078dde3..9c4e8d3 100644 --- a/src/engine.c +++ b/src/engine.c @@ -1,29 +1,201 @@ #include #include +#include #include "engine.h" #define CELL_X(x) (-2 + 10 * (x)) #define CELL_Y(y) (-3 + 10 * (y)) +//--- +// Utilities +//--- + + +/* Determine the cycle pattern for a certain door */ +static char const *map_door_cycle(struct map const *map, int x, int y) +{ + int door = map->tiles[y * map->w + x]; + int cycle_id = -1; + + if(door >= TILE_VDOOR && door < TILE_VDOOR + 8) + cycle_id = door - TILE_VDOOR; + else if(door >= TILE_HDOOR && door < TILE_HDOOR + 8) + cycle_id = door - TILE_HDOOR + 8; + else + return NULL; + + return map->door_cycle + map->door_cycle_index[cycle_id]; +} + +/* Check whether a cell of the map is walkable */ +static bool map_walkable(struct game const *game, int x, int y) +{ + /* Refuse to walk on walls */ + int tile = game->map->tiles[y * game->map->w + x]; + if(tile == TILE_WALL) return false; + + /* Refuse to walk on closed doors */ + for(int t = 0; t < game->dynamic_tile_count; t++) + { + struct dynamic_tile const *dt = &game->dynamic_tiles[t]; + if(dt->x != x || dt->y != y) continue; + + char const *cycle = map_door_cycle(game->map, x, y); + if(!dt->state2 && cycle[dt->state] == '#') + return false; + } + + return true; +} + +//--- +// Map loading +//--- + +void engine_load(struct game *game, struct map const *map) +{ + game->map = map; + game->time = 0; + + for(int p = 0; p < PLAYER_COUNT + 1; p++) + game->players[p] = NULL; + + memset(&game->dynamic_tiles, 0, + MAP_DYNAMIC_TILES * sizeof(struct dynamic_tile)); + + /* Create dynamic tiles for suitable tiles in the map */ + int t = 0; + + for(int y = 0; y < map->h && t < MAP_DYNAMIC_TILES; y++) + for(int x = 0; x < map->w && t < MAP_DYNAMIC_TILES; x++) + { + int tile = map->tiles[y*map->w + x]; + struct dynamic_tile *dt = &game->dynamic_tiles[t]; + + dt->y = y; + dt->x = x; + dt->state = 0; + + int is_dynamic = 1; + + if(tile == TILE_START) + { + dt->idle = !anim_tile_start(&dt->anim, 1); + } + else if(tile == TILE_END) + { + dt->idle = !anim_tile_end(&dt->anim, 1); + } + else if(tile >= TILE_KEY && tile < TILE_KEY + 8) + { + /* TODO: Initial keys' dynamic tile information */ + } + else if((tile >= TILE_VDOOR && tile < TILE_VDOOR + 8) || + (tile >= TILE_HDOOR && tile < TILE_HDOOR + 8)) + { + dt->anim.dir = (tile < TILE_HDOOR) ? DIR_UP : DIR_LEFT; + char const *cycle = map_door_cycle(map, x, y); + + if(cycle[dt->state] == '#') + dt->idle = !anim_door_closed(&dt->anim, 1); + else + dt->idle = !anim_door_open(&dt->anim, 1); + + } + else is_dynamic = 0; + + t += is_dynamic; + } + + game->dynamic_tile_count = t; +} + +void engine_spawn(struct game *game) +{ + struct map const *map = game->map; + int p = 0; + + for(int y = 0; y < map->h && game->players[p]; y++) + for(int x = 0; x < map->w && game->players[p]; x++) + { + /* Try to spawn a player at (x,y) */ + if(map->tiles[y*map->w + x] == TILE_START) + { + struct player *player = game->players[p]; + + player->y = y; + player->x = x; + player->dir = DIR_DOWN; + player->anim.dir = DIR_DOWN; + player->idle = !anim_player_idle(&player->anim, 1); + + p++; + } + } +} + +bool engine_all_players_idle(struct game *game) +{ + for(int p = 0; game->players[p]; p++) + { + if(!game->players[p]->idle) return false; + } + return true; +} + +bool engine_all_dynamic_tiles_idle(struct game *game) +{ + for(int t = 0; t < game->dynamic_tile_count; t++) + { + if(!game->dynamic_tiles[t].idle) return false; + } + return true; +} + +bool engine_wins(struct game *game, struct player *player) +{ + /* First check that the player is participating in this game */ + bool found = false; + for(int p = 0; game->players[p] && !found; p++) + { + if(game->players[p] == player) found = true; + } + if(!found) return false; + + int tile = game->map->tiles[player->y * game->map->w + player->x]; + return (tile == TILE_END); +} + //--- // Animations //--- -void engine_tick(struct game *game, int dt) +void engine_tick(struct game *game, int delta_time) { - game->time += dt; + game->time += delta_time; /* Update the animations for every player */ for(int p = 0; game->players[p]; p++) { struct player *player = game->players[p]; - player->anim.duration -= dt; + player->anim.duration -= delta_time; if(player->anim.duration > 0) continue; /* Call the animation function to generate the next frame */ player->idle = !player->anim.function(&player->anim, 0); } + + /* Update animations for dynamic tiles */ + for(int t = 0; t < game->dynamic_tile_count; t++) + { + struct dynamic_tile *dt = &game->dynamic_tiles[t]; + + dt->anim.duration -= delta_time; + if(dt->anim.duration > 0) continue; + + dt->idle = !dt->anim.function(&dt->anim, 0); + } } //--- @@ -37,11 +209,24 @@ static void engine_draw_player(struct player const *player) player->anim.img); } +static void engine_draw_dynamic_tile(struct dynamic_tile const *dt) +{ + /* The -10 is hardcoded to suit the format of the tilesheet */ + dframe(CELL_X(dt->x) + dt->anim.dx, + CELL_Y(dt->y) + dt->anim.dy - 10, + dt->anim.img); +} + void engine_draw(struct game const *game) { dclear(C_WHITE); dimage(0, 0, game->map->img); + for(int t = 0; t < game->dynamic_tile_count; t++) + { + engine_draw_dynamic_tile(&game->dynamic_tiles[t]); + } + for(int p = 0; game->players[p]; p++) { engine_draw_player(game->players[p]); @@ -52,13 +237,6 @@ void engine_draw(struct game const *game) // Physics //--- -/* Check whether a cell of the map is walkable */ -static int map_walkable(struct map const *map, int x, int y) -{ - int tile = map->tiles[y * map->w + x]; - return (tile != TILE_WALL); -} - int engine_move(struct game *game, struct player *player, int dir) { int dx = (dir == DIR_RIGHT) - (dir == DIR_LEFT); @@ -73,7 +251,7 @@ int engine_move(struct game *game, struct player *player, int dir) player->anim.dir = dir; /* Only move the player if the destination is walkable */ - if(!map_walkable(game->map, player->x + dx, player->y + dy)) + if(!map_walkable(game, player->x + dx, player->y + dy)) { /* If not, set the new idle animation */ if(dir != olddir) @@ -91,3 +269,48 @@ int engine_move(struct game *game, struct player *player, int dir) return 1; } + +void engine_finish_turn(struct game *game) +{ + /* TODO: Have players pick up keys and open corresponding doors */ + + /* Update door cycles */ + for(int t = 0; t < game->dynamic_tile_count; t++) + { + struct dynamic_tile *dt = &game->dynamic_tiles[t]; + char const *cycle = map_door_cycle(game->map, dt->x, dt->y); + if(!cycle) continue; + + /* Determine current open/closed status */ + int current_status = dt->state2 ? '.' : cycle[dt->state]; + + /* Move to next cycle state */ + dt->state++; + if(cycle[dt->state] == ' ' || cycle[dt->state] == 0) + dt->state = 0; + dt->state2 = 0; + + /* Determine next open/closed status (this might be different + from the cycle state if a player standing in the door + prevents if from closing) */ + int new_status = cycle[dt->state]; + + for(int p = 0; game->players[p]; p++) + { + struct player *player = game->players[p]; + if(player->x == dt->x && player->y == dt->y) + { + new_status = '.'; + dt->state2 = 1; + } + } + + if(new_status == current_status) continue; + + /* Set animations when changing status */ + if(new_status == '#') + dt->idle = !anim_door_closing(&dt->anim, 1); + else if(new_status == '.') + dt->idle = !anim_door_opening(&dt->anim, 1); + } +} diff --git a/src/engine.h b/src/engine.h index da7d5dd..1bf484c 100644 --- a/src/engine.h +++ b/src/engine.h @@ -2,12 +2,16 @@ #define _MYSTNB_ENGINE_H #include +#include #include #include "animation.h" /* Maximum number of players */ #define PLAYER_COUNT 1 +/* Maximum number of dynamic tiles on the map */ +#define MAP_DYNAMIC_TILES 32 + /* Time per engine tick (ms) */ #define ENGINE_TICK 25 @@ -24,7 +28,7 @@ struct player int x, y; /* Direction currently facing */ int dir; - /* Whether playing is currently idle (ie. can move) */ + /* Whether player is currently idle (ie. can move) */ int idle; /* Current animation function and data */ struct anim_data anim; @@ -63,17 +67,50 @@ enum map_tile TILE_HDOOR = 32, }; +/* struct dynamic_tile: Dynamic tile information */ +struct dynamic_tile +{ + /* Position */ + int x, y; + /* Current state */ + int state; + int state2; + /* Whether animation is idle */ + int idle; + /* Current animation */ + struct anim_data anim; +}; + /* struct game: A running game with a map and some players */ struct game { /* Current map */ - struct map *map; + struct map const *map; /* Players */ struct player *players[PLAYER_COUNT + 1]; /* Current game time (ms) */ int time; + /* Dynamic tiles */ + struct dynamic_tile dynamic_tiles[MAP_DYNAMIC_TILES]; + /* Number of dynamic tiles */ + int dynamic_tile_count; }; +/* Load map with no players */ +void engine_load(struct game *game, struct map const *map); + +/* Spawn all the players on the map */ +void engine_spawn(struct game *game); + +/* Check whether all players are idle */ +bool engine_all_players_idle(struct game *game); + +/* Check whether all dynamic tiles are idle */ +bool engine_all_dynamic_tiles_idle(struct game *game); + +/* Check whether a player stands on the exit */ +bool engine_wins(struct game *game, struct player *player); + /* Update animations */ void engine_tick(struct game *game, int dt); @@ -84,4 +121,7 @@ void engine_draw(struct game const *game); 0 otherwise (collisions or out-of-bounds moves) */ int engine_move(struct game *game, struct player *player, int direction); +/* Process automatic actions at the end of a turn */ +void engine_finish_turn(struct game *game); + #endif /* _MYSTNB_ENGINE_H */ diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..ffeb799 --- /dev/null +++ b/src/game.c @@ -0,0 +1,116 @@ +#include +#include +#include +#include + +#include "game.h" +#include "engine.h" + +/* All maps */ + +extern struct map map_lv1; + +struct map *maps[] = { + &map_lv1, +}; + +/* Phases of a turn in the game */ + +enum turn_phase +{ + PHASE_IDLE = 0, + PHASE_MOVE = 1, + PHASE_UPDATE = 2, +}; + +/* Returns a direction to move in */ +static int get_inputs(void) +{ + int opt = GETKEY_DEFAULT & ~GETKEY_REP_ARROWS; + int timeout = 1; + + while(1) + { + key_event_t ev = getkey_opt(opt, &timeout); + if(ev.type == KEYEV_NONE) return -1; + + int key = ev.key; + if(key == KEY_DOWN) return DIR_DOWN; + if(key == KEY_RIGHT) return DIR_RIGHT; + if(key == KEY_UP) return DIR_UP; + if(key == KEY_LEFT) return DIR_LEFT; + } +} + +/* Notify play_level() that the next frame is due soon */ +static int callback_tick(volatile int *tick) +{ + *tick = 1; + return TIMER_CONTINUE; +} + +bool play_level(int level) +{ + if(level < 1 || level > 8) return false; + + /* Load the map */ + struct game game; + engine_load(&game, maps[level-1]); + + /* Create and spawn a solo player */ + struct player singleplayer; + game.players[0] = &singleplayer; + engine_spawn(&game); + + /* Start the frame timer */ + static volatile int tick = 1; + int t = timer_configure(TIMER_ANY, ENGINE_TICK*1000, + GINT_CALL(callback_tick, &tick)); + if(t < 0) return false; + timer_start(t); + + enum turn_phase phase = PHASE_IDLE; + bool succeeded = false; + + while(1) + { + while(!tick) sleep(); + tick = 0; + + engine_draw(&game); + dupdate(); + + int dir = get_inputs(); + + /* If the player inputs a control during IDLE, move them */ + if(phase == PHASE_IDLE && dir >= 0) + { + if(engine_move(&game, &singleplayer, dir)) + phase = PHASE_MOVE; + } + /* When the move is finished, perform a map update */ + else if(phase == PHASE_MOVE && engine_all_players_idle(&game)) + { + /* TODO: Winning condition */ + if(engine_wins(&game, &singleplayer)) + { + succeeded = true; + break; + } + + phase = PHASE_UPDATE; + engine_finish_turn(&game); + } + /* When update finishes, start another turn */ + else if(phase == PHASE_UPDATE && + engine_all_dynamic_tiles_idle(&game)) + { + phase = PHASE_IDLE; + } + + engine_tick(&game, ENGINE_TICK); + } + + timer_stop(t); + return succeeded; +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..bb817bb --- /dev/null +++ b/src/game.h @@ -0,0 +1,9 @@ +#ifndef _MYSTNB_GAME_H +#define _MYSTNB_GAME_H + +#include + +/* Play the level, returns whether player completed the level */ +bool play_level(int level); + +#endif /* _MYSTNB_GAME_H */ diff --git a/src/main.c b/src/main.c index 6c67035..0d97a18 100644 --- a/src/main.c +++ b/src/main.c @@ -1,8 +1,8 @@ #include #include -#include -#include +#include +#include "game.h" #include "engine.h" static void draw_menu(int selected) @@ -54,88 +54,19 @@ static int main_menu(void) selected--; if(key == KEY_RIGHT && selected < 8) selected++; + if(key == KEY_EXIT) + gint_osmenu(); } return selected; } -/* Returns a direction to move in */ -static int get_inputs(void) -{ - int opt = GETKEY_DEFAULT & ~GETKEY_REP_ARROWS; - int timeout = 1; - - while(1) - { - key_event_t ev = getkey_opt(opt, &timeout); - if(ev.type == KEYEV_NONE) return -1; - - int key = ev.key; - if(key == KEY_DOWN) return DIR_DOWN; - if(key == KEY_RIGHT) return DIR_RIGHT; - if(key == KEY_UP) return DIR_UP; - if(key == KEY_LEFT) return DIR_LEFT; - } -} - -static int callback_tick(volatile int *tick) -{ - *tick = 1; - return TIMER_CONTINUE; -} int main(void) { - GUNUSED int level = main_menu(); - - struct player singleplayer = { - .x = 2, - .y = 3, - .dir = DIR_DOWN, - .anim.function = anim_player_idle, - .anim.dir = DIR_DOWN, - }; - singleplayer.idle = !anim_player_idle(&singleplayer.anim, 1); - - extern struct map map_lv1; - - struct game game = { - .map = &map_lv1, - .players = { &singleplayer, NULL }, - .time = 0, - }; - - /* Global tick clock */ - static volatile int tick = 1; - - int t = timer_configure(TIMER_ANY, ENGINE_TICK*1000, - GINT_CALL(callback_tick, &tick)); - if(t >= 0) timer_start(t); - - int level_finished = 0; - while(!level_finished) + while(1) { - while(!tick) sleep(); - tick = 0; - - engine_draw(&game); - dupdate(); - - int dir = get_inputs(); - int turn_finished = 0; - - if(dir >= 0) - { - turn_finished = engine_move(&game, &singleplayer, dir); - } - if(turn_finished) - { - /* Update doors, etc */ - } - - engine_tick(&game, ENGINE_TICK); + GUNUSED int level = main_menu(); + play_level(1); } - - if(t >= 0) timer_stop(t); - return 1; }