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:
2025-06-25 15:51:27 +02:00
parent a202442853
commit 937ecedfe3
12 changed files with 252 additions and 550 deletions

View File

@@ -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 FisherYates 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 FisherYates 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(&gt.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) {
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) { void game_free(void) {
audio_wav_unload(&dat->music);
audio_wav_unload(&dat->place_sfx);
audio_device_free();
// zero-out the rest of the data
*dat = (gamedata){0};
} }

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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__)
return 1; #pragma GCC unroll COLUMNS
#endif
for (int i = 0; i < COLUMNS; i++) res |= !row[i];
return !res;
} }
static void clear_rows(row* const rows, uint16_t* const score) { /* checks for filled rows, clearing a max of 4 consecutive rows, if present.
row cache[4] = {0}; // you can only clear four rows at a time * all row pointers will be moved down to fill the gap, the filled rows will be inserted at the top, and zeroed-out */
unsigned filled = 0; static void clear_rows(u8* restrict* restrict rows, u16* const score) {
unsigned checked = 0; 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;
// loop through each row (excluding the empty rows at the top when clearing a line) for (uint y = 0; y < (ROWS - fillc); y++) {
for (unsigned y = 0; y < (ROWS - filled); y++) { int i = (ROWS - 1) - y; // invert the index direction, so we start at the highest index
int const i = (ROWS - 1) - y; // get the index starting from the bottom rows[i] = rows[i - fillc]; // either assigns the current row to itself, or to the "next" row
rows[i] = rows[i - filled]; // set the row to the new or same address if (checkc >= 4 || !is_filled(rows[i])) {
checkc += (fillc > 0 && checkc < 4);
// 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;
}
}
} }

View File

@@ -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

View File

@@ -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
cache->score_surface = txt_surface;
cache->score_texture = txt_texture;
} }
if (cache->score_surface == NULL || cache->score_texture == NULL) { assert(score_surface && score_texture);
error("the score texture was unavailable!", ); SDL_Rect text_rect = {get_column_pos(COLUMNS + 1), get_row_pos(0), score_surface->w, score_surface->h};
return; SDL_RenderCopy(rend, score_texture, NULL, &text_rect);
}
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(renderdata* const render_data) { void render_free(void) {
SDL_DestroyRenderer(render_data->renderer); assert(rend);
SDL_DestroyWindow(render_data->window); SDL_DestroyRenderer(rend);
TTF_CloseFont(render_data->font); rend = NULL;
SDL_FreeSurface(render_data->cache->score_surface);
SDL_DestroyTexture(render_data->cache->score_texture); TTF_CloseFont(font);
free(render_data->cache); SDL_FreeSurface(score_surface);
*render_data = (renderdata){0}; SDL_DestroyTexture(score_texture);
font = NULL;
score_surface = NULL;
score_texture = NULL;
} }

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
} }