cake
/
pixeltd
Archived
1
0
Fork 0
This repository has been archived on 2024-04-15. You can view files and clone it, but cannot push or open issues or pull requests.
pixeltd/src/game.c

314 lines
6.8 KiB
C

#include "main.h"
#include <gint/display.h>
#include <gint/keyboard.h>
#define NENEMIES 64
/* State definition, as used throughout the main game.
*
* For each enemy, the data is:
* - alive: if the enemy is alive or dead.
*
* - nstepf: number of frames for one step, defines the speed of the
* enemy.
*
* - x, y: calculated only for display and collision.
* - step, substep: where the AI is at. `step` is the index starting at zero
* of the path where is at, `substep` is the substep from 0 to 7, looping
* when 8 (augmenting step, except when at the castle).
* - nfwait: number of frames to be waited.
* - pdr: the enemy that spawned previously or is the next further on the
* path. Simple chain list that could evolve later if some enemies are
* slowed down or have various speeds, allowing some enemies to overtake
* others. */
struct enemy {
int alive;
int nstepf;
int x, y;
int step, substep;
int nfwait;
struct enemy **ndrp; /* pointer on the pointer to this enemy structure */
struct enemy *pdr; /* previous enemy to be drawn */
};
struct state {
/* Map information:
*
* - w, h: dimensions
* - sp_x, sp_y: enemy spawn coordinates
* - nep: number of enemy path */
int w, h;
int sp_x, sp_y;
int nep; /* number of enemies path. */
uint8_t *grid;
uint8_t *ep;
/* AI information:
*
* - tbsp: time between enemy spawns.
* - tnsp: time to next spawn (as a number of ticks).
* - nae: number of currently alive enemies.
* - first_enemy: pointer to the last enemy to have spawned,
* to draw first */
int tbsp, tnsp;
int nae;
struct enemy *first_enemy;
/* Camera, cursor and display information:
*
* - cam_x, cam_y: camera coordinates, always displays 16x8 tiles
* - cur_x, cur_y: map coordinate for the cursor.
* - drf: draw frame. */
int cam_x, cam_y;
int cur_x, cur_y;
int drf; /* draw frame, varies from 0 to 255 and looping */
/* Raw data */
struct enemy enemies[NENEMIES];
};
static struct state state;
/* init(raw): initialize the state with the given raw map data. */
static void init(uint8_t *raw)
{
int i;
/* Initialize map information */
state.w = raw[0];
state.h = raw[1];
state.sp_x = raw[4];
state.sp_y = raw[5];
state.nep = raw[6];
state.grid = &raw[(raw[16] << 8) | raw[17]];
state.ep = &raw[(raw[18] << 8) | raw[19]];
/* Initialize AI information */
state.tbsp = 50;
state.tnsp = state.tbsp;
state.nae = 0;
state.first_enemy = NULL;
for (i = 0; i < NENEMIES; i++)
state.enemies[i].alive = 0;
/* Initialize display information */
state.cam_x = raw[2];
state.cam_y = raw[3];
state.cur_x = 1;
state.cur_y = 1;
state.drf = 0;
}
/* tick(): tick the game engine. */
static void tick()
{
struct enemy *ep;
int i;
for (ep = state.first_enemy; ep; ep = ep->pdr) {
if (!--ep->nfwait) {
ep->nfwait = ep->nstepf;
switch (state.ep[ep->step]) {
case 0:
ep->y--;
break;
case 1:
ep->x++;
break;
case 2:
ep->y++;
break;
case 3:
ep->x--;
break;
}
ep->substep++;
if (ep->substep == 8) {
ep->step++;
ep->substep = 0;
if (ep->step == state.nep) {
ep->alive = 0;
if (ep->ndrp)
(*ep->ndrp) = ep->pdr;
state.nae--;
/* TODO: remove one life to the castle */
}
}
}
}
/* Spawn enemies if required */
if (!--state.tnsp) {
state.tnsp = state.tbsp;
if (state.nae < NENEMIES) {
for (i = 0, ep = &state.enemies[0]; i < NENEMIES; i++, ep++);
ep->pdr = state.first_enemy;
ep->pdr->ndrp = &ep->pdr;
state.first_enemy = ep;
ep->ndrp = &state.first_enemy;
ep->alive = 1;
ep->x = state.sp_x << 3;
ep->y = state.sp_y << 3;
ep->step = 0;
ep->substep = 0;
/* TODO: use a given type of enemy? */
ep->nstepf = 10;
ep->nfwait = ep->nstepf;
state.nae++;
}
}
}
/* tile_cam(x, y): return the type of the tile at coordinates (x, y) relative
* to the current camera position.
*
* If out of bounds, a basic placement tile (id. 0) will be returned */
static inline int tile_cam(int x, int y)
{
x += state.cam_x;
y += state.cam_y;
if (x < 0 || x >= state.w || y < 0 || y >= state.h)
return 0;
return state.grid[y * state.w + x];
}
/* draw(): basically the drawing function for everything. */
static void draw()
{
int x, y;
dclear(C_WHITE);
/* draw the grid */
for (y = 0; y < 8; y++) {
for (x = 0; x < 16; x++) {
int tile = tile_cam(x, y);
int ox = x * 8, oy = y * 8;
if (tile == 1 || tile == 2) {
/* above, below, left, right */
if (tile_cam(x, y - 1) == 0)
dline(ox, oy, ox + 7, oy, C_BLACK);
if (tile_cam(x, y + 1) == 0)
dline(ox, oy + 7, ox + 7, oy + 7, C_BLACK);
if (tile_cam(x - 1, y) == 0)
dline(ox, oy, ox, oy + 7, C_BLACK);
if (tile_cam(x + 1, y) == 0)
dline(ox + 7, oy, ox + 7, oy + 7, C_BLACK);
/* top left, top right, bottom left, bottom right */
if (tile_cam(x - 1, y - 1) == 0)
dpixel(ox, oy, C_BLACK);
if (tile_cam(x + 1, y - 1) == 0)
dpixel(ox + 7, oy, C_BLACK);
if (tile_cam(x - 1, y + 1) == 0)
dpixel(ox, oy + 7, C_BLACK);
if (tile_cam(x + 1, y + 1) == 0)
dpixel(ox + 7, oy + 7, C_BLACK);
}
}
}
/* Draw the enemies. */
{
struct enemy *ep;
extern bopti_image_t img_enemy;
for (ep = state.first_enemy; ep; ep = ep->pdr)
dimage(ep->x, ep->y, &img_enemy);
}
/* Draw the cursor */
{
int d = state.drf & 128 ? 2 : 0;
int ox = state.cur_x - state.cam_x + 1;
int oy = state.cur_y - state.cam_y + 1;
dline(ox + d, oy, ox + d + 1, oy, C_BLACK);
dline(ox + d + 4, oy, ox + d + 5, oy, C_BLACK);
dline(ox, oy + d, ox, oy + d + 1, C_BLACK);
dline(ox, oy + d + 4, ox, oy + d + 5, C_BLACK);
dline(ox + d, oy + 7, ox + d + 1, oy + 7, C_BLACK);
dline(ox + d + 4, oy + 7, ox + d + 5, oy + 7, C_BLACK);
dline(ox + 7, oy + d, ox + 7, oy + d + 1, C_BLACK);
dline(ox + 7, oy + d + 4, ox + 7, oy + d + 5, C_BLACK);
}
/* okay we can update now */
dprint(16, 96, C_BLACK, "state.tnsp = 0x%08X", state.tnsp);
dupdate();
}
/* Main game function. */
menu_t *game(menu_t *last_menu)
{
int timeout = 1 /* a non-zero value just in case */;
extern uint8_t map_basic[];
(void)last_menu;
init(map_basic);
while (1) {
int should_draw = 0;
key_event_t event = getkey_opt(GETKEY_MOD_SHIFT | GETKEY_BACKLIGHT,
&timeout);
/* TODO: find a way to creatively deduce the time it takes to
* go from here to the `if (should_draw)`, in order to keep
* an as-constant-as-possible draw time */
switch (event.type) {
case KEYEV_NONE:
/* timeout event */
should_draw = 1;
break;
case KEYEV_DOWN:
if (event.key == KEY_EXE)
return no_menu;
}
if (timeout < 0 || should_draw) {
timeout = 65;
state.drf = (state.drf + 1) & 255;
/* Here, we should draw. */
tick(); /* TODO: perhaps a better placement somewhere */
draw();
}
}
return no_menu;
}