mirror of
https://github.com/thepigeongenerator/tetris_clone.git
synced 2025-12-17 14:05:45 +01:00
implement new code, and remove deprecated old code. Overal optimisation.
this code is currently broken, but this'll be fixed in a subsequent commit
This commit is contained in:
169
src/game/game.c
169
src/game/game.c
@@ -1,152 +1,71 @@
|
|||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
|
||||||
#include <SDL_audio.h>
|
|
||||||
#include <SDL_keyboard.h>
|
|
||||||
#include <SDL_scancode.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "../io/audio.h"
|
#include "../io/colour/colour8.h"
|
||||||
#include "../util/types.h"
|
#include "../util/types.h"
|
||||||
|
#include "../util/vec.h"
|
||||||
#include "./tetromino/shapes.h"
|
#include "./tetromino/shapes.h"
|
||||||
#include "gametime.h"
|
|
||||||
#include "tetromino/placing.h"
|
#include "tetromino/placing.h"
|
||||||
|
|
||||||
/* shuffle the array using a Fisher–Yates shuffle */
|
static colour8 rowdat[COLUMNS * ROWS] = {0}; // contains the raw data of the rows, in no particular order
|
||||||
static inline void shuffle(uint8_t const size, u8* const elmnts) {
|
static struct gamedata dat = {0};
|
||||||
for (uint i = 0; i < (size - 1); i++) {
|
|
||||||
uint const j = i + rand() % (size - i);
|
/* shuffle an array using the Fisher–Yates shuffle algorithm.
|
||||||
u8 const cache = elmnts[i];
|
* `nmemb` is the number of members.
|
||||||
elmnts[i] = elmnts[j];
|
* `membs` is the byte size of each member */
|
||||||
elmnts[j] = cache;
|
static void shuffle(void* restrict ptr, size_t nmemb, size_t membs) {
|
||||||
|
u8 dat[membs];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nmemb; i++) {
|
||||||
|
size_t j = i + rand() % (nmemb - i);
|
||||||
|
void* ptri = (u8*)ptr + i * membs;
|
||||||
|
void* ptrj = (u8*)ptr + j * membs;
|
||||||
|
memcpy(dat, ptri, membs);
|
||||||
|
memcpy(ptri, ptrj, membs);
|
||||||
|
memcpy(ptrj, ptri, membs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void next_shape(gamedata* const dat) {
|
void next_shape(void) {
|
||||||
dat->curr_idx++; // increase the current shape index
|
// as long as we're not at the last shape, we can just increment
|
||||||
dat->sel_x = COLUMNS / 2 - SHAPE_WIDTH / 2; // move the shape position to the centre
|
dat.pdat.sel = (i8vec2){COLUMNS / 2 - SHAPE_WIDTH / 2, 0};
|
||||||
dat->sel_y = 0;
|
dat.pdat.idx++;
|
||||||
|
if (dat.pdat.idx < TETROMINO_COUNT - 1) return;
|
||||||
|
|
||||||
// return if know which shape is next
|
// shuffle all next shapes, preserving the last
|
||||||
if (dat->curr_idx < (TETROMINO_COUNT - 1))
|
dat.pdat.idx = 0;
|
||||||
return;
|
shuffle(dat.pdat.nxt, TETROMINO_COUNT - 1, sizeof(u8));
|
||||||
|
|
||||||
dat->curr_idx = 0;
|
// swap the first and last shape, thus preserving what the shape would've been
|
||||||
|
u8 cache = dat.pdat.nxt[0];
|
||||||
shuffle(TETROMINO_COUNT - 1, dat->nxt);
|
dat.pdat.nxt[0] = dat.pdat.nxt[TETROMINO_COUNT - 1];
|
||||||
u8 cache = dat->nxt[0];
|
dat.pdat.nxt[TETROMINO_COUNT - 1] = cache;
|
||||||
dat->nxt[0] = dat->nxt[TETROMINO_COUNT - 1];
|
|
||||||
dat->nxt[TETROMINO_COUNT - 1] = cache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_init(gamedata* const dat) {
|
struct gamedata* game_init(void) {
|
||||||
// set a random seed using the system clock
|
|
||||||
srand(time(NULL));
|
srand(time(NULL));
|
||||||
|
|
||||||
struct gametime gt = {{0}, 0};
|
// populate the data arrays
|
||||||
gametime_get(>.ts);
|
for (int i = 0; i < ROWS; i++)
|
||||||
|
dat.rows[i] = rowdat + i * COLUMNS;
|
||||||
|
for (int i = 0; i < TETROMINO_COUNT; i++)
|
||||||
|
dat.pdat.nxt[i] = i;
|
||||||
|
|
||||||
// initialize audio device
|
// initialise the placing data correctly
|
||||||
audio_device_init(32000, AUDIO_S16, 1, 4096);
|
dat.pdat.sel = (i8vec2){COLUMNS / 2 - SHAPE_WIDTH / 2, 0};
|
||||||
|
shuffle(dat.pdat.nxt, TETROMINO_COUNT, sizeof(u8));
|
||||||
*dat = (gamedata){
|
return &dat;
|
||||||
{0}, // rowdat
|
|
||||||
{0}, // row
|
|
||||||
gt, // time
|
|
||||||
audio_wav_load("korobeiniki.wav"), // music
|
|
||||||
audio_wav_load("place.wav"), // place_sfx
|
|
||||||
0, // score
|
|
||||||
{0}, // nxt
|
|
||||||
0, // curr_idx
|
|
||||||
0, // sel_x
|
|
||||||
0, // sel_y
|
|
||||||
true, // run
|
|
||||||
};
|
|
||||||
|
|
||||||
// initialize the rows within the game data
|
|
||||||
for (int8_t i = 0; i < ROWS; i++) {
|
|
||||||
dat->rows[i] = dat->rowdat + (i * COLUMNS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the shape data in each slot to it's corrsponding ID
|
|
||||||
for (uint i = 0; i < TETROMINO_COUNT; i++)
|
|
||||||
dat->nxt[i] = i;
|
|
||||||
|
|
||||||
dat->curr_idx = -1; // set the current index to the max so it becomes zero after increasement
|
|
||||||
next_shape(dat); // select the next shape (shuffle should not be triggered)
|
|
||||||
shuffle(TETROMINO_COUNT, dat->nxt); // manually trigger a shuffle
|
|
||||||
}
|
|
||||||
|
|
||||||
// updates the gametime
|
|
||||||
static inline void update_gametime(gamedata* dat) {
|
|
||||||
struct timespec ts;
|
|
||||||
gametime_get(&ts);
|
|
||||||
dat->time.ms = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
||||||
dat->time.ts = ts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// called every time the game's state is updated
|
// called every time the game's state is updated
|
||||||
void game_update(gamedata* const dat) {
|
void game_update(int movdat) {
|
||||||
static time_t timer_update = 0;
|
place_update(&dat, movdat);
|
||||||
static time_t timer_music = 0;
|
|
||||||
static time_t timer_move = 0;
|
|
||||||
static time_t timer_rot = 0;
|
|
||||||
update_gametime(dat);
|
|
||||||
uint8_t const* keys = SDL_GetKeyboardState(NULL);
|
|
||||||
|
|
||||||
if (keys[SDL_SCANCODE_ESCAPE])
|
|
||||||
dat->run = false;
|
|
||||||
|
|
||||||
input_data move = MOVE_NONE; // contains the move data
|
|
||||||
time_t ctime = dat->time.ms;
|
|
||||||
|
|
||||||
if (ctime > timer_update) {
|
|
||||||
timer_update = ctime + 500;
|
|
||||||
move |= MOVE_DOWN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctime > timer_music) {
|
void game_free(void) {
|
||||||
timer_music = ctime + (dat->music.ms);
|
|
||||||
audio_play(&dat->music);
|
|
||||||
}
|
|
||||||
|
|
||||||
// for rotation updating
|
|
||||||
if (ctime > timer_rot) {
|
|
||||||
input_data urot = MOVE_NONE;
|
|
||||||
if (keys[SDL_SCANCODE_Q]) urot |= MOVE_ROTLEFT;
|
|
||||||
if (keys[SDL_SCANCODE_E]) urot |= MOVE_ROTRIGHT;
|
|
||||||
|
|
||||||
if (urot != MOVE_NONE) {
|
|
||||||
timer_rot = ctime + 100;
|
|
||||||
move |= urot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for movement updating
|
|
||||||
if (ctime > timer_move) {
|
|
||||||
input_data umove = MOVE_NONE;
|
|
||||||
if (keys[SDL_SCANCODE_LEFT] || keys[SDL_SCANCODE_A]) umove |= MOVE_LEFT;
|
|
||||||
if (keys[SDL_SCANCODE_RIGHT] || keys[SDL_SCANCODE_D]) umove |= MOVE_RIGHT;
|
|
||||||
if (keys[SDL_SCANCODE_DOWN] || keys[SDL_SCANCODE_S] || keys[SDL_SCANCODE_SPACE]) umove |= MOVE_DOWN;
|
|
||||||
|
|
||||||
if (umove != MOVE_NONE) {
|
|
||||||
timer_move = ctime + 20;
|
|
||||||
move |= umove;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the block position
|
|
||||||
if (move != MOVE_NONE)
|
|
||||||
place_update(dat, move);
|
|
||||||
}
|
|
||||||
|
|
||||||
void game_free(gamedata* const dat) {
|
|
||||||
audio_wav_unload(&dat->music);
|
|
||||||
audio_wav_unload(&dat->place_sfx);
|
|
||||||
audio_device_free();
|
|
||||||
|
|
||||||
// zero-out the rest of the data
|
|
||||||
*dat = (gamedata){0};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "../io/audio.h"
|
|
||||||
#include "../io/colour/colour8.h"
|
#include "../io/colour/colour8.h"
|
||||||
#include "../util/types.h"
|
#include "../util/types.h"
|
||||||
#include "gametime.h"
|
#include "../util/vec.h"
|
||||||
|
#include "tetromino/shapes.h"
|
||||||
|
|
||||||
// constants for pi(π) and tau(τ)
|
// constants for pi(π) and tau(τ)
|
||||||
#define PI (M_PI) // π constant
|
#define PI (M_PI) // π constant
|
||||||
@@ -15,27 +15,27 @@
|
|||||||
#define TAUf (M_PIf * 2.0F) // τ constant as a 32-bit floating point
|
#define TAUf (M_PIf * 2.0F) // τ constant as a 32-bit floating point
|
||||||
|
|
||||||
// stores the data used in the game
|
// stores the data used in the game
|
||||||
#define COLUMNS ((int8_t)10)
|
#define COLUMNS 10
|
||||||
#define ROWS ((int8_t)24)
|
#define ROWS 24
|
||||||
|
|
||||||
typedef colour8 const* const row_const;
|
/* contains the placement data */
|
||||||
typedef colour8* row;
|
struct pdat {
|
||||||
|
u8 nxt[TETROMINO_COUNT];
|
||||||
|
i8vec2 sel;
|
||||||
|
u8 idx;
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct {
|
/* contains game data that's commonly shared */
|
||||||
colour8 rowdat[ROWS * COLUMNS];
|
struct gamedata {
|
||||||
|
struct pdat pdat;
|
||||||
colour8* rows[ROWS];
|
colour8* rows[ROWS];
|
||||||
struct gametime time;
|
u16 pnts;
|
||||||
audiodata music;
|
};
|
||||||
audiodata place_sfx;
|
|
||||||
uint16_t score;
|
|
||||||
u8 nxt[7]; // the order of the shape ids that they should appear in
|
|
||||||
uint8_t curr_idx; // current shape index
|
|
||||||
int8_t sel_x; // selected shape x position
|
|
||||||
int8_t sel_y; // selected shape y position
|
|
||||||
bool run;
|
|
||||||
} gamedata;
|
|
||||||
|
|
||||||
void next_shape(gamedata*); // initializes everything needed to start the game; outputs to gamedata
|
|
||||||
void game_init(gamedata*); // initializes the game
|
/* increments to the next shape, shuffling the next shapes, if there isn't a next shape immediately after the current one. */
|
||||||
void game_update(gamedata*); // causes an update to occur within the game
|
void next_shape(void);
|
||||||
void game_free(gamedata*); // frees the resources associated with the game
|
|
||||||
|
struct gamedata* game_init(void);
|
||||||
|
void game_update(int);
|
||||||
|
void game_free(void);
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
struct gametime {
|
|
||||||
struct timespec ts;
|
|
||||||
time_t ms;
|
|
||||||
};
|
|
||||||
|
|
||||||
#if __has_include(<features.h>)
|
|
||||||
#include <features.h>
|
|
||||||
#endif
|
|
||||||
#if __has_include(<features.h>) && _POSIX_C_SOURCE >= 199309L
|
|
||||||
#include <bits/time.h>
|
|
||||||
static inline void gametime_get(struct timespec* ts) {
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, ts);
|
|
||||||
}
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
#include <profileapi.h>
|
|
||||||
#include <windows.h>
|
|
||||||
#include <winnt.h>
|
|
||||||
static inline void gametime_get(struct timespec* ts) {
|
|
||||||
LARGE_INTEGER cnt, frq;
|
|
||||||
QueryPerformanceCounter(&cnt);
|
|
||||||
QueryPerformanceFrequency(&frq);
|
|
||||||
ts->tv_sec = (time_t)(cnt.QuadPart / frq.QuadPart);
|
|
||||||
ts->tv_nsec = (time_t)((cnt.QuadPart % frq.QuadPart) * 1000000000 / frq.QuadPart);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
#include "paths.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "../util/compat.h"
|
|
||||||
|
|
||||||
char const* restrict path_dat = NULL;
|
|
||||||
char const* restrict path_opts = NULL;
|
|
||||||
char const* restrict path_font = NULL;
|
|
||||||
char const* restrict path_music = NULL;
|
|
||||||
char const* restrict path_place_sfx = NULL;
|
|
||||||
|
|
||||||
// gets the game's data path, returns 0 on failure, otherwise the datapath's string length
|
|
||||||
static unsigned getdatpath(void) {
|
|
||||||
char const* home = getenv(unixonly("HOME") winonly("APPDATA")); // get the user data directory path (appropriated for each platform)
|
|
||||||
if (!home) home = "."; // if this failed, set the path to `.`, which represents cwd
|
|
||||||
|
|
||||||
unsigned len = strlen(home);
|
|
||||||
len += 1 + 20 unixonly(+13); // add 21 bytes to the home length, to account for adding .local/share later
|
|
||||||
char* datpath = malloc(len);
|
|
||||||
if (!datpath) return 0;
|
|
||||||
|
|
||||||
// copy the data from home into the datapath
|
|
||||||
strcpy(datpath, home);
|
|
||||||
|
|
||||||
#ifdef __unix__
|
|
||||||
// include the .local/share directory, if the HOME environment variable was valid
|
|
||||||
if (home[0] != '.') strcat(datpath, "/.local/share");
|
|
||||||
else {
|
|
||||||
// if the HOME directory wasn't defined, shrink the string
|
|
||||||
len -= 13;
|
|
||||||
void* ptr = realloc(datpath, len);
|
|
||||||
if (ptr) datpath = ptr; // likely doesn't actually change the pointer, but just to be sure
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
strcat(datpath, PATH_SEP_STR "quinns_tetris_clone");
|
|
||||||
path_dat = datpath;
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline char const* init_path(char const* restrict const str, unsigned len) {
|
|
||||||
void* ptr = malloc(len);
|
|
||||||
if (!ptr) return NULL;
|
|
||||||
strcpy(ptr, path_dat);
|
|
||||||
strcat(ptr, str);
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
int paths_init(void) {
|
|
||||||
unsigned len = getdatpath();
|
|
||||||
if (!len) return 1;
|
|
||||||
|
|
||||||
// these are explicitly static, as string literals just work like that
|
|
||||||
path_opts = init_path("/opts.cfg", len + 9); // TODO: shouldn't opts be stored at .config/?
|
|
||||||
path_font = init_path("/pixeldroid_botic-regular.ttf", len + 29); // TODO: these three paths should not be stored like opts
|
|
||||||
path_music = init_path("/korobeiniki.wav", len + 16);
|
|
||||||
path_place_sfx = init_path("place.wav", len + 10);
|
|
||||||
return -(!path_opts || !path_font || !path_music || !path_place_sfx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void paths_free(void) {
|
|
||||||
free((void*)path_dat), path_dat = NULL;
|
|
||||||
free((void*)path_opts), path_opts = NULL;
|
|
||||||
free((void*)path_music), path_music = NULL;
|
|
||||||
free((void*)path_place_sfx), path_place_sfx = NULL;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
extern char const* restrict path_dat;
|
|
||||||
extern char const* restrict path_opts;
|
|
||||||
extern char const* restrict path_font;
|
|
||||||
extern char const* restrict path_music;
|
|
||||||
extern char const* restrict path_place_sfx;
|
|
||||||
|
|
||||||
/* initializes the paths, paths are evaluated to NULL upon failure.
|
|
||||||
returns 0 upon success, >0 upon failure <0 upon non-critical failure */
|
|
||||||
int paths_init(void);
|
|
||||||
|
|
||||||
/* frees allocated data */
|
|
||||||
void paths_free(void);
|
|
||||||
@@ -1,71 +1,67 @@
|
|||||||
#include "placing.h"
|
#include "placing.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/cdefs.h>
|
||||||
|
|
||||||
#include "../../io/audio.h"
|
#include "../../io/input.h"
|
||||||
#include "../../io/colour/colour8.h"
|
#include "../../io/window.h"
|
||||||
#include "../../util/types.h"
|
#include "../../util/types.h"
|
||||||
|
#include "../../util/vec.h"
|
||||||
#include "../game.h"
|
#include "../game.h"
|
||||||
#include "shapes.h"
|
#include "shapes.h"
|
||||||
|
|
||||||
|
|
||||||
static int is_filled(row_const const row) {
|
/* checks if `row` of `COLUMNS` wide contains anything with `0`.
|
||||||
for (int8_t x = 0; x < COLUMNS; x++) {
|
* returns `1` if it doesn't, otherwise returns `0` */
|
||||||
if (row[x] == 0) {
|
__attribute_const__ static int is_filled(u8 const* restrict row) {
|
||||||
return 0;
|
int res = 0;
|
||||||
}
|
#if defined(__clang__)
|
||||||
|
#pragma unroll COLUMNS
|
||||||
|
#elif defined(__GNUC__)
|
||||||
|
#pragma GCC unroll COLUMNS
|
||||||
|
#endif
|
||||||
|
for (int i = 0; i < COLUMNS; i++) res |= !row[i];
|
||||||
|
return !res;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
/* checks for filled rows, clearing a max of 4 consecutive rows, if present.
|
||||||
}
|
* all row pointers will be moved down to fill the gap, the filled rows will be inserted at the top, and zeroed-out */
|
||||||
|
static void clear_rows(u8* restrict* restrict rows, u16* const score) {
|
||||||
|
u8* cache[4] = {0}; // the I piece is 4 long, meaning only 4 rows can be cleared at a time
|
||||||
|
uint fillc = 0;
|
||||||
|
uint checkc = 0;
|
||||||
|
|
||||||
static void clear_rows(row* const rows, uint16_t* const score) {
|
for (uint y = 0; y < (ROWS - fillc); y++) {
|
||||||
row cache[4] = {0}; // you can only clear four rows at a time
|
int i = (ROWS - 1) - y; // invert the index direction, so we start at the highest index
|
||||||
unsigned filled = 0;
|
rows[i] = rows[i - fillc]; // either assigns the current row to itself, or to the "next" row
|
||||||
unsigned checked = 0;
|
|
||||||
|
|
||||||
// loop through each row (excluding the empty rows at the top when clearing a line)
|
if (checkc >= 4 || !is_filled(rows[i])) {
|
||||||
for (unsigned y = 0; y < (ROWS - filled); y++) {
|
checkc += (fillc > 0 && checkc < 4);
|
||||||
int const i = (ROWS - 1) - y; // get the index starting from the bottom
|
|
||||||
|
|
||||||
rows[i] = rows[i - filled]; // set the row to the new or same address
|
|
||||||
|
|
||||||
// continue if the line isn't filled or the max amount has been reached
|
|
||||||
if (checked >= 4 || !is_filled(rows[i])) {
|
|
||||||
if (filled > 0 && checked < 4) checked++;
|
|
||||||
continue; // continue to the next line
|
|
||||||
}
|
|
||||||
|
|
||||||
cache[filled] = rows[i]; // cache the current row address
|
|
||||||
filled++; // increase filled, and keep the row in the cache
|
|
||||||
checked++; // increase the checked count
|
|
||||||
y--; // decrease y to check this line again
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filled == 0) return;
|
|
||||||
*score += 8 << filled;
|
|
||||||
|
|
||||||
// do while, as we already know that entering the loop that filled is non-zero
|
|
||||||
do {
|
|
||||||
filled--;
|
|
||||||
rows[filled] = cache[filled];
|
|
||||||
|
|
||||||
// zero out the filled row
|
|
||||||
for (unsigned x = 0; x < COLUMNS; x++)
|
|
||||||
cache[filled][x] = 0;
|
|
||||||
} while (filled > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sets a shape to the screen
|
|
||||||
static void set_shape_i(row const* const row, u8 const id, int8_t const pos_x) {
|
|
||||||
u16 const shape = shape_from_id(id);
|
|
||||||
colour8 const colour = colour_from_id(id);
|
|
||||||
for (int8_t y = 0; y < SHAPE_HEIGHT; y++) {
|
|
||||||
u8 const shape_row = shape_get_row(shape, y);
|
|
||||||
// TODO: this is suboptimal, ditch the entire "representing shapes as binary-formatted data" and instead use a switch...case.
|
|
||||||
|
|
||||||
if (shape_row == 0)
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cache[fillc] = rows[i]; // cache the current row address, since it's going to be overridden
|
||||||
|
fillc++;
|
||||||
|
checkc++;
|
||||||
|
y--;
|
||||||
|
}
|
||||||
|
|
||||||
|
*score += (8 << fillc) * !!fillc;
|
||||||
|
for (uint i = 0; i < fillc; i++) {
|
||||||
|
memset(cache[i], 0, COLUMNS);
|
||||||
|
rows[i] = cache[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is suboptimal, ditch the entire "representing shapes as binary-formatted data" and instead use a switch...case.
|
||||||
|
/* writes a shape to the screen */
|
||||||
|
static void set_shape(u8* restrict const* restrict row, u8 id, int pos_x) {
|
||||||
|
u16 shape = shape_from_id(id);
|
||||||
|
u8 colour = colour_from_id(id);
|
||||||
|
|
||||||
|
for (uint y = 0; y < SHAPE_HEIGHT; y++) {
|
||||||
|
u8 const shape_row = shape_get_row(shape, y);
|
||||||
|
|
||||||
for (int8_t x = 0; x < SHAPE_WIDTH; x++)
|
for (int8_t x = 0; x < SHAPE_WIDTH; x++)
|
||||||
if (shape_is_set(shape_row, x))
|
if (shape_is_set(shape_row, x))
|
||||||
@@ -73,11 +69,7 @@ static void set_shape_i(row const* const row, u8 const id, int8_t const pos_x) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void set_shape(row const* const row, u8 const id, int8_t const pos_x, int8_t const pos_y) {
|
static int shape_intersects(u8* restrict const* restrict const rows, u8 const id, i8vec2 pos) {
|
||||||
set_shape_i(&row[pos_y], id, pos_x); // calls itself, but omitting the pos_y argument, instead opting for specifying the row
|
|
||||||
}
|
|
||||||
|
|
||||||
static int shape_intersects(row const* const rows, u8 const id, int8_t const x, int8_t const y) {
|
|
||||||
u16 const shape = shape_from_id(id);
|
u16 const shape = shape_from_id(id);
|
||||||
|
|
||||||
for (int y0 = 0; y0 < SHAPE_HEIGHT; y0++) {
|
for (int y0 = 0; y0 < SHAPE_HEIGHT; y0++) {
|
||||||
@@ -86,8 +78,8 @@ static int shape_intersects(row const* const rows, u8 const id, int8_t const x,
|
|||||||
|
|
||||||
for (int x0 = 0; x0 < SHAPE_WIDTH; x0++) {
|
for (int x0 = 0; x0 < SHAPE_WIDTH; x0++) {
|
||||||
if (shape_is_set(shape_row, x0) == false) continue; // if the bit isn't set at this index; continue
|
if (shape_is_set(shape_row, x0) == false) continue; // if the bit isn't set at this index; continue
|
||||||
int const x1 = x + x0;
|
int x1 = pos[0] + x0;
|
||||||
int const y1 = y + y0;
|
int y1 = pos[1] + y0;
|
||||||
|
|
||||||
if (x1 < 0 || x1 >= COLUMNS) return 1; // if X is out of bounds
|
if (x1 < 0 || x1 >= COLUMNS) return 1; // if X is out of bounds
|
||||||
if (y1 < 0 || y1 >= ROWS) return 1; // if Y is out of bounds
|
if (y1 < 0 || y1 >= ROWS) return 1; // if Y is out of bounds
|
||||||
@@ -97,50 +89,37 @@ static int shape_intersects(row const* const rows, u8 const id, int8_t const x,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline u8 rotate_id(u8 const id, int const dir) {
|
void place_update(struct gamedata* gdat, int movdat) {
|
||||||
return (id + dir) & 31;
|
|
||||||
}
|
|
||||||
|
|
||||||
void place_update(gamedata* const game_data, input_data const move) {
|
|
||||||
// store the current index and ID, only changes when placed (which yields no movement) and rotation (which occurs last)
|
// store the current index and ID, only changes when placed (which yields no movement) and rotation (which occurs last)
|
||||||
uint8_t const curr_idx = game_data->curr_idx;
|
int tmp;
|
||||||
u8 curr_id = game_data->nxt[curr_idx];
|
u8 idx = gdat->pdat.idx;
|
||||||
|
u8 id = gdat->pdat.nxt[idx];
|
||||||
|
|
||||||
// set the shape if we moved vertically and intersected
|
// set the shape if we moved vertically and intersected
|
||||||
if (move & 4) {
|
if (movdat & MOVD) {
|
||||||
int8_t const y = game_data->sel_y + 1;
|
i8 y = gdat->pdat.sel[1] + 1;
|
||||||
if (shape_intersects(game_data->rows, curr_id, game_data->sel_x, y)) {
|
if (shape_intersects(gdat->rows, id, gdat->pdat.sel)) {
|
||||||
set_shape(game_data->rows, curr_id, game_data->sel_x, game_data->sel_y); // if the shape intersects vertically, write the shape at the current position and return
|
set_shape(gdat->rows + gdat->pdat.sel[1], id, gdat->pdat.sel[0]); // if the shape intersects vertically, write the shape at the current position and return
|
||||||
clear_rows(game_data->rows, &game_data->score); // clear the rows that have been completed
|
clear_rows(gdat->rows, &gdat->pnts); // clear the rows that have been completed
|
||||||
|
|
||||||
audio_play(&game_data->place_sfx);
|
// TODO: play place_sfx
|
||||||
|
|
||||||
next_shape(game_data);
|
next_shape();
|
||||||
if (shape_intersects(game_data->rows, game_data->curr_idx, game_data->sel_x, game_data->sel_y))
|
if (shape_intersects(gdat->rows, gdat->pdat.idx, gdat->pdat.sel)) {
|
||||||
game_data->run = false;
|
window_close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, just set Y
|
// otherwise, just set Y
|
||||||
game_data->sel_y = y;
|
gdat->pdat.sel[1] = y;
|
||||||
}
|
|
||||||
|
|
||||||
// update shape's X coordinate movement
|
|
||||||
if ((move & 3) != 3 && (move & 3)) {
|
|
||||||
int8_t const x = game_data->sel_x + ((move & 3) == 1 ? -1 : 1); // either move along -x or +x
|
|
||||||
if (shape_intersects(game_data->rows, curr_id, x, game_data->sel_y) == false) {
|
|
||||||
game_data->sel_x = x; // set X if the shape does not intersect
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the shape's rotation
|
// update X axis
|
||||||
if (move & 8 || move & 16) {
|
tmp = !!(movdat & MOVL) * -1 + !!(movdat & MOVR);
|
||||||
u8 const id = move & 8 // check which direction we should move
|
gdat->pdat.sel[0] += (tmp && shape_intersects(gdat->rows, id, gdat->pdat.sel + (i8vec2){tmp, 0})) * tmp;
|
||||||
? rotate_id(curr_id, -8)
|
|
||||||
: rotate_id(curr_id, 8);
|
// update roll
|
||||||
if (shape_intersects(game_data->rows, id, game_data->sel_x, game_data->sel_y) == false) {
|
tmp = id - (((!!(movdat & MOVRL) * -8 + !!(movdat & MOVRR) * 8) + id) & 31);
|
||||||
game_data->nxt[curr_idx] = id;
|
gdat->pdat.nxt[idx] += (tmp && shape_intersects(gdat->rows, tmp, gdat->pdat.sel)) * tmp;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,7 @@
|
|||||||
|
|
||||||
#include "../game.h"
|
#include "../game.h"
|
||||||
|
|
||||||
typedef uint8_t input_data;
|
// TODO: this singular function does too much, break it up via different returns and such.
|
||||||
enum {
|
/* updates the movement of the pdat structure, updating the rows when colliding downwards.
|
||||||
MOVE_NONE = 0,
|
* closes window when the next shape intersects with the current one */
|
||||||
MOVE_LEFT = 1,
|
void place_update(struct gamedata* gdat, int movdat);
|
||||||
MOVE_RIGHT = 2,
|
|
||||||
MOVE_DOWN = 4,
|
|
||||||
MOVE_ROTLEFT = 8,
|
|
||||||
MOVE_ROTRIGHT = 16,
|
|
||||||
};
|
|
||||||
|
|
||||||
void place_update(gamedata* game_data, input_data move);
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
void dbg_set_all(gamedata* game_data);
|
|
||||||
#endif
|
|
||||||
|
|||||||
145
src/io/render.c
145
src/io/render.c
@@ -7,81 +7,63 @@
|
|||||||
#include <SDL_surface.h>
|
#include <SDL_surface.h>
|
||||||
#include <SDL_ttf.h>
|
#include <SDL_ttf.h>
|
||||||
#include <SDL_video.h>
|
#include <SDL_video.h>
|
||||||
|
#include <assert.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "../error.h"
|
#include "../error.h"
|
||||||
#include "../game/game.h"
|
#include "../game/game.h"
|
||||||
#include "../game/tetromino/shapes.h"
|
#include "../game/tetromino/shapes.h"
|
||||||
#include "../util/types.h"
|
#include "../util/types.h"
|
||||||
|
#include "../util/vec.h"
|
||||||
#include "colour/colour32.h"
|
#include "colour/colour32.h"
|
||||||
#include "colour/colour8.h"
|
#include "colour/colour8.h"
|
||||||
|
|
||||||
#define COLOUR_SCORE COLOUR32_YELLOW
|
#define COLOUR_SCORE COLOUR32_YELLOW
|
||||||
|
|
||||||
void render_init(renderdata* const render_dat, gamedata const* const game_dat) {
|
SDL_Renderer* rend = NULL;
|
||||||
SDL_Window* const window = SDL_CreateWindow("tetris clone", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
|
TTF_Font* font = NULL;
|
||||||
if (window == NULL) fatal(ERROR_SDL_RENDERING_INIT, "Window failed to be created! SDL Error: %s", SDL_GetError());
|
struct gamedata const* gdat = NULL;
|
||||||
|
|
||||||
SDL_Renderer* const renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);
|
static SDL_Surface* score_surface = NULL;
|
||||||
if (renderer == NULL) fatal(ERROR_SDL_RENDERING_INIT, "Renderer failed to be created! SDL Error: %s", SDL_GetError());
|
static SDL_Texture* score_texture = NULL;
|
||||||
|
|
||||||
TTF_Font* const font = TTF_OpenFont("pixeldroid_botic-regular.ttf", PX_DENS);
|
void render_init(SDL_Window* win, struct gamedata const* game_data) {
|
||||||
|
rend = SDL_CreateRenderer(win, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);
|
||||||
|
if (rend == NULL) fatal(ERROR_SDL_RENDERING_INIT, "Renderer failed to be created! SDL Error: %s", SDL_GetError());
|
||||||
|
|
||||||
|
font = TTF_OpenFont("pixeldroid_botic-regular.ttf", PX_DENS);
|
||||||
if (font == NULL) error("Failed to open font! TTF Error: %s", TTF_GetError());
|
if (font == NULL) error("Failed to open font! TTF Error: %s", TTF_GetError());
|
||||||
|
|
||||||
// initialize the render data
|
gdat = game_data;
|
||||||
*render_dat = (renderdata){
|
|
||||||
game_dat,
|
|
||||||
window,
|
|
||||||
renderer,
|
|
||||||
font,
|
|
||||||
calloc(1, sizeof(struct render_cache)), // zero-initialize the memory as we read from it
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int32_t get_column_pos(uint8_t column) {
|
static inline int32_t get_column_pos(uint column) {
|
||||||
return column * BLOCK_WIDTH + 1 + TET_PADDING;
|
return column * BLOCK_WIDTH + 1 + TET_PADDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int32_t get_row_pos(uint8_t row) {
|
static inline int32_t get_row_pos(uint row) {
|
||||||
return row * BLOCK_HEIGHT + 1 + TET_PADDING;
|
return row * BLOCK_HEIGHT + 1 + TET_PADDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void draw_score_text(renderdata const* dat) {
|
static void draw_score_text(void) {
|
||||||
struct render_cache* const cache = dat->cache;
|
static u16 prev_pts = 0xFFFF;
|
||||||
uint16_t const score = dat->game_dat->score;
|
|
||||||
|
|
||||||
SDL_Renderer* const renderer = dat->renderer;
|
if (prev_pts ^ gdat->pnts) {
|
||||||
TTF_Font* const font = dat->font;
|
char score_text[6] = "0"; // max digits of a u16 + null terminator
|
||||||
|
prev_pts = gdat->pnts;
|
||||||
|
if (gdat->pnts) sprintf(score_text, "%hu0", gdat->pnts);
|
||||||
|
|
||||||
if (cache->prevscore != score || cache->score_texture == NULL) {
|
SDL_FreeSurface(score_surface);
|
||||||
char score_text[6]; // max digits of a uint16 + \0 terminator
|
SDL_DestroyTexture(score_texture);
|
||||||
if (!score) sprintf(score_text, "0");
|
score_surface = TTF_RenderText_Solid(font, score_text, (SDL_Colour){COLOUR_SCORE.r, COLOUR_SCORE.g, COLOUR_SCORE.b, COLOUR_SCORE.a});
|
||||||
else sprintf(score_text, "%hu0", score);
|
score_texture = SDL_CreateTextureFromSurface(rend, score_surface);
|
||||||
|
|
||||||
SDL_Surface* const txt_surface = TTF_RenderText_Solid(font, score_text, (SDL_Colour){COLOUR_SCORE.r, COLOUR_SCORE.g, COLOUR_SCORE.b, COLOUR_SCORE.a});
|
|
||||||
SDL_Texture* const txt_texture = SDL_CreateTextureFromSurface(renderer, txt_surface);
|
|
||||||
|
|
||||||
if (cache->score_texture != NULL || cache->score_surface != NULL) {
|
|
||||||
// free old data
|
|
||||||
SDL_FreeSurface(cache->score_surface);
|
|
||||||
SDL_DestroyTexture(cache->score_texture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// write data to cache
|
assert(score_surface && score_texture);
|
||||||
cache->score_surface = txt_surface;
|
SDL_Rect text_rect = {get_column_pos(COLUMNS + 1), get_row_pos(0), score_surface->w, score_surface->h};
|
||||||
cache->score_texture = txt_texture;
|
SDL_RenderCopy(rend, score_texture, NULL, &text_rect);
|
||||||
}
|
|
||||||
|
|
||||||
if (cache->score_surface == NULL || cache->score_texture == NULL) {
|
|
||||||
error("the score texture was unavailable!", );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Rect text_rect = {get_column_pos(COLUMNS + 1), get_row_pos(0), cache->score_surface->w, cache->score_surface->h};
|
|
||||||
SDL_RenderCopy(renderer, cache->score_texture, NULL, &text_rect);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this is suboptimal, since each block will be 2 triangles, wasting perf. Consider using switch...case hard-coded drawing
|
// TODO: this is suboptimal, since each block will be 2 triangles, wasting perf. Consider using switch...case hard-coded drawing
|
||||||
@@ -92,72 +74,61 @@ static inline int draw_block(SDL_Renderer* const renderer, int8_t const x, int8_
|
|||||||
}
|
}
|
||||||
|
|
||||||
// draws a shape at the specified position
|
// draws a shape at the specified position
|
||||||
static void draw_shape(SDL_Renderer* const renderer, u8 const id, int8_t const pos_x, int8_t const pos_y) {
|
static void draw_shape(u8 const id, i8vec2 pos) {
|
||||||
u16 shape = shape_from_id(id);
|
u16 shape = shape_from_id(id);
|
||||||
set_colour8(renderer, colour_from_id(id));
|
set_colour8(rend, colour_from_id(id));
|
||||||
|
|
||||||
for (int8_t y = 0; y < SHAPE_HEIGHT; y++) {
|
for (int8_t y = 0; y < SHAPE_HEIGHT; y++) {
|
||||||
u8 shape_row = shape_get_row(shape, y);
|
u8 shape_row = shape_get_row(shape, y);
|
||||||
|
|
||||||
if (shape_row == 0)
|
if (shape_row == 0) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
for (int8_t x = 0; x < SHAPE_WIDTH; x++)
|
for (int8_t x = 0; x < SHAPE_WIDTH; x++)
|
||||||
if (shape_is_set(shape_row, x))
|
if (shape_is_set(shape_row, x))
|
||||||
draw_block(renderer, pos_x + x, pos_y + y);
|
draw_block(rend, pos[0] + x, pos[1] + y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw the block data in the level
|
// draw the block data in the level
|
||||||
static void render_level(SDL_Renderer* const renderer, gamedata const* const data) {
|
static void render_level(void) {
|
||||||
for (int8_t y = 0; y < ROWS; y++) {
|
for (int y = 0; y < ROWS; y++) {
|
||||||
row_const const row = data->rows[y];
|
u8 const* row = gdat->rows[y];
|
||||||
|
|
||||||
for (int8_t x = 0; x < COLUMNS; x++) {
|
for (int x = 0; x < COLUMNS; x++) {
|
||||||
if (row[x] != 0) {
|
if (row[x] != 0) {
|
||||||
set_colour8(renderer, row[x]);
|
set_colour8(rend, row[x]);
|
||||||
draw_block(renderer, x, y);
|
draw_block(rend, x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void render_update(renderdata const* const dat) {
|
void render_update(void) {
|
||||||
SDL_Renderer* const renderer = dat->renderer;
|
set_colour32(rend, COLOUR32_BLACK);
|
||||||
gamedata const* const game_data = dat->game_dat;
|
SDL_RenderClear(rend);
|
||||||
|
set_colour32(rend, COLOUR32_WHITE);
|
||||||
int success = 0; // if an error occurs, this value is <0
|
|
||||||
|
|
||||||
// clear render
|
|
||||||
set_colour32(renderer, COLOUR32_BLACK); // using colour32 is more efficient, as it sets the colours directly
|
|
||||||
success |= SDL_RenderClear(renderer);
|
|
||||||
|
|
||||||
set_colour32(renderer, COLOUR32_WHITE);
|
|
||||||
|
|
||||||
static SDL_Rect const field_size = {TET_PADDING, TET_PADDING, TET_WIDTH + 1, TET_HEIGHT + 1};
|
static SDL_Rect const field_size = {TET_PADDING, TET_PADDING, TET_WIDTH + 1, TET_HEIGHT + 1};
|
||||||
SDL_RenderDrawRect(renderer, &field_size);
|
SDL_RenderDrawRect(rend, &field_size);
|
||||||
draw_shape(renderer, game_data->nxt[game_data->curr_idx + 1], COLUMNS + 1, 3); // draw the next shape
|
|
||||||
|
|
||||||
if (dat->font)
|
if (font) draw_score_text();
|
||||||
draw_score_text(dat);
|
|
||||||
|
|
||||||
render_level(renderer, dat->game_dat);
|
render_level();
|
||||||
draw_shape(renderer, game_data->nxt[game_data->curr_idx], game_data->sel_x, game_data->sel_y); // draw the current shape
|
draw_shape(gdat->pdat.nxt[gdat->pdat.idx], gdat->pdat.sel);
|
||||||
|
draw_shape(gdat->pdat.nxt[gdat->pdat.idx + 1], (i8vec2){COLUMNS + 1, 3});
|
||||||
|
|
||||||
if (success < 0) {
|
SDL_RenderPresent(rend);
|
||||||
warn("something went wrong whilst renderering! SDL Error: %s\n", SDL_GetError());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_RenderPresent(renderer);
|
void render_free(void) {
|
||||||
}
|
assert(rend);
|
||||||
|
SDL_DestroyRenderer(rend);
|
||||||
|
rend = NULL;
|
||||||
|
|
||||||
void render_free(renderdata* const render_data) {
|
TTF_CloseFont(font);
|
||||||
SDL_DestroyRenderer(render_data->renderer);
|
SDL_FreeSurface(score_surface);
|
||||||
SDL_DestroyWindow(render_data->window);
|
SDL_DestroyTexture(score_texture);
|
||||||
TTF_CloseFont(render_data->font);
|
font = NULL;
|
||||||
SDL_FreeSurface(render_data->cache->score_surface);
|
score_surface = NULL;
|
||||||
SDL_DestroyTexture(render_data->cache->score_texture);
|
score_texture = NULL;
|
||||||
free(render_data->cache);
|
|
||||||
*render_data = (renderdata){0};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SDL_render.h>
|
|
||||||
#include <SDL_surface.h>
|
|
||||||
#include <SDL_ttf.h>
|
|
||||||
#include <SDL_video.h>
|
#include <SDL_video.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <sys/cdefs.h>
|
||||||
|
|
||||||
#include "../game/game.h"
|
#include "../game/game.h"
|
||||||
|
|
||||||
@@ -17,22 +15,6 @@
|
|||||||
#define BLOCK_WIDTH (TET_WIDTH / COLUMNS) // width of a block
|
#define BLOCK_WIDTH (TET_WIDTH / COLUMNS) // width of a block
|
||||||
#define BLOCK_HEIGHT (TET_HEIGHT / ROWS) // height of a block
|
#define BLOCK_HEIGHT (TET_HEIGHT / ROWS) // height of a block
|
||||||
|
|
||||||
// contains the data that's cached between renders
|
__nonnull((1, 2)) void render_init(SDL_Window*, struct gamedata const*);
|
||||||
struct render_cache {
|
void render_update(void); // causes a draw to occur, will also determine update rate
|
||||||
SDL_Texture* score_texture;
|
void render_free(void); // frees the memory allocated to the renderer in render_data
|
||||||
SDL_Surface* score_surface;
|
|
||||||
uint16_t prevscore;
|
|
||||||
};
|
|
||||||
|
|
||||||
// contains the data necessary for rendering
|
|
||||||
typedef struct {
|
|
||||||
gamedata const* game_dat;
|
|
||||||
SDL_Window* window;
|
|
||||||
SDL_Renderer* renderer;
|
|
||||||
TTF_Font* font;
|
|
||||||
struct render_cache* cache;
|
|
||||||
} renderdata;
|
|
||||||
|
|
||||||
void render_init(renderdata*, gamedata const*); // initializes the renderer, outputs to render_data
|
|
||||||
void render_update(renderdata const*); // causes a draw to occur, will also determine update rate
|
|
||||||
void render_free(renderdata*); // frees the memory allocated to the renderer in render_data
|
|
||||||
|
|||||||
@@ -5,17 +5,20 @@
|
|||||||
#include <SDL_error.h>
|
#include <SDL_error.h>
|
||||||
#include <SDL_video.h>
|
#include <SDL_video.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#include "../error.h"
|
#include "../error.h"
|
||||||
#include "../game/game.h"
|
#include "../game/game.h"
|
||||||
|
#include "../game/time.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
|
||||||
static SDL_Window* win = NULL;
|
static SDL_Window* win = NULL;
|
||||||
static bool close = false;
|
static bool close = false;
|
||||||
|
static audiodata music;
|
||||||
|
|
||||||
void window_init(void) {
|
static void window_init(struct gamedata const* gdat) {
|
||||||
assert(!win && !close);
|
assert(!win && !close);
|
||||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
|
||||||
fatal(ERROR_SDL_INIT, "SDL could not initialize! SDL Error: %s", SDL_GetError());
|
fatal(ERROR_SDL_INIT, "SDL could not initialize! SDL Error: %s", SDL_GetError());
|
||||||
@@ -24,27 +27,37 @@ void window_init(void) {
|
|||||||
if (window == NULL)
|
if (window == NULL)
|
||||||
fatal(ERROR_SDL_RENDERING_INIT, "Window failed to be created! SDL Error: %s", SDL_GetError());
|
fatal(ERROR_SDL_RENDERING_INIT, "Window failed to be created! SDL Error: %s", SDL_GetError());
|
||||||
|
|
||||||
render_init(win);
|
render_init(win, gdat);
|
||||||
audio_device_init(32000, AUDIO_S16, 1, 4096);
|
audio_device_init(32000, AUDIO_S16, 1, 4096);
|
||||||
|
music = audio_wav_load("korobeiniki.wav");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void window_free(void) {
|
||||||
|
assert(win);
|
||||||
|
// render_free();
|
||||||
|
SDL_DestroyWindow(win);
|
||||||
|
win = NULL;
|
||||||
|
close = false;
|
||||||
|
|
||||||
|
audio_wav_unload(&music);
|
||||||
|
audio_device_free();
|
||||||
|
}
|
||||||
|
|
||||||
|
void window_open(struct gamedata const* gdat) {
|
||||||
|
window_init(gdat);
|
||||||
|
|
||||||
|
while (!close) {
|
||||||
|
game_update(input_getdat());
|
||||||
|
render_update();
|
||||||
|
|
||||||
|
static time_t timeout = 0;
|
||||||
|
if (time_poll(time_pull(), music.ms, &timeout))
|
||||||
|
audio_play(&music);
|
||||||
|
}
|
||||||
|
|
||||||
|
window_free();
|
||||||
}
|
}
|
||||||
|
|
||||||
void window_close(void) {
|
void window_close(void) {
|
||||||
close = true;
|
close = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void window_loop(void) {
|
|
||||||
assert(win);
|
|
||||||
|
|
||||||
while (!close) {
|
|
||||||
game_update(input_getdat());
|
|
||||||
render_update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void window_free(void) {
|
|
||||||
assert(win);
|
|
||||||
render_free();
|
|
||||||
SDL_DestroyWindow(win);
|
|
||||||
win = NULL;
|
|
||||||
close = false;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,5 @@
|
|||||||
#define SCREEN_WIDTH ((COLUMNS + 6) * PX_DENS) // window width
|
#define SCREEN_WIDTH ((COLUMNS + 6) * PX_DENS) // window width
|
||||||
#define SCREEN_HEIGHT ((COLUMNS) * PX_DENS / COLUMNS * ROWS) // window height
|
#define SCREEN_HEIGHT ((COLUMNS) * PX_DENS / COLUMNS * ROWS) // window height
|
||||||
|
|
||||||
void window_init(void);
|
void window_open(struct gamedata const*);
|
||||||
void window_close(void);
|
void window_close(void);
|
||||||
void window_loop(void);
|
|
||||||
void window_free(void);
|
|
||||||
|
|||||||
57
src/main.c
57
src/main.c
@@ -1,62 +1,23 @@
|
|||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <SDL_error.h>
|
#include <stdlib.h>
|
||||||
#include <SDL_events.h>
|
|
||||||
#include <SDL_ttf.h>
|
|
||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "game/game.h"
|
#include "game/game.h"
|
||||||
#include "game/paths.h"
|
#include "io/window.h"
|
||||||
#include "io/render.h"
|
|
||||||
|
|
||||||
// initialized in init(), reading beforehand is undefined behaviour
|
static void stop(void) {
|
||||||
static gamedata gdat;
|
debug("stopping...", );
|
||||||
static renderdata rdat;
|
window_close();
|
||||||
|
SDL_Quit();
|
||||||
// initialize the game
|
|
||||||
static void init(void) {
|
|
||||||
// initialize SDL
|
|
||||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) fatal(ERROR_SDL_INIT, "SDL could not initialize! SDL Error: %s", SDL_GetError());
|
|
||||||
if (TTF_Init() != 0) fatal(ERROR_SDL_FONT_INIT, "the TTF module of SDL could not initialize! TTF Error: %s", TTF_GetError());
|
|
||||||
|
|
||||||
// initialize other game components
|
|
||||||
paths_init();
|
|
||||||
game_init(&gdat);
|
|
||||||
render_init(&rdat, &gdat);
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform the updates to the game
|
|
||||||
static void update(void) {
|
|
||||||
// update the input
|
|
||||||
{
|
|
||||||
SDL_Event e;
|
|
||||||
while (SDL_PollEvent(&e)) {
|
|
||||||
switch (e.type) {
|
|
||||||
case SDL_QUIT:
|
|
||||||
gdat.run = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform updates
|
|
||||||
game_update(&gdat);
|
|
||||||
render_update(&rdat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// entry-point of the application
|
// entry-point of the application
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
(void)argc, (void)argv;
|
(void)argc, (void)argv;
|
||||||
|
// register stop as exit function
|
||||||
|
atexit(stop);
|
||||||
|
|
||||||
init();
|
window_open(game_init());
|
||||||
debug("successfully initialized!", );
|
|
||||||
|
|
||||||
while (gdat.run == true)
|
|
||||||
update();
|
|
||||||
|
|
||||||
debug("done! starting to free resources...", );
|
|
||||||
game_free(&gdat);
|
|
||||||
render_free(&rdat);
|
|
||||||
paths_free();
|
|
||||||
SDL_Quit();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user