aboutsummaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorEmil Williams2026-02-17 06:47:32 +0000
committerEmil Williams2026-02-17 07:56:38 +0000
commit428b68f791edcb89c811c9aca5dedbf7ec5d1335 (patch)
tree1608be97acc9c94e4cfca18264de86e8b7557e5b /source
parent0bb3381eefcb645f1abd516e3a6827bad1767406 (diff)
downloadMonobomberman-428b68f791edcb89c811c9aca5dedbf7ec5d1335.tar.xz
Monobomberman-428b68f791edcb89c811c9aca5dedbf7ec5d1335.tar.zst
revised tiles, powerups, timer + basic gameplay8e8m
Diffstat (limited to 'source')
-rw-r--r--source/all.h183
-rw-r--r--source/game.c73
-rw-r--r--source/gamemode.c101
-rw-r--r--source/main.c60
-rw-r--r--source/options.gperf28
-rw-r--r--source/render.c55
-rw-r--r--source/update.c138
7 files changed, 387 insertions, 251 deletions
diff --git a/source/all.h b/source/all.h
index 5aa6fd1..f98943d 100644
--- a/source/all.h
+++ b/source/all.h
@@ -41,7 +41,7 @@
#define TEXTURE_LIMIT (3*8)
-/* Spritesheets will be (128*4x128*6)
+/* Spritesheets will be (N*4xN*6)
group 0 is [0-1][0 ] / un/breakable walls
group 1 (just explosions, the nulls are skipped for sake of compactness)
is [2-3][0 ] |
@@ -50,65 +50,74 @@
group 3 is [0-3][3 ] / player
group 4 is [0-3][4 ] / bomb
group 5 is [0-3][4 ] / enemy
-
*/
-enum {
- /* each group is for its own group of textures,
- with at most 8 textures being possible within a functional group.
- we assume LSB per x86-64 ABI,
- this would need to be redefined / bit field part removed */
- EXPLOSIVE = (1 << 3),
- PASSIBLE = (1 << 4),
- BREAKABLE = (1 << 5),
- LETHAL = (1 << 6),
- POWERUP = (1 << 7),
- /* lethality / breakable / explosive are not specially grouped. */
-
- /* group 0 "impassible" set */
- IMPASSIBLE_WALL = 0,
- IMPASSIBLE_BREAKABLE_WALL = 1,
- IMPASSIBLE_NOTHING = 2,
-
- /* group 1 "passible" set */
- PASSIBLE_NOTHING = PASSIBLE | 0,
- PASSIBLE_NOTHING_LETHAL = PASSIBLE | LETHAL | 1,
- PASSIBLE_EXPLOSIVE_LETHAL = PASSIBLE | LETHAL | EXPLOSIVE | 0,
- PASSIBLE_EXPLOSIVE_LETHAL_END = PASSIBLE | LETHAL | EXPLOSIVE | 1,
-
- /* group 2 "pickup" set */
- POWERUP_BOMB = PASSIBLE | POWERUP | 0,
- POWERUP_POWER = PASSIBLE | POWERUP | 1,
- POWERUP_SPEED = PASSIBLE | POWERUP | 2,
- // These will probably never be negative:
- POWERUP_PIERCE = PASSIBLE | POWERUP | 3,
- POWERUP_KICK = PASSIBLE | POWERUP | 4,
- POWERUP_THROW = PASSIBLE | POWERUP | 5,
- POWERUP_BOUNCE = PASSIBLE | POWERUP | 6,
-
- /* group 3 and higher is not directly classified by the tile system */
+#define TILE_LENGTH_LIMIT 21
+enum powerup {
+ POWERUP_BOMB = 1,
+ POWERUP_POWER,
+ POWERUP_SPEED,
+ POWERUP_PIERCE,
+ POWERUP_KICK,
+ POWERUP_THROW,
+ POWERUP_BOUNCE,
+ POWERUP_CURSE,
};
-#define TILE_LENGTH_LIMIT 21
+/* highly dependent on atlas definition in game.c */
+enum atlas {
+ RENDER_UNBREAKABLE = 0,
+ RENDER_BREAKABLE,
+
+ RENDER_EXPLOSION_START,
+ RENDER_EXPLOSION_END,
+
+ RENDER_POWERUP_BOMB,
+ RENDER_POWERUP_POWER,
+ RENDER_POWERUP_SPEED,
+ RENDER_POWERUP_PIERCE,
+ RENDER_POWERUP_KICK,
+ RENDER_POWERUP_THROW,
+ RENDER_POWERUP_BOUNCE,
+ RENDER_POWERUP_CURSE,
+
+ RENDER_PLAYER_RIGHT,
+ RENDER_PLAYER_LEFT,
+ RENDER_PLAYER_UP,
+ RENDER_PLAYER_DOWN,
+
+ RENDER_BOMB_0,
+ RENDER_BOMB_1,
+ RENDER_BOMB_2,
+ RENDER_BOMB_3,
+
+ RENDER_ENEMY_RIGHT,
+ RENDER_ENEMY_LEFT,
+ RENDER_ENEMY_UP,
+ RENDER_ENEMY_DOWN,
+};
+
+typedef struct {
+ u8 texture : 1; // frames for everything that doesn't move, static assets.
+ u8 explosive : 1; // explosion animations, coopts texture for explosion frames.
+ u8 passable : 1; // important subgroup.
+ u8 breakable : 1;
+ u8 lethal : 1; // player will die if they occupy this space during the check
+ i8 pickup : 4; // positive / negative pickups
+ // 3 bits left for extensions.
+} tile_data_t;
+
+static const tile_data_t impassable_tile = (tile_data_t) {.passable = 0};
+static const tile_data_t impassable_wall = (tile_data_t) {.passable = 0, .texture = 1};
+static const tile_data_t breakable_wall = (tile_data_t) {.passable = 0, .texture = 1, .breakable = 1};
+static const tile_data_t passable_tile = (tile_data_t) {.passable = 1};
+static const tile_data_t explosive_tile = (tile_data_t) {.passable = 1, .explosive = 1, .lethal = 1, .texture = 0};
+
typedef struct {
- union {
- u16 _;
- struct {
- u8 texture : 3; // frames for everything that doesn't move, static assets.
- u8 explosive : 1; // explosion animations, coopts texture for explosion frames.
- u8 passable : 1; // important subgroup.
- u8 breakable : 1;
- u8 lethal : 1; // player will die if they occupy this space during the check
- i8 pickup : 4; // positive / negative pickups
- // 5 bits left for extensions.
- };
- } state[TILE_LENGTH_LIMIT][TILE_LENGTH_LIMIT] aligned;
+ tile_data_t state[TILE_LENGTH_LIMIT][TILE_LENGTH_LIMIT] aligned;
u8 color aligned;
- Rectangle wall[2] aligned;
- Rectangle explosion[2] aligned;
- Rectangle powerup[8] aligned;
} tiles_t;
#define PLAYER_LIMIT (1<<2)
@@ -116,66 +125,59 @@ typedef struct {
enum {
RIGHT, LEFT, UP, DOWN
};
-
+
+typedef struct {
+ u8 bomb_limit : 4;
+ u8 bomb_count : 4;
+ u8 power : 4; // < MAX(TILE_WIDTH, TILE_HEIGHT)
+ u8 speed : 4; // travels n units per second
+ u8 pierce : 1;
+ u8 kick : 1; // no intent to implement
+ u8 throw : 1; // no intent to implement
+ u8 bounce : 1; // no intent to implement
+ u8 alive : 1;
+ u8 direction : 2; // right left up down
+ u8 moving : 1;
+ // 10 bits left for extensions.
+} player_data_t;
+
typedef struct {
i16 x[PLAYER_LIMIT] aligned;
i16 y[PLAYER_LIMIT] aligned;
- f32 animation_x[PLAYER_LIMIT] aligned;
- f32 animation_y[PLAYER_LIMIT] aligned;
- union {
- u32 _;
- struct {
- u8 bomb_limit : 4;
- u8 bomb_count : 4;
- u8 power : 4; // < MAX(TILE_WIDTH, TILE_HEIGHT)
- u8 speed : 4; // travels n units per second
- u8 pierce : 1;
- u8 kick : 1;
- u8 throw : 1; // no intent to implement
- u8 bounce : 1;
- u8 alive : 1;
- u8 direction : 2; // right left up down
- u8 moving : 1;
- // 10 bits left for extensions.
- };
- } state[PLAYER_LIMIT] aligned;
+ player_data_t state[PLAYER_LIMIT] aligned;
u8 color[PLAYER_LIMIT] aligned;
- Rectangle player[4] aligned;
} players_t;
#define BOMB_LIMIT (1<<4)
-
+
typedef struct {
- i16 x[PLAYER_LIMIT][BOMB_LIMIT] aligned;
- i16 y[PLAYER_LIMIT][BOMB_LIMIT] aligned;
- union {
- u16 _;
- struct {
u8 power : 4; // < MAX(TILE_WIDTH, TILE_HEIGHT)
u8 pierce : 1;
- u8 bounce : 1;
+ u8 bounce : 1; // no intent to implement
// 10 bits left for extensions.
- };
- } state[PLAYER_LIMIT][BOMB_LIMIT] aligned;
+} bomb_data_t;
+
+typedef struct {
+ i16 x[PLAYER_LIMIT][BOMB_LIMIT] aligned;
+ i16 y[PLAYER_LIMIT][BOMB_LIMIT] aligned;
+ bomb_data_t state[PLAYER_LIMIT][BOMB_LIMIT] aligned;
u16 timer[PLAYER_LIMIT][BOMB_LIMIT] aligned; // updates until explosion.
u8 color[2] aligned;
- Rectangle bomb[4] aligned;
} bombs_t;
#define ENEMY_LIMIT (1<<4)
-
+
enum {
MOVEMENT_VERTICAL,
MOVEMENT_HORIZONTAL,
MOVEMENT_RANDOM,
MOVEMENT_LAST,
};
-
+
typedef struct {
i16 x[ENEMY_LIMIT] aligned;
i16 y[ENEMY_LIMIT] aligned;
u8 movement[ENEMY_LIMIT] aligned;
- Rectangle enemy[4] aligned;
} enemies_t;
#define CONFIG_STRING_LIMIT 128
@@ -200,20 +202,21 @@ typedef struct {
bombs_t bombs aligned;
enemies_t enemies aligned;
config_t config aligned;
+ u16 time_limit aligned;
+ u8 client aligned;
Font font aligned;
-
+ Rectangle atlas[4*6] aligned;
Texture spritesheet aligned;
Camera2D camera aligned;
- u16 time_limit aligned;
- u8 client aligned;
} game_t;
/* game.c */
+void GameInitialize(game_t * game);
+void GameDeinitialize(game_t * game);
void GameStart(config_t config);
void GameResize(game_t * game);
-void GameReinitialize(game_t * game);
/* gamemode.c */
diff --git a/source/game.c b/source/game.c
index d98b235..943cf96 100644
--- a/source/game.c
+++ b/source/game.c
@@ -1,15 +1,8 @@
#include "all.h"
-static void GameInitialize(game_t * game);
-static void GameDeinitialize(game_t * game);
static void GameLoop(game_t * game);
static void GameReport(game_t * game, f32 fps, f32 ups, u32 total_fps, u32 total_ups);
-void GameReinitialize(game_t * game) {
- GameDeinitialize(game);
- GameInitialize(game);
-}
-
void GameStart(config_t config) {
_Alignas(64) game_t game[1] = {0};
game->config = config;
@@ -40,7 +33,7 @@ void GameResize(game_t * game) {
}
}
-static void GameInitialize(game_t * game) {
+void GameInitialize(game_t * game) {
/* Strict parameters */
#define DEFAULT(a, b) ((b) ? (b) : (a))
game->config.resolution_x = MAX(200, DEFAULT(600, game->config.resolution_x));
@@ -59,58 +52,47 @@ static void GameInitialize(game_t * game) {
{ strlcpy(game->config.window_name, "Unset Window Name, lol lmao", CONFIG_STRING_LIMIT); }
#undef DEFAULT
+
{
int t = game->config.spritesheet_scale;
- /* better, but not really good, it's FINE */
- Rectangle wall[2] = // group 0
- {(Rectangle){ 0, 0, t, t},
- (Rectangle){t - 1, 0, t, t}};
-
- Rectangle explosion[2] = // group 1
- {(Rectangle){t * 2 - 1, 0, t, t},
- (Rectangle){t * 3 - 1, 0, t, t}};
-
- Rectangle powerup[8] = // group 2
- {(Rectangle){ 0, t, t, t},
+ Rectangle atlas[4*6] =
+ /* walls : group 0 */
+ {(Rectangle){ 0, 0, t, t}, /* unbreakable */
+ (Rectangle){t - 1, 0, t, t}, /* breakable */
+ /* explosions : group 1 */
+ (Rectangle){t * 2 - 1, 0, t, t},
+ (Rectangle){t * 3 - 1, 0, t, t},
+ /* powerup : group 2 */
+ (Rectangle){ 0, t, t, t},
(Rectangle){t - 1, t, t, t},
(Rectangle){t * 2 - 1, t, t, t},
(Rectangle){t * 3 - 1, t, t, t},
(Rectangle){ 0, t * 2, t, t},
(Rectangle){t - 1, t * 2, t, t},
(Rectangle){t * 2 - 1, t * 2, t, t},
- (Rectangle){t * 3 - 1, t * 2, t, t}};
-
- Rectangle player[4] = // group 3
- {(Rectangle){ 0, t * 3, t, t},
+ (Rectangle){t * 3 - 1, t * 2, t, t},
+ /* player : group 3 */
+ (Rectangle){ 0, t * 3, t, t},
(Rectangle){t - 1, t * 3, t, t},
(Rectangle){t * 2 - 1, t * 3, t, t},
- (Rectangle){t * 3 - 1, t * 3, t, t}};
-
- Rectangle bomb[4] = // group 4
- {(Rectangle){ 0, t * 4, t, t},
+ (Rectangle){t * 3 - 1, t * 3, t, t},
+ /* bomb : group 4 */
+ (Rectangle){ 0, t * 4, t, t},
(Rectangle){t - 1, t * 4, t, t},
(Rectangle){t * 2 - 1, t * 4, t, t},
- (Rectangle){t * 3 - 1, t * 4, t, t}};
-
- Rectangle enemy[4] = // group 5
- {(Rectangle){ 0, t * 5, t, t},
+ (Rectangle){t * 3 - 1, t * 4, t, t},
+ /* enemy : group 5 */
+ (Rectangle){ 0, t * 5, t, t},
(Rectangle){t - 1, t * 5, t, t},
(Rectangle){t * 2 - 1, t * 5, t, t},
(Rectangle){t * 3 - 1, t * 5, t, t}};
- memcpy(game->tiles.wall, wall, sizeof(wall));
- memcpy(game->tiles.explosion, explosion, sizeof(explosion));
- memcpy(game->tiles.powerup, powerup, sizeof(powerup));
- memcpy(game->players.player, player, sizeof(player));
- memcpy(game->bombs.bomb, bomb, sizeof(bomb));
- memcpy(game->enemies.enemy, enemy, sizeof(enemy));
+ memcpy(game->atlas, atlas, sizeof(atlas));
}
MultiPlayer(game);
- game->tiles.color = (rand() % 4) | ((rand() % 4) << 2) | ((rand() % 4) << 4) | GAME_OPAQUE;
- if (game->tiles.color == GAME_OPAQUE) { game->tiles.color |= GAME_WHITE; }
-
+ game->tiles.color = (rand() % 2 + 2) | ((rand() % 2 + 2) << 2) | ((rand() % 2 + 2) << 4) | GAME_OPAQUE;
game->bombs.color[0] = GAME_WHITE | GAME_OPAQUE;
game->bombs.color[1] = GAME_RED | GAME_OPAQUE;
@@ -125,7 +107,7 @@ static void GameInitialize(game_t * game) {
ClearWindowState(FLAG_WINDOW_HIDDEN);
}
-static void GameDeinitialize(game_t * game) {
+void GameDeinitialize(game_t * game) {
UnloadTexture(game->spritesheet);
if (GetFontDefault().texture.id != game->font.texture.id) { UnloadFont(game->font); }
RaylibDeinitialize();
@@ -186,10 +168,10 @@ static void GameLoop(game_t * game) {
StepStart(print);
GameReport(game,
- round(frames_per_second / TIMESPEC_TO_F64(print_delta)),
- round(updates_per_second / TIMESPEC_TO_F64(print_delta)),
- frame_total,
- update_total);
+ round(frames_per_second / TIMESPEC_TO_F64(print_delta)),
+ round(updates_per_second / TIMESPEC_TO_F64(print_delta)),
+ frame_total,
+ update_total);
frames_per_second = updates_per_second = 0;
StepStop(print);
@@ -204,3 +186,4 @@ static void GameReport(game_t * game, f32 fps, f32 ups, u32 total_fps, u32 total
fps, ups, total_fps, total_ups);
#endif
}
+
diff --git a/source/gamemode.c b/source/gamemode.c
index d548773..6a09b08 100644
--- a/source/gamemode.c
+++ b/source/gamemode.c
@@ -1,33 +1,84 @@
#include "all.h"
-void MultiPlayer(game_t * game) {
+static void MapPrint(game_t * game) {
u8 width = game->config.map_x;
u8 height = game->config.map_y;
- int i, j;
+ printf("texture, explosive, breakable, lethal, pickup\n");
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < height; ++j) {
+ tile_data_t * tile = &game->tiles.state[i][j];
+ printf("%2d:%1d:%1d:%1d:%1d ", tile->texture, tile->explosive, tile->breakable, tile->lethal, tile->pickup);
+ }
+ printf("\n");
+ }
+}
- for (i = 0; i < PLAYER_LIMIT; ++i) {
- for (j = 0; j < BOMB_LIMIT; ++j) {
+static void GamemodeReset(game_t * game) {
+ for (int i = 0; i < PLAYER_LIMIT; ++i) {
+ for (int j = 0; j < BOMB_LIMIT; ++j) {
game->bombs.timer[i][j] = 0;
}
}
+ bzero(game->players.state, sizeof(*game->players.state) * PLAYER_LIMIT);
+}
+
+static void PlayerPlaceCorners(game_t * game, u8 * color, size_t color_count) {
+ u8 width = game->config.map_x;
+ u8 height = game->config.map_y;
+ float player_x[4] =
+ {0, (width-1), (width-1), 0 };
+ float player_y[4] =
+ {0, (height-1), 0, (height-1)};
+
+ for (int i = 0; i < MIN(game->config.player_count, PLAYER_LIMIT); ++i) {
+ game->players.x[i] = player_x[i % color_count];
+ game->players.y[i] = player_y[i % color_count];
+ game->players.color[i] = color[i % color_count];
+ }
+}
+
+static void MapClearCorners(game_t * game) {
+ /* areas that must be passible nothings */
+ u8 width = game->config.map_x;
+ u8 height = game->config.map_y;
+
+ u8 offset_x[12] =
+ {0, 1, 0, width-1, width-2, width-1, width-1, width-2, width-1, 0, 1, 0};
+ u8 offset_y[12] =
+ {0, 0, 1, height-1, height-1, height-2, 0, 0, 1, height-1, height-1, height-2};
+
+ for (int i = 0; i < MIN((game->config.player_count * 3), 12); ++i) {
+ game->tiles.state[offset_x[i]][offset_y[i]] = passable_tile;
+ }
+}
+
+void MultiPlayer(game_t * game) {
+ u8 width = game->config.map_x;
+ u8 height = game->config.map_y;
+ int i, j;
+
+ GamemodeReset(game);
+
for (i = 0; i < width; ++i) {
for (j = 0; j < height; ++j) {
- game->tiles.state[i][j]._ = rand() % 10 ? IMPASSIBLE_BREAKABLE_WALL : PASSIBLE_NOTHING;
- /* game->tiles.state[i][j]._ |= rand() % 3 ? 0 : POWERUP; */
+ game->tiles.state[i][j] = rand() % 10 ? breakable_wall : passable_tile;
+ if (rand() % 3 == 0)
+ {
+ game->tiles.state[i][j].pickup = rand() % 5 ? POWERUP_POWER : POWERUP_BOMB;
+ } else if (rand() % 10 == 0) {
+ game->tiles.state[i][j].pickup = POWERUP_PIERCE;
+ }
}
}
for (i = 1; i < width; i += 2) {
for (j = 1; j < height; j += 2) {
- game->tiles.state[i][j]._ = IMPASSIBLE_WALL;
+ game->tiles.state[i][j] = impassable_wall;
}
}
- bzero(game->players.state, sizeof(*game->players.state) * PLAYER_LIMIT);
-
for (i = 0; i < MIN(PLAYER_LIMIT, game->config.player_count); ++i) {
- bzero(&game->players.state[i]._, sizeof(game->players.state[i]._));
game->players.state[i].bomb_limit = 1;
game->players.state[i].power = 2;
game->players.state[i].speed = 2;
@@ -37,11 +88,6 @@ void MultiPlayer(game_t * game) {
game->time_limit = game->config.ups * 60 * 3;
- float player_x[4] =
- {0, (width-1), (width-1), 0 };
- float player_y[4] =
- {0, (height-1), 0, (height-1)};
-
u8 color[4] = {
GAME_RED | GAME_GREEN | GAME_OPAQUE,
GAME_RED | GAME_GREEN | GAME_BLUE | GAME_OPAQUE,
@@ -49,26 +95,7 @@ void MultiPlayer(game_t * game) {
GAME_BLUE | GAME_OPAQUE,
};
- for (i = 0; i < MIN(game->config.player_count, PLAYER_LIMIT); ++i) {
- game->players.x[i] = player_x[i % 4];
- game->players.y[i] = player_y[i % 4];
- game->players.color[i] = color[i % 4];
- }
-
- /* areas that must be passible nothings */
- u8 offset_x[12] =
- {0, 1, 0, width-1, width-2, width-1, width-1, width-2, width-1, 0, 1, 0};
- u8 offset_y[12] =
- {0, 0, 1, height-1, height-1, height-2, 0, 0, 1, height-1, height-1, height-2};
-
- for (i = 0; i < MIN((game->config.player_count * 3), 12); ++i) {
- game->tiles.state[offset_x[i]][offset_y[i]]._ = PASSIBLE_NOTHING;
- }
-
- for (i = 0; i < width; ++i) {
- for (j = 0; j < height; ++j) {
- printf("%3d ", game->tiles.state[i][j]._);
- }
- printf("\n");
- }
+ PlayerPlaceCorners(game, color, 4);
+ MapClearCorners(game);
+ MapPrint(game);
}
diff --git a/source/main.c b/source/main.c
index 70ab419..bc55b2d 100644
--- a/source/main.c
+++ b/source/main.c
@@ -1,10 +1,66 @@
#include "all.h"
+#include <options.h>
+
+#define arg if (++arguments, !--count) { goto help; } else
+config_t Arguments(int count, char ** arguments) {
+ config_t config = {0};
+ while (++arguments, --count > 0) {
+ while (**arguments == '-') { ++*arguments; }
+ struct options * option = options_lookup(*arguments,strlen(*arguments));
+ if (!option) {
+ printf("Unknown option '%s', try 'help'\n", *arguments);
+ goto abort;
+ }
+ switch (option->number) {
+ case OPTION_HELP: {
+ help:
+ printf("help/h/? -- -- This.\n"
+ "resolution -- <x> -- The on-display resolution squared\n"
+ "fps -- <x> -- Framerate\n"
+ "ups -- <x> -- Updates\n"
+ "font -- </path/to> -- Glorious Font\n"
+ "spritesheet -- </path/to> -- The spritesheet for displaying all of reality\n"
+ "spritesheet_scale -- <x> -- The square scale of the above\n"
+ "players -- <x> -- N players\n"
+ "map/map_size -- <x y> -- the in-game map size\n"
+ );
+ exit(0); }
+ case OPTION_RESOLUTION: {
+ arg { config.resolution_x = atoi(*arguments); }
+ break; }
+ case OPTION_FPS: {
+ arg { config.fps = atoi(*arguments); }
+ break; }
+ case OPTION_UPS: {
+ arg { config.ups = atoi(*arguments); }
+ break; }
+ case OPTION_PLAYER_COUNT: {
+ arg { config.player_count = atoi(*arguments); }
+ break; }
+ case OPTION_MAP_SIZE: {
+ arg { config.map_x = atoi(*arguments); }
+ arg { config.map_y = atoi(*arguments); }
+ break; }
+ case OPTION_SPRITESHEET: {
+ arg { strlcpy(config.spritesheet, *arguments, CONFIG_STRING_LIMIT); }
+ break; }
+ case OPTION_SPRITESHEET_SCALE: {
+ arg { config.spritesheet_scale = atoi(*arguments); }
+ break; }
+ case OPTION_FONT: {
+ arg { strlcpy(config.font, *arguments, CONFIG_STRING_LIMIT); }
+ break; }
+ }
+ }
+ return config;
+abort:
+ exit(1);
+}
int Main(int count, char ** arguments)
{
- (void)count;
char * window_name = arguments[0];
- config_t config = (config_t){0};
+ config_t config = Arguments(count, arguments);
char * p = strchr(window_name, '/');
strlcpy(config.window_name, p ? p+1 : window_name, CONFIG_STRING_LIMIT);
srand(time(NULL));
diff --git a/source/options.gperf b/source/options.gperf
new file mode 100644
index 0000000..5d07a6f
--- /dev/null
+++ b/source/options.gperf
@@ -0,0 +1,28 @@
+%{
+enum {
+OPTION_NONE=0,
+OPTION_HELP,
+OPTION_RESOLUTION,
+OPTION_FPS,
+OPTION_UPS,
+OPTION_FONT,
+OPTION_SPRITESHEET,
+OPTION_SPRITESHEET_SCALE,
+OPTION_PLAYER_COUNT,
+OPTION_MAP_SIZE,
+};
+%}
+struct options { char * name; int number; };
+%%
+help, OPTION_HELP
+h, OPTION_HELP
+?, OPTION_HELP
+resolution, OPTION_RESOLUTION
+fps, OPTION_FPS
+ups, OPTION_UPS
+font, OPTION_FONT
+spritesheet, OPTION_SPRITESHEET
+spritesheet_scale, OPTION_SPRITESHEET_SCALE
+players, OPTION_PLAYER_COUNT
+map, OPTION_MAP_SIZE
+map_size, OPTION_MAP_SIZE
diff --git a/source/render.c b/source/render.c
index 5f5fb25..3ee28e2 100644
--- a/source/render.c
+++ b/source/render.c
@@ -11,11 +11,7 @@ void Render(game_t * game, f64 interpolation) {
BeginDrawing();
-#ifndef NDEBUG
- ClearBackground(BLACK);
-#else
ClearBackground(COLOR_TO_RAYLIB(game->tiles.color));
-#endif
BeginMode2D(game->camera);
RenderTiles(game);
RenderBombs(game);
@@ -27,30 +23,26 @@ void Render(game_t * game, f64 interpolation) {
SwapScreenBuffer();
}
+always_inline void AtlasDraw(game_t * game, u16 atlas_number, u8 color, int i, int j) {
+ DrawTextureRec(
+ game->spritesheet,
+ game->atlas[atlas_number],
+ (Vector2) {i*game->config.spritesheet_scale, j*game->config.spritesheet_scale},
+ COLOR_TO_RAYLIB(color));
+}
+
void RenderTiles(game_t * game) {
for (int i = 0; i < game->config.map_x; ++i) {
for (int j = 0; j < game->config.map_y; ++j) {
- #ifndef NDEBUG
- if ((game->tiles.state[i][j]._ | 1) & PASSIBLE || game->tiles.state[i][j]._ == IMPASSIBLE_NOTHING) {
- DrawRectangleRec((Rectangle) {i*game->config.spritesheet_scale, j*game->config.spritesheet_scale, game->config.spritesheet_scale, game->config.spritesheet_scale}, COLOR_TO_RAYLIB(game->tiles.color));
+ tile_data_t * tile = &game->tiles.state[i][j];
+ if (!tile->passable) {
+ if (tile->texture)
+ { AtlasDraw(game, tile->breakable ? 1 : 0, game->tiles.color, i, j); }
+ } else if (tile->pickup) {
+ AtlasDraw(game, RENDER_POWERUP_BOMB + tile->pickup - 1, GAME_WHITE | GAME_OPAQUE, i, j);
}
- #endif
-
- if (game->tiles.state[i][j]._ < 2) {
- DrawTextureRec(
- game->spritesheet,
- game->tiles.wall[game->tiles.state[i][j].texture],
- (Vector2) {i*game->config.spritesheet_scale, j*game->config.spritesheet_scale},
- COLOR_TO_RAYLIB(game->tiles.color));
- }
-
- /* This almost requires horrible no good melding. Almost. */
- if (game->tiles.state[i][j]._ & EXPLOSIVE) {
- DrawTextureRec(
- game->spritesheet,
- game->tiles.explosion[game->tiles.state[i][j].texture],
- (Vector2) {i*game->config.spritesheet_scale, j*game->config.spritesheet_scale},
- WHITE);
+ if (tile->explosive) {
+ AtlasDraw(game, RENDER_EXPLOSION_START + tile->texture, game->bombs.color[0], i, j);
}
}
}
@@ -59,11 +51,8 @@ void RenderTiles(game_t * game) {
void RenderPlayers(game_t * game) {
for (int i = 0; i < PLAYER_LIMIT; ++i) {
if (game->players.state[i].alive) {
- DrawTextureRec(
- game->spritesheet,
- game->players.player[game->players.state[i].direction],
- (Vector2) {game->players.x[i] * game->config.spritesheet_scale, game->players.y[i] * game->config.spritesheet_scale},
- COLOR_TO_RAYLIB(game->players.color[i]));
+ AtlasDraw(game, RENDER_PLAYER_RIGHT + game->players.state[i].direction,
+ game->players.color[i], game->players.x[i], game->players.y[i]);
}
}
}
@@ -72,11 +61,9 @@ void RenderBombs(game_t * game) {
for (int i = 0; i < PLAYER_LIMIT; ++i) {
for (int j = 0; j < PLAYER_LIMIT; ++j) {
if (game->bombs.timer[i][j]) {
- DrawTextureRec(
- game->spritesheet,
- game->bombs.bomb[game->bombs.timer[i][j] % 4],
- (Vector2) {game->bombs.x[i][j]*game->config.spritesheet_scale, game->bombs.y[i][j]*game->config.spritesheet_scale},
- COLOR_TO_RAYLIB(game->bombs.color[game->bombs.timer[i][j]%2]));
+ AtlasDraw(game, RENDER_BOMB_0 + game->bombs.timer[i][j] % 4,
+ game->bombs.color[game->bombs.timer[i][j]%2],
+ game->bombs.x[i][j], game->bombs.y[i][j]);
}
}
}
diff --git a/source/update.c b/source/update.c
index 610252b..766073e 100644
--- a/source/update.c
+++ b/source/update.c
@@ -6,8 +6,11 @@ static void PlaceBomb(game_t * game);
static int CheckInputDebug(game_t * game);
static void CheckInputPlaceBomb(game_t * game);
static void CheckInputMovement(game_t * game);
-static void UpdatePlayer(game_t * game);
+static void UpdateClientPlayer(game_t * game);
+static void UpdatePlayers(game_t * game);
static void UpdateBomb(game_t * game);
+static void UpdateTimeLimit(game_t * game);
+static void CheckWin(game_t * game);
i16 Update(game_t * game, timespec_t now) {
(void) now;
@@ -16,11 +19,14 @@ i16 Update(game_t * game, timespec_t now) {
if (CheckInputDebug(game)) { return 1; }
CheckInputMovement(game);
CheckInputPlaceBomb(game);
- UpdatePlayer(game);
+ UpdateClientPlayer(game);
+ UpdatePlayers(game);
UpdateBomb(game);
CheckKilled(game);
UpdateExplosions(game);
- return 0;
+ CheckWin(game);
+ UpdateTimeLimit(game);
+return 0;
}
static void CheckKilled(game_t * game) {
@@ -31,28 +37,40 @@ static void CheckKilled(game_t * game) {
}
}
-
static void UpdateExplosions(game_t * game) {
size_t i, j;
for (i = 0; i < game->config.map_x; ++i) {
for (j = 0; j < game->config.map_y; ++j) {
- if (game->tiles.state[i][j]._ >= PASSIBLE_EXPLOSIVE_LETHAL
- && game->tiles.state[i][j]._ <= PASSIBLE_EXPLOSIVE_LETHAL_END) {
- if (game->tiles.state[i][j]._ == PASSIBLE_EXPLOSIVE_LETHAL_END)
- { game->tiles.state[i][j]._ = PASSIBLE_NOTHING; }
+ #define EXPLOSIVE_START 0
+ #define EXPLOSIVE_END 1
+ tile_data_t * tile = &game->tiles.state[i][j];
+ if (tile->explosive
+ && tile->texture >= EXPLOSIVE_START
+ && tile->texture <= EXPLOSIVE_END) {
+ if (tile->texture == EXPLOSIVE_END)
+ {
+ tile->texture = 0;
+ tile->explosive = 0;
+ tile->passable = 1;
+ tile->lethal = 0;
+ if (!tile->breakable) {
+ tile->pickup = 0;
+ }
+ tile->breakable = 0;
+ }
else
- { ++game->tiles.state[i][j]._; }
+ { ++tile->texture; }
}
}
}
}
static void PlaceBomb(game_t * game) {
- auto state = &game->players.state[game->client];
+ player_data_t * state = &game->players.state[game->client];
if (state->bomb_count < state->bomb_limit) {
game->tiles.state
[game->players.x[game->client]]
- [game->players.y[game->client]]._ = IMPASSIBLE_NOTHING;
+ [game->players.y[game->client]] = impassable_tile;
game->bombs.x[game->client][state->bomb_count] = game->players.x[game->client];
game->bombs.y[game->client][state->bomb_count] = game->players.y[game->client];
game->bombs.state[game->client][state->bomb_count].power = state->power;
@@ -67,7 +85,7 @@ static int CheckInputDebug(game_t * game) {
switch (GetKeyPressed()) {
case KEY_ESCAPE: return 1;
#ifndef NDEBUG
- case KEY_F1: GameReinitialize(game); break;
+ case KEY_F1: GameDeinitialize(game); GameInitialize(game); break;
case KEY_R: MultiPlayer(game); break;
case KEY_T: if (game->client < 3) game->client++; break;
case KEY_G: if (game->client != 0) game->client--; break;
@@ -77,7 +95,7 @@ static int CheckInputDebug(game_t * game) {
}
static void CheckInputPlaceBomb(game_t * game) {
- auto state = &game->players.state[game->client];
+ player_data_t * state = &game->players.state[game->client];
if ((IsKeyPressed(KEY_FIVE) || IsKeyPressed(KEY_SPACE) || IsKeyPressed(KEY_U) || IsKeyPressed(KEY_O)
|| IsKeyPressed(KEY_M) || IsKeyPressed(KEY_PERIOD) || IsKeyPressed(KEY_Z) || IsKeyPressed(KEY_C)
|| IsKeyPressed(KEY_Q) || IsKeyPressed(KEY_E) || IsKeyPressed(KEY_ENTER))
@@ -87,7 +105,7 @@ static void CheckInputPlaceBomb(game_t * game) {
static void CheckInputMovement(game_t * game) {
- auto state = &game->players.state[game->client];
+ player_data_t * state = &game->players.state[game->client];
state->moving = 0;
if (IsKeyPressed(KEY_UP) || IsKeyPressed(KEY_EIGHT) || IsKeyPressed(KEY_I) || IsKeyPressed(KEY_W))
@@ -112,63 +130,97 @@ static void CheckInputMovement(game_t * game) {
/* { state->moving = 0; } */
}
-static void UpdatePlayer(game_t * game) {
- auto state = &game->players.state[game->client];
+
+static void UpdatePlayers(game_t * game) {
+ for (int i = 0; i < game->config.player_count; ++i) {
+ player_data_t * state = &game->players.state[i];
+ i16 * player_x = &game->players.x[i];
+ i16 * player_y = &game->players.y[i];
+ if (game->tiles.state[*player_x][*player_y].pickup) {
+ switch (game->tiles.state[*player_x][*player_y].pickup) {
+ case POWERUP_BOMB: if (state->bomb_limit < 3) { ++state->bomb_limit; } break;
+ case POWERUP_POWER: if (state->power < 15) { ++state->power; } break;
+ case POWERUP_PIERCE: state->pierce = 1; break;
+ }
+ game->tiles.state[*player_x][*player_y].pickup = 0;
+ }
+ }
+}
+
+static void UpdateClientPlayer(game_t * game) {
+ player_data_t * state = &game->players.state[game->client];
i16 * player_x = &game->players.x[game->client];
i16 * player_y = &game->players.y[game->client];
- /* f32 * animation_x = &game->players.animation_x[game->client]; */
- /* f32 * animation_y = &game->players.animation_y[game->client]; */
float direction_x = -(state->direction == LEFT) + (state->direction == RIGHT);
float direction_y = -(state->direction == UP) + (state->direction == DOWN);
i16 delta_x = direction_x * state->moving;
i16 delta_y = direction_y * state->moving;
- if (*player_x + delta_x >= 0
- && *player_x + delta_x < game->config.map_x
- && game->tiles.state[*player_x + delta_x][*player_y]._ & PASSIBLE)
+ if (delta_x
+ && *player_x + delta_x >= 0
+ && *player_x + delta_x < game->config.map_x
+ && game->tiles.state[*player_x + delta_x][*player_y].passable)
{ *player_x += delta_x; }
+
if (*player_y + delta_y >= 0
- && *player_y + delta_y < game->config.map_y
- && game->tiles.state[*player_x][*player_y + delta_y]._ & PASSIBLE)
+ && *player_y + delta_y < game->config.map_y
+ && game->tiles.state[*player_x][*player_y + delta_y].passable)
{ *player_y += delta_y; }
}
static void UpdateBomb(game_t * game) {
- size_t i, j, k;
ssize_t
offset_x[4] = {-1, 1, 0, 0},
offset_y[4] = { 0, 0, -1, 1};
- for (i = 0; i < PLAYER_LIMIT; ++i) {
- for (j = 0; j < BOMB_LIMIT; ++j) {
+ for (int i = 0; i < PLAYER_LIMIT; ++i) {
+ for (int j = 0; j < BOMB_LIMIT; ++j) {
if (game->bombs.timer[i][j]) {
--game->bombs.timer[i][j];
if (!game->bombs.timer[i][j]) {
- ssize_t block[4] = {0};
- for (k = 0; k < 4 * game->players.state[i].power; ++k) {
- if (block[k%4]) { continue; }
+ ssize_t block[4] = {0};
+ i16 x = game->bombs.x[i][j], y = game->bombs.y[i][j];
+ for (int k = 0; k < 4 * game->players.state[i].power; ++k) {
+ if (block[k%4]) { continue; }
i16
rx = game->bombs.x[i][j] + offset_x[k%4] * ((k / 4) + 1),
ry = game->bombs.y[i][j] + offset_y[k%4] * ((k / 4) + 1);
if (rx < game->config.map_x && rx >= 0
- && ry < game->config.map_y && ry >= 0)
- {
- if (game->tiles.state[rx][ry]._ & PASSIBLE) {
- game->tiles.state[rx][ry]._ = PASSIBLE_EXPLOSIVE_LETHAL;
- } else if (game->tiles.state[rx][ry]._ == IMPASSIBLE_BREAKABLE_WALL) {
- game->tiles.state[rx][ry]._ = PASSIBLE_EXPLOSIVE_LETHAL;
- if (!game->players.state[i].pierce) {
- block[k%4] = 1;
- }
- } else {
- block[k%4] = 1;
- }
- }
+ && ry < game->config.map_y && ry >= 0) {
+ printf("rx %d ry %d\n", rx, ry);
+ tile_data_t * tile = &game->tiles.state[rx][ry];
+ if (tile->passable) {
+ tile->explosive = 1;
+ tile->lethal = 1;
+ } else if (tile->breakable) {
+ tile->explosive = 1;
+ tile->lethal = 1;
+ if (!game->players.state[i].pierce) {
+ block[k%4] = 1;
+ }
+ } else {
+ block[k%4] = 1;
+ }
+ }
}
- game->tiles.state[game->bombs.x[i][j]][game->bombs.y[i][j]]._ = PASSIBLE_EXPLOSIVE_LETHAL;
+ game->tiles.state[x][y].explosive = 1;
+ game->tiles.state[x][y].lethal = 1;
--game->players.state[i].bomb_count;
}
}
}
}
}
+
+static void CheckWin(game_t * game) {
+ int sum = 0;
+ for (int i = 0; i < game->config.player_count; ++i) {
+ sum += game->players.state[i].alive;
+ }
+ if (sum <= 1) { MultiPlayer(game);}
+}
+
+static void UpdateTimeLimit(game_t * game) {
+ --game->time_limit;
+ if (!game->time_limit) { MultiPlayer(game); }
+}