aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xMakefile2
-rw-r--r--README.md49
-rw-r--r--assets/.gitignore3
-rw-r--r--assets/simple.def1
-rw-r--r--assets/simple.pngbin0 -> 148566 bytes
-rw-r--r--source/all.h219
-rw-r--r--source/chad.h1
-rw-r--r--source/game.c213
-rw-r--r--source/gamemode.c115
-rw-r--r--source/main.c5
-rw-r--r--source/raylib.c40
-rw-r--r--source/render.c76
-rw-r--r--source/update.c164
13 files changed, 768 insertions, 120 deletions
diff --git a/Makefile b/Makefile
index 168eeec..e3419fd 100755
--- a/Makefile
+++ b/Makefile
@@ -61,7 +61,7 @@ ifeq (${VECTORIZED},1)
ifeq (${CC},clang)
CFLAGS += -Rpass=loop-vectorize
else
- CFLAGS += -fopt-info-vec-all
+ CFLAGS += -fopt-info-vec-optimized
endif
endif
diff --git a/README.md b/README.md
index 7a42168..e258042 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,10 @@
-MonoBomberMan --- IP-based Multiplayer Bomber Man
+MonoBomberMan
=================================================
+# IP-based Multiplayer Bomber Man
+
+Setup is a trivial as compilation.
+
## Game Internals
We want the traditional experience with simple power-ups and benign enemies.
@@ -10,13 +14,16 @@ Design-wise we'll keep things centralized and assume that this is a unified high
We'll start with the fundamental corpus:
+ #define EXPECTED_UPS 30 // shouldn't matter at low values.
+ #define EXPECTED_FPS 60 // or higher
+
typedef struct {
tiles_t tiles; // tiles classifying texturing, accessibility, and lethality.
// Any communication between entities is done here.
players_t players; // Controlled-by-humans.
bombs_t bombs; // timed explosives.
enemies_t enemies; // Simple walk-back-and-forth enemies.
-
+
// Windowing / Game Loop
Font font; // the global font
u16 horizontal, vertical; // highly mutable width & height of the current window
@@ -35,26 +42,26 @@ The naming here is to imply that these substructures clearly imply that they cov
POWERUP_BOMB,
POWERUP_POWER,
POWERUP_SPEED,
- // These will probably never be negative:
+ // These will probably never be negative:
POWERUP_PIERCE,
POWERUP_KICK,
POWERUP_THROW,
POWERUP_BOUNCE,
- // Curse is something I've slightly read up on, but it seems to be timer based.
- // POWERUP_CURSE, // no plan to implement.
- }
+ // Curse is something I've slightly read up on, but it seems to be timer based.
+ // POWERUP_CURSE, // no plan to implement.
+ };
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 explosive : 1; // explosion animations, coopts texture for explosion frames.
u8 passable : 1;
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.
+ // 5 bits left for extensions.
};
} state[TILE_LIMIT];
u8 color[TILE_LIMIT];
@@ -65,7 +72,8 @@ We will track fundamental interactions here. Powerups are grouped in per their s
#define PLAYER_LIMIT (1<<2)
typedef struct {
- f32 x[PLAYER_LIMIT], y[PLAYER_LIMIT]; // for smooth movement, rounding is used for tile checks.
+ // for smooth movement, rounding is used for tile checks.
+ f32 x[PLAYER_LIMIT], y[PLAYER_LIMIT];
union {
u32 _;
struct {
@@ -100,8 +108,8 @@ The framerate being a higher value should result in a visually consistent style.
// 10 bits left for extensions.
};
} state[BOMB_LIMIT];
- u16 timer[BOMB_LIMIT]; // updates until explosion.
- u8 color[BOMB_LIMIT], color_flash[BOMB_LIMIT];
+ u16 timer[BOMB_LIMIT]; // updates until explosion.
+ u8 color[BOMB_LIMIT], color_flash[BOMB_LIMIT];
} bombs_t;
Bombs are pretty simple. After they explode they lag behind.
@@ -112,9 +120,9 @@ We can assume frames are interpolated and UPS is low.
enum {
MOVEMENT_VERTICAL,
- MOVEMENT_HORIZONTAL,
- MOVEMENT_RANDOM,
- }
+ MOVEMENT_HORIZONTAL,
+ MOVEMENT_RANDOM,
+ };
typedef struct {
f32 x[ENEMY_LIMIT], y[ENEMY_LIMIT];
@@ -124,7 +132,18 @@ We can assume frames are interpolated and UPS is low.
Enemies are primitive. These set the tile they are on as lethal.
We can assume they have the same base movement speed.
-## Networking
+#### Loading
+
+We want to load a bunch of information at the start and it's trivial with or without Raylib.
+
+The important directories/files are:
+assets/(spritesheet).png
+fonts/(fontset)/
+configuration.txt <- omitted for simplicity.
+
+We'll look for the assets for tiles, bombs, explosion frames, & players.
+
+#### Networking
## Copyright
[monobomberman](https://github.com/8e8m/monobomberman) by [Anon Anonison](https://github.com/agvxov) & [Emil Williams](https://github.com/8e8m) is marked [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/).
diff --git a/assets/.gitignore b/assets/.gitignore
new file mode 100644
index 0000000..1f297dd
--- /dev/null
+++ b/assets/.gitignore
@@ -0,0 +1,3 @@
+*
+!*.png
+!*.def
diff --git a/assets/simple.def b/assets/simple.def
new file mode 100644
index 0000000..528f30b
--- /dev/null
+++ b/assets/simple.def
@@ -0,0 +1 @@
+4:128x6:128
diff --git a/assets/simple.png b/assets/simple.png
new file mode 100644
index 0000000..a80e12f
--- /dev/null
+++ b/assets/simple.png
Binary files differ
diff --git a/source/all.h b/source/all.h
index 0c9bcec..93ea881 100644
--- a/source/all.h
+++ b/source/all.h
@@ -5,33 +5,228 @@
#include <stdint.h>
#include <math.h>
+#include <netinet/in.h>
+
#include <raylib.h>
#include <raygui.h>
#include <rlgl.h>
#include "chad.h"
+#define StepStart(prefix) \
+ prefix##_delta = timespec_sub(now, prefix##_last); \
+ if (timespec_cmp(prefix##_delta, prefix##_interval) >= 0) { \
+ (void) 0
+ /* ... */
+#define StepStop(prefix) \
+ prefix##s_per_second++; \
+ prefix##_total++; \
+ prefix##_last = timespec_add(prefix##_last, prefix##_interval); \
+ if (timespec_cmp(prefix##_last, now) < 0) { \
+ prefix##_last = now; \
+ } \
+ }
+
+/* this is precisely why namespacing is a good idea */
+#define GAME_RED (0x03<<0)
+#define GAME_GREEN (0x03<<2)
+#define GAME_BLUE (0x03<<4)
+#define GAME_OPAQUE (0x03<<6)
+#define GAME_WHITE (GAME_RED | GAME_GREEN | GAME_BLUE)
+#define COLOR_TO_RAYLIB(c) (Color) { \
+ 85*((c & GAME_RED)>>0), \
+ 85*((c & GAME_GREEN)>>2), \
+ 85*((c & GAME_BLUE)>>4), \
+ 85*((c & GAME_OPAQUE)>>6), }
+
+#define TEXTURE_LIMIT (3*8)
+
+/* Spritesheets will be (128*4x128*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 ] |
+ group 2 is [0-3][1 ] | Negatives use color mask
+ [0-2][2 ] /
+ 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
+
+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];
+ u8 color;
+ Rectangle wall[2] aligned;
+ Rectangle explosion[2] aligned;
+ Rectangle powerup[8] aligned;
+} tiles_t;
+
+#define PLAYER_LIMIT (1<<2)
+
+enum {
+ RIGHT, LEFT, UP, DOWN
+};
+
+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;
+ u8 color[PLAYER_LIMIT] aligned;
+ Rectangle player[4] aligned;
+} players_t;
+
+#define BOMB_LIMIT (1<<4)
+
typedef struct {
- Font font __attribute__((aligned));
- u16 horizontal, vertical __attribute__((aligned));
- u16 ups, fps __attribute__((aligned));
+ 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;
+ // 10 bits left for extensions.
+ };
+ } 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];
+} enemies_t;
+
+typedef struct {
+ u16 resolution_x, resolution_y;
+ u8 fps, ups;
+ char font[128];
+ char spritesheet[128];
+ u16 spritesheet_scale;
+ /* --- */
+ u8 map_x, map_y;
+} config_t;
+
+typedef struct {
+ tiles_t tiles aligned;
+ players_t players aligned;
+ bombs_t bombs aligned;
+ enemies_t enemies aligned;
+ config_t config aligned;
+
+ Font font aligned;
+
+ Texture spritesheet aligned;
+ Camera2D camera aligned;
+
+ u8 client;
} game_t;
/* game.c */
-void GameInitialize(game_t * game, char * window_name);
-void GameDeinitialize(game_t * game);
-void GameFrame(game_t * game, size_t frame, f32 x, f32 y);
-Vector2 GameFrameVector(game_t * game, size_t frame);
-void GameLoop(game_t * game);
-i16 GameUpdate(game_t * game, timespec_t now);
-void GameRender(game_t * game, f64 interpolation);
-void GameReport(game_t * game, f32 fps, f32 ups, u32 total_fps, u32 total_ups) ;
+void GameStart(char * program_name);
+void GameResize(game_t * game);
+
+/* gamemode.c */
+
+void MultiPlayer(game_t * game, u16 width, u16 height, u8 player_count);
+void SinglePlayer(game_t * game, u16 width, u16 height);
+
+/* update.c */
+
+i16 Update(game_t * game, timespec_t now);
+
+/* render.c */
+
+void Render(game_t * game, f64 interpolation);
/* raylib.c */
-Font DefaultFont(char * choice);
void GuiLoadStyleDarkSimple(void);
+Font DefaultFont(char * choice);
+void RaylibInitialize(int horizontal, int vertical, char * window_name, Font default_font);
+void RaylibDeinitialize(void);
/* ... */
diff --git a/source/chad.h b/source/chad.h
index ceaec36..01eed68 100644
--- a/source/chad.h
+++ b/source/chad.h
@@ -13,6 +13,7 @@
#include <stdint.h>
#define always_inline static inline __attribute__((always_inline))
+#define aligned __attribute__((aligned))
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)>(b)?(a):(b))
diff --git a/source/game.c b/source/game.c
index 2ade12a..ea02cbb 100644
--- a/source/game.c
+++ b/source/game.c
@@ -1,49 +1,131 @@
#include "all.h"
-void GameInitialize(game_t * game, char * window_name) {
- SetConfigFlags(FLAG_WINDOW_RESIZABLE);
- SetTraceLogLevel(LOG_NONE);
- /* :config */
- game->horizontal = 1920;
- game->vertical = 1080;
- InitWindow(game->horizontal, game->vertical, window_name);
- game->ups = 60;
- game->fps = 30;
- game->font = DefaultFont("fonts/Atkinson/mono/AtkinsonHyperlegibleMono-Bold.otf");
- /* :setup */
- SetWindowState(FLAG_WINDOW_HIDDEN);
- InitAudioDevice();
- SetWindowPosition(0, 0);
- GuiLoadStyleDarkSimple();
- GuiSetFont(game->font);
- /* --- */
+static void GameInitialize(game_t * game, char * window_name);
+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 GameStart(char * program_name) {
+ _Alignas(64) game_t game[1] = {0};
+ GameInitialize(game, program_name);
+ GameLoop(game);
+ GameDeinitialize(game);
}
-void GameDeinitialize(game_t * game) {
- SetWindowState(FLAG_WINDOW_HIDDEN);
- UnloadFont(game->font);
- CloseAudioDevice();
- CloseWindow();
+static void GameRecalculateViewport(game_t * game) {
+ game->config.resolution_x = GetScreenWidth();
+ game->config.resolution_y = GetScreenHeight();
+ game->camera = (Camera2D) {
+ .offset = (Vector2) { 0 },
+ .target = (Vector2) { 0 },
+ .rotation = 0.,
+ .zoom = fminf(
+ (float) game->config.resolution_x /
+ (game->config.map_x * game->config.spritesheet_scale),
+ (float) game->config.resolution_y /
+ (game->config.map_y * game->config.spritesheet_scale)),
+ };
}
-void GameLoop(game_t * game) {
-
- #define StepStart(prefix) \
- prefix##_delta = timespec_sub(now, prefix##_last); \
- if (timespec_cmp(prefix##_delta, prefix##_interval) >= 0) { (void) 0
+void GameResize(game_t * game) {
+ if (IsWindowResized()) {
+ GameRecalculateViewport(game);
+ }
+}
- #define StepStop(prefix) \
- prefix##s_per_second++; \
- prefix##_total++; \
- prefix##_last = timespec_add(prefix##_last, prefix##_interval); \
- if (timespec_cmp(prefix##_last, now) < 0) { \
- prefix##_last = now; \
- } \
+static void GameInitialize(game_t * game, char * window_name) {
+
+ game->config = (config_t) {
+ .resolution_x = 600,
+ .resolution_y = 600,
+ .fps = 60,
+ .ups = 30,
+ .font = "fonts/Atkinson/mono/AtkinsonHyperlegibleMono-Bold.otf",
+ .spritesheet = "assets/simple.png",
+ .spritesheet_scale = 128,
+ .map_x = 13,
+ .map_y = 13,
+ };
+ {
+ 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){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 - 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 - 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 - 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));
}
+ MultiPlayer(game, game->config.map_x, game->config.map_y, 4);
+
+ 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->bombs.color[0] = GAME_WHITE | GAME_OPAQUE;
+ game->bombs.color[1] = GAME_RED | GAME_OPAQUE;
+
+ /* :config */
+ game->font = DefaultFont(game->config.font);
+
+ /* this is retarded (intentionally) */
+ RaylibInitialize(game->config.resolution_x-1, game->config.resolution_y-1, window_name, game->font);
+ SetWindowSize(game->config.resolution_x, game->config.resolution_y);
+ GameRecalculateViewport(game);
+
+ game->spritesheet = LoadTexture(game->config.spritesheet);
+ if (game->spritesheet.id <= 0) { abort(); }
+
+ ClearWindowState(FLAG_WINDOW_HIDDEN);
+}
+
+static void GameDeinitialize(game_t * game) {
+ UnloadTexture(game->spritesheet);
+ if (GetFontDefault().texture.id != game->font.texture.id) { UnloadFont(game->font); }
+ RaylibDeinitialize();
+}
+
+static void GameLoop(game_t * game) {
+
#define StepSimpleStart(prefix,linear) \
prefix##_delta = timespec_sub(now, prefix##_last); \
- if (timespec_cmp(prefix##_delta, (timespec_t){1.,0.}) >= 0) { \
+ if (timespec_cmp(prefix##_delta, linear) >= 0) { \
(void)0
#define StepSimpleStop(prefix) \
@@ -52,8 +134,8 @@ void GameLoop(game_t * game) {
timespec_t
now,
- update_interval = {0, (f64) TIMESPEC_HZ / game->ups},
- frame_interval = {0, (f64) TIMESPEC_HZ / game->fps},
+ update_interval = {0, (f64) TIMESPEC_HZ / game->config.ups},
+ frame_interval = {0, (f64) TIMESPEC_HZ / game->config.fps},
update_last, frame_last, print_last,
update_delta, frame_delta, print_delta,
wait;
@@ -66,32 +148,30 @@ void GameLoop(game_t * game) {
f64 interpolation = 0.;
- ClearWindowState(FLAG_WINDOW_HIDDEN);
-
clock_gettime(CLOCK_MONOTONIC, &now);
update_last = frame_last = print_last = now;
while (1) {
StepStart(update);
- if (GameUpdate(game, now)) { return; }
+ if (Update(game, now)) { return; }
StepStop(update);
StepStart(frame);
clock_gettime(CLOCK_MONOTONIC, &now);
interpolation =
CLAMP(
- TIMESPEC_TO_F64(update_delta)
- / TIMESPEC_TO_F64(update_interval),
- 0.0, 1.0f);
- GameRender(game, interpolation);
+ TIMESPEC_TO_F64(update_delta)
+ / TIMESPEC_TO_F64(update_interval),
+ 0.0, 1.0f);
+ Render(game, interpolation);
StepStop(frame);
clock_gettime(CLOCK_MONOTONIC, &now);
wait = timespec_sub(
timespec_min(
- timespec_add(update_last, update_interval),
- timespec_add(frame_last, frame_interval)),
+ timespec_add(update_last, update_interval),
+ timespec_add(frame_last, frame_interval)),
now);
if (timespec_cmp(wait, zero_seconds) > 0) {
@@ -100,10 +180,10 @@ void GameLoop(game_t * game) {
StepSimpleStart(print, one_second);
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;
StepSimpleStop(print);
@@ -111,33 +191,10 @@ void GameLoop(game_t * game) {
}
}
-i16 GameUpdate(game_t * game, timespec_t now) {
- (void) now;
-
- PollInputEvents();
- if (IsWindowResized()) {
- game->horizontal = GetScreenWidth();
- game->vertical = GetScreenHeight();
- }
- switch (GetKeyPressed()) {
- case KEY_ESCAPE: return 1;
- }
- return 0;
-}
-
-void GameRender(game_t * game, f64 interpolation) {
- (void)interpolation;
-
- BeginDrawing();
- ClearBackground(BLACK);
- /* >>> */
-
- /* --- */
- rlDrawRenderBatchActive();
- SwapScreenBuffer();
-}
-
-void GameReport(game_t * game, f32 fps, f32 ups, u32 total_fps, u32 total_ups) {
+static void GameReport(game_t * game, f32 fps, f32 ups, u32 total_fps, u32 total_ups) {
+ (void)game;
+#ifndef NDEBUG
printf("[FPS|UPS|Total] (%3.0f : %3.0f) | [%7u/%7u]\n",
- fps, ups, total_fps, total_ups);
+ fps, ups, total_fps, total_ups);
+#endif
}
diff --git a/source/gamemode.c b/source/gamemode.c
new file mode 100644
index 0000000..cf5d06a
--- /dev/null
+++ b/source/gamemode.c
@@ -0,0 +1,115 @@
+#include "all.h"
+
+void MultiPlayer(game_t * game, u16 width, u16 height, u8 player_count) {
+ int i, j;
+
+ for (i = 0; i < width; ++i) {
+ for (j = 0; j < height; ++j) {
+ game->tiles.state[i][j]._ = rand() % 10 ? IMPASSIBLE_BREAKABLE_WALL : PASSIBLE_NOTHING;
+ }
+ }
+
+ for (i = 1; i < width; i += 2) {
+ for (j = 1; j < height; j += 2) {
+ game->tiles.state[i][j]._ = IMPASSIBLE_WALL;
+ }
+ }
+
+ bzero(game->players.state, sizeof(*game->players.state) * PLAYER_LIMIT);
+
+ for (i = 0; i < MIN(PLAYER_LIMIT, player_count); ++i) {
+ game->players.state[i].bomb_limit = 1;
+ game->players.state[i].power = 2;
+ game->players.state[i].speed = 2;
+ game->players.state[i].alive = 1;
+ game->players.state[i].direction = DOWN;
+ }
+
+ 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,
+ GAME_GREEN | GAME_OPAQUE,
+ GAME_BLUE | GAME_OPAQUE,
+ };
+
+ for (i = 0; i < MIN(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((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");
+ }
+}
+
+/* missing proper player / bomb / enemy initialization */
+void SinglePlayer(game_t * game, u16 width, u16 height) {
+ u16 i, j;
+
+ for (i = 0; i < width; ++i) {
+ for (j = 0; j < height; ++j) {
+ game->tiles.state[i][j]._ = rand() % 10 ? IMPASSIBLE_BREAKABLE_WALL : PASSIBLE_NOTHING;
+ }
+ }
+
+ u16 x, y;
+ int distance, direction;
+ for (i = 0; i < ENEMY_LIMIT; ++i) {
+ distance = rand() % MIN(width, height);
+ direction = rand() % MOVEMENT_LAST;
+ x = rand() % width;
+ y = rand() % height;
+ game->enemies.x[i] = x;
+ game->enemies.y[i] = y;
+ game->enemies.movement[i] = direction;
+ game->tiles.state[x][y]._ = PASSIBLE_NOTHING;
+ for (j = -(distance/2); j+(distance/2) < distance; ++j) {
+ game->tiles.state
+ [x + (j * (direction == MOVEMENT_HORIZONTAL) * (x + j < width ))]
+ [y + (j * (direction == MOVEMENT_VERTICAL ) * (y + j < height))]._ = PASSIBLE_NOTHING;
+ }
+ }
+
+ for (i = 1; i < width; i += 2) {
+ for (j = 1; j < height; j += 2) {
+ game->tiles.state[i][j]._ = IMPASSIBLE_WALL;
+ }
+ }
+
+ game->tiles.state[0][0]._ = PASSIBLE_NOTHING;
+ game->tiles.state[1][0]._ = PASSIBLE_NOTHING;
+ game->tiles.state[0][1]._ = PASSIBLE_NOTHING;
+
+ game->players.x[0] = 0;
+ game->players.y[0] = 0;
+ game->players.state[0].bomb_limit = 1;
+ game->players.state[0].power = 2;
+ game->players.state[0].speed = 3;
+ game->players.state[0].alive = 1;
+
+ for (i = 0; i < width; ++i) {
+ for (j = 0; j < height; ++j) {
+ printf("%3d ", game->tiles.state[i][j]._);
+ }
+ printf("\n");
+ }
+}
diff --git a/source/main.c b/source/main.c
index d00f7e7..370e463 100644
--- a/source/main.c
+++ b/source/main.c
@@ -3,13 +3,10 @@
int Main(int count, char ** arguments)
{
(void)count;
- _Alignas(64) game_t game[1] = {0};
char * program_name = arguments[0];
srand(time(NULL));
Root(program_name);
- GameInitialize(game, program_name);
- GameLoop(game);
- GameDeinitialize(game);
+ GameStart(program_name);
return 0;
}
diff --git a/source/raylib.c b/source/raylib.c
index 935feee..d96aa4a 100644
--- a/source/raylib.c
+++ b/source/raylib.c
@@ -1,13 +1,3 @@
-/* raylib.c & raygui.c */
-
-#include <raylib.h>
-
-Font DefaultFont(char * choice) {
- Font font = LoadFont(choice);
- if (!IsFontValid(font)) { font = GetFontDefault(); }
- return font;
-}
-
/* raygui.c */
#pragma GCC diagnostic push
@@ -31,3 +21,33 @@ void GuiLoadStyleDarkSimple(void) {
GuiSetStyle(darkStyleProps[i].controlId, darkStyleProps[i].propertyId, darkStyleProps[i].propertyValue);
}
}
+
+/* raylib.c */
+
+#include <raylib.h>
+
+Font DefaultFont(char * choice) {
+ Font font = LoadFont(choice);
+ if (!IsFontValid(font)) { font = GetFontDefault(); }
+ return font;
+}
+
+void RaylibInitialize(int horizontal, int vertical, char * window_name, Font default_font) {
+#ifdef NDEBUG
+ SetTraceLogLevel(LOG_NONE);
+#endif
+ /* SetConfigFlags(FLAG_WINDOW_RESIZABLE); */
+ InitWindow(horizontal, vertical, window_name);
+ SetWindowState(FLAG_WINDOW_HIDDEN);
+ /* we should spawn this in the center of the screen and have our window scale to the limit of the screen */
+ InitAudioDevice();
+ SetWindowPosition(0, 0);
+ GuiLoadStyleDarkSimple();
+ GuiSetFont(default_font);
+}
+
+void RaylibDeinitialize(void) {
+ SetWindowState(FLAG_WINDOW_HIDDEN);
+ CloseAudioDevice();
+ CloseWindow();
+}
diff --git a/source/render.c b/source/render.c
new file mode 100644
index 0000000..5af04f8
--- /dev/null
+++ b/source/render.c
@@ -0,0 +1,76 @@
+#include "all.h"
+
+static void RenderTiles(game_t * game);
+static void RenderPlayers(game_t * game);
+static void RenderBombs(game_t * game);
+
+void Render(game_t * game, f64 interpolation) {
+ (void)game;
+ (void)interpolation;
+
+ BeginDrawing();
+ ClearBackground(BLACK);
+ BeginMode2D(game->camera);
+ RenderTiles(game);
+ RenderBombs(game);
+ RenderPlayers(game);
+ EndMode2D();
+ /* --- */
+ rlDrawRenderBatchActive();
+ SwapScreenBuffer();
+}
+
+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) {
+ 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));
+ }
+
+ 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);
+ }
+
+
+ }
+ }
+}
+
+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]));
+ }
+ }
+}
+
+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]));
+ }
+ }
+ }
+}
diff --git a/source/update.c b/source/update.c
new file mode 100644
index 0000000..9cec977
--- /dev/null
+++ b/source/update.c
@@ -0,0 +1,164 @@
+#include "all.h"
+
+static void CheckKilled(game_t * game);
+static void UpdateExplosions(game_t * game);
+static void PlaceExplosive(game_t * game);
+static int CheckInputDebug(game_t * game);
+static void CheckInputPlaceExplosive(game_t * game);
+static void CheckInputMovement(game_t * game);
+static void UpdatePlayer(game_t * game);
+static void UpdateBomb(game_t * game);
+
+i16 Update(game_t * game, timespec_t now) {
+ (void) now;
+ PollInputEvents();
+ GameResize(game);
+ if (CheckInputDebug(game)) { return 1; }
+ CheckInputMovement(game);
+ CheckInputPlaceExplosive(game);
+ UpdatePlayer(game);
+ UpdateBomb(game);
+ CheckKilled(game);
+ UpdateExplosions(game);
+ return 0;
+}
+
+static void CheckKilled(game_t * game) {
+ for (size_t i = 0; i < PLAYER_LIMIT; ++i) {
+ if (game->tiles.state[game->players.x[i]][game->players.y[i]].lethal) {
+ game->players.state[i].alive = 0;
+ }
+ }
+}
+
+
+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) {
+ if (game->tiles.state[i][j]._ == PASSIBLE_EXPLOSIVE_LETHAL_END)
+ { game->tiles.state[i][j]._ = PASSIBLE_NOTHING; }
+ else
+ { ++game->tiles.state[i][j]._; }
+ }
+ }
+ }
+}
+
+static void PlaceExplosive(game_t * game) {
+ auto 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->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;
+ game->bombs.state[game->client][state->bomb_count].pierce = state->pierce;
+ game->bombs.state[game->client][state->bomb_count].bounce = state->bounce;
+ game->bombs.timer[game->client][state->bomb_count] = game->config.ups * 2;
+ ++state->bomb_count;
+ }
+}
+
+static int CheckInputDebug(game_t * game) {
+ switch (GetKeyPressed()) {
+ case KEY_ESCAPE: return 1;
+#ifndef NDEBUG
+ case KEY_R: MultiPlayer(game, game->config.map_x, game->config.map_y, 4); break;
+ case KEY_T: if (game->client < 3) game->client++; break;
+ case KEY_G: if (game->client != 0) game->client--; break;
+#endif
+ }
+ return 0;
+}
+
+static void CheckInputPlaceExplosive(game_t * game) {
+ auto 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))
+ && state->alive)
+ { PlaceExplosive(game); }
+}
+
+
+static void CheckInputMovement(game_t * game) {
+ auto state = &game->players.state[game->client];
+
+ state->moving = 0;
+ if (IsKeyPressed(KEY_UP) || IsKeyPressed(KEY_EIGHT) || IsKeyPressed(KEY_I) || IsKeyPressed(KEY_W))
+ { state->moving = 1; state->direction = UP; }
+ if (IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_FOUR) || IsKeyPressed(KEY_J) || IsKeyPressed(KEY_A))
+ { state->moving = 1; state->direction = LEFT; }
+ if (IsKeyPressed(KEY_DOWN) || IsKeyPressed(KEY_TWO) || IsKeyPressed(KEY_K) || IsKeyPressed(KEY_S))
+ { state->moving = 1; state->direction = DOWN; }
+ if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_SIX) || IsKeyPressed(KEY_L) || IsKeyPressed(KEY_D))
+ { state->moving = 1; state->direction = RIGHT; }
+
+ /* if (IsKeyDown(KEY_EIGHT) || IsKeyDown(KEY_I) || IsKeyDown(KEY_W) */
+ /* || IsKeyDown(KEY_FOUR) || IsKeyDown(KEY_J) || IsKeyDown(KEY_A) */
+ /* || IsKeyDown(KEY_TWO) || IsKeyDown(KEY_K) || IsKeyDown(KEY_S) */
+ /* || IsKeyDown(KEY_SIX) || IsKeyDown(KEY_L) || IsKeyDown(KEY_D)) */
+ /* { state->moving = 1; } */
+
+ /* if (IsKeyReleased(KEY_EIGHT) || IsKeyReleased(KEY_I) || IsKeyReleased(KEY_W) */
+ /* || IsKeyReleased(KEY_FOUR) || IsKeyReleased(KEY_J) || IsKeyReleased(KEY_A) */
+ /* || IsKeyReleased(KEY_TWO) || IsKeyReleased(KEY_K) || IsKeyReleased(KEY_S) */
+ /* || IsKeyReleased(KEY_SIX) || IsKeyReleased(KEY_L) || IsKeyReleased(KEY_D)) */
+ /* { state->moving = 0; } */
+}
+
+static void UpdatePlayer(game_t * game) {
+ auto 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)
+ { *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; }
+}
+
+static void UpdateBomb(game_t * game) {
+ u16 * b = *game->bombs.timer;
+ ssize_t
+ offset_x[4] = {-1, 1, 0, 0},
+ offset_y[4] = { 0, 0, -1, 1};
+ size_t i, j;
+ for (i = 0; i < PLAYER_LIMIT * BOMB_LIMIT; ++i) {
+ if (b[i]) {
+ --b[i];
+ if (!b[i]) {
+ /* game->tiles.state[(*game->bombs.x)[i]][(*game->bombs.y)[i]]._ = PASSIBLE_NOTHING; */
+ /* explosion */
+ for (j = 0; j < 4; ++j) {
+ i16
+ rx = (*game->bombs.x)[i] + offset_x[j],
+ ry = (*game->bombs.y)[i] + offset_y[j];
+
+ if (game->tiles.state[rx][ry]._ & PASSIBLE || game->tiles.state[rx][ry]._ == IMPASSIBLE_BREAKABLE_WALL) {
+ if (rx < game->config.map_x && rx >= 0
+ && ry < game->config.map_y && ry >= 0)
+ { game->tiles.state[rx][ry]._ = PASSIBLE_EXPLOSIVE_LETHAL; }
+ }
+ }
+ game->tiles.state[*game->bombs.x[i]][*game->bombs.y[i]]._ = PASSIBLE_EXPLOSIVE_LETHAL;
+ /* --- */
+ --game->players.state[i>>2].bomb_count;
+ }
+ }
+ }
+}