Compare commits

...

10 Commits

Author SHA1 Message Date
fef5885293 full removal of SDL 2025-09-10 11:59:38 +02:00
2309bd86eb add GLAD "library" 2025-09-10 11:38:02 +02:00
2e36c53d4e rewrite makefile
dropping `SDL2` requirement, opting for `glfw3` instead.
2025-09-10 11:38:02 +02:00
baf680ee3c differentiate between out-of-bounds intersection and regular intersection. 2025-09-10 11:38:02 +02:00
a827c84223 add a tad more documentation about how we encode our data. 2025-09-10 11:38:02 +02:00
f8eb814a4a fix: stored I tetrominos read as empty. 2025-09-10 11:38:02 +02:00
42b9e9b10c fix: add out of bounds checks to tetris_intersect 2025-09-10 11:38:02 +02:00
7ccc679d54 fix: rotation mask incorrect 2025-09-10 11:38:02 +02:00
e9226e4a39 rework shape handelling, placement and play field management 2025-09-10 11:38:02 +02:00
d471c42388 rework error script 2025-09-10 11:38:02 +02:00
33 changed files with 22342 additions and 1169 deletions

View File

@@ -0,0 +1,311 @@
#ifndef __khrplatform_h_
#define __khrplatform_h_
/*
** Copyright (c) 2008-2018 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* Khronos platform-specific types and definitions.
*
* The master copy of khrplatform.h is maintained in the Khronos EGL
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
* The last semantic modification to khrplatform.h was at commit ID:
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
*
* Adopters may modify this file to suit their platform. Adopters are
* encouraged to submit platform specific modifications to the Khronos
* group so that they can be included in future versions of this file.
* Please submit changes by filing pull requests or issues on
* the EGL Registry repository linked above.
*
*
* See the Implementer's Guidelines for information about where this file
* should be located on your system and for more details of its use:
* http://www.khronos.org/registry/implementers_guide.pdf
*
* This file should be included as
* #include <KHR/khrplatform.h>
* by Khronos client API header files that use its types and defines.
*
* The types in khrplatform.h should only be used to define API-specific types.
*
* Types defined in khrplatform.h:
* khronos_int8_t signed 8 bit
* khronos_uint8_t unsigned 8 bit
* khronos_int16_t signed 16 bit
* khronos_uint16_t unsigned 16 bit
* khronos_int32_t signed 32 bit
* khronos_uint32_t unsigned 32 bit
* khronos_int64_t signed 64 bit
* khronos_uint64_t unsigned 64 bit
* khronos_intptr_t signed same number of bits as a pointer
* khronos_uintptr_t unsigned same number of bits as a pointer
* khronos_ssize_t signed size
* khronos_usize_t unsigned size
* khronos_float_t signed 32 bit floating point
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
* nanoseconds
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
* khronos_boolean_enum_t enumerated boolean type. This should
* only be used as a base type when a client API's boolean type is
* an enum. Client APIs which use an integer or other type for
* booleans cannot use this as the base type for their boolean.
*
* Tokens defined in khrplatform.h:
*
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
*
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
*
* Calling convention macros defined in this file:
* KHRONOS_APICALL
* KHRONOS_APIENTRY
* KHRONOS_APIATTRIBUTES
*
* These may be used in function prototypes as:
*
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
* int arg1,
* int arg2) KHRONOS_APIATTRIBUTES;
*/
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
# define KHRONOS_STATIC 1
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APICALL
*-------------------------------------------------------------------------
* This precedes the return type of the function in the function prototype.
*/
#if defined(KHRONOS_STATIC)
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
* header compatible with static linking. */
# define KHRONOS_APICALL
#elif defined(_WIN32)
# define KHRONOS_APICALL __declspec(dllimport)
#elif defined (__SYMBIAN32__)
# define KHRONOS_APICALL IMPORT_C
#elif defined(__ANDROID__)
# define KHRONOS_APICALL __attribute__((visibility("default")))
#else
# define KHRONOS_APICALL
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIENTRY
*-------------------------------------------------------------------------
* This follows the return type of the function and precedes the function
* name in the function prototype.
*/
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
/* Win32 but not WinCE */
# define KHRONOS_APIENTRY __stdcall
#else
# define KHRONOS_APIENTRY
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIATTRIBUTES
*-------------------------------------------------------------------------
* This follows the closing parenthesis of the function prototype arguments.
*/
#if defined (__ARMCC_2__)
#define KHRONOS_APIATTRIBUTES __softfp
#else
#define KHRONOS_APIATTRIBUTES
#endif
/*-------------------------------------------------------------------------
* basic type definitions
*-----------------------------------------------------------------------*/
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
* Using <stdint.h>
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
/*
* To support platform where unsigned long cannot be used interchangeably with
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
* unsigned long long or similar (this results in different C++ name mangling).
* To avoid changes for existing platforms, we restrict usage of intptr_t to
* platforms where the size of a pointer is larger than the size of long.
*/
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
#define KHRONOS_USE_INTPTR_T
#endif
#endif
#elif defined(__VMS ) || defined(__sgi)
/*
* Using <inttypes.h>
*/
#include <inttypes.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
/*
* Win32
*/
typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
/*
* Sun or Digital
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t;
#else
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
/*
* Hypothetical platform with no float or int64 support
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0
#else
/*
* Generic fallback
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
/*
* Types that are (so far) the same on all platforms
*/
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
/*
* Types that differ between LLP64 and LP64 architectures - in LLP64,
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
* to be the only LLP64 architecture in current use.
*/
#ifdef KHRONOS_USE_INTPTR_T
typedef intptr_t khronos_intptr_t;
typedef uintptr_t khronos_uintptr_t;
#elif defined(_WIN64)
typedef signed long long int khronos_intptr_t;
typedef unsigned long long int khronos_uintptr_t;
#else
typedef signed long int khronos_intptr_t;
typedef unsigned long int khronos_uintptr_t;
#endif
#if defined(_WIN64)
typedef signed long long int khronos_ssize_t;
typedef unsigned long long int khronos_usize_t;
#else
typedef signed long int khronos_ssize_t;
typedef unsigned long int khronos_usize_t;
#endif
#if KHRONOS_SUPPORT_FLOAT
/*
* Float type
*/
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
/* Time types
*
* These types can be used to represent a time interval in nanoseconds or
* an absolute Unadjusted System Time. Unadjusted System Time is the number
* of nanoseconds since some arbitrary system event (e.g. since the last
* time the system booted). The Unadjusted System Time is an unsigned
* 64 bit value that wraps back to 0 every 584 years. Time intervals
* may be either signed or unsigned.
*/
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
/*
* Dummy value used to pad enum types to 32 bits.
*/
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
/*
* Enumerated boolean type
*
* Values other than zero should be considered to be true. Therefore
* comparisons should not be made against KHRONOS_TRUE.
*/
typedef enum {
KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
#endif /* __khrplatform_h_ */

13568
lib/glad/include/glad/gl.h Normal file

File diff suppressed because one or more lines are too long

8012
lib/glad/src/gl.c Normal file

File diff suppressed because it is too large Load Diff

159
makefile
View File

@@ -1,115 +1,86 @@
# dependencies:
# - make
# - clang
# - bear (debug)
# - sdl2
# - git bash (windows)
#
# project name = the workspace directory name
NAME := $(shell basename $(PWD))
DEBUG ?= 0
ARCH ?= 0
SHELL = bash
.SHELLFLAGS = -O globstar -c
NAME = tetris
VERSION = 0.0.3
DEBUG ?= 0
CC ?= cc
LD ?= ld
MARCH ?= $(shell uname -m)
KERNEL ?= $(shell uname -s)
# compiler settings
CC := clang
STD := c17
CFLAGS := -m32 -Wall -Wextra -Wpedantic -Wno-pointer-arith
LDFLAGS := -m32 -lm
CFLAGS += -c -std=gnu99 -Wall -Wextra -Wpedantic -MMD -MP
LDFLAGS += -flto
ifneq ($(DEBUG),0)
CFLAGS += -g -Og -fsanitize=address,undefined
LDFLAGS += -fsanitize=address,undefined
PROF := dbg
ifeq ($(KERNEL),)
ISWIN := $(if $(filter $(OS),Windows_NT),1,0)
ifeq ($(ISWIN),1)
KERNEL = mingw
MARCH = x86_64
else
CFLAGS += -DNDEBUG -O2 -Werror
PROF := rel
MARCH := $(shell uname -m)
KERNEL := $(shell uname -s | tr '[:upper:]' '[:lower:]')
endif
ifneq ($(MAKECMDGOALS),clean)
ifeq ($(ARCH),linux-x86)
CFLAGS += -target x86_64-pc-linux-gnu $(shell pkg-config --cflags sdl2 SDL2_ttf)
LDFLAGS += -target x86_64-pc-linux-gnu $(shell pkg-config --libs sdl2 SDL2_ttf)
else ifeq ($(ARCH),win-x86)
CFLAGS += -target x86_64-pc-windows-gnu
LDFLAGS += -target x86_64-pc-windows-gnu -fuse-ld=lld
EXT := .exe
else
$(error you must set the ARCH environment variable to one of these: 'linux-x86' 'win-x86')
ISWIN := $(if $(filter $(KERNEL),mingw),1,0)
endif
ifeq ($(MARCH),)
$(error must also set MARCH when manually setting KERNEL)
endif
ifneq ($(ARCH),0)
# dirs
DIR_BIN := bin/$(ARCH)/$(PROF)
DIR_OBJ := obj/$(ARCH)/$(PROF)
TARGET := $(DIR_BIN)/$(NAME)$(EXT)
# source files
SRC := $(wildcard src/*.c) $(wildcard src/**/*.c) $(wildcard src/**/**/*.c) $(wildcard src/**/**/**/*.c) $(wildcard src/**/**/**/**/*.c)
OBJ := $(patsubst src/%,$(DIR_OBJ)/%,$(SRC:.c=.o))
DEP := $(OBJ:.o=.d)
SRC_ASSETS := $(wildcard assets/*)
ASSETS := $(patsubst assets/%,$(DIR_BIN)/%,$(SRC_ASSETS))
COMPILE_COMMANDS := $(DIR_OBJ)/compile_commands.json
ifeq ($(DEBUG),1)
PROF = dbg
CFLAGS += -UNDEBUG -Og -g -Wextra -Wpedantic
CFLAGS += $(if $(filter 1,$(ISWIN)),,-fsanitize=address,undefined) -ftrapv
LDFLAGS += $(if $(filter 1,$(ISWIN)),,-fsanitize=address,undefined) -ftrapv
else
PROF = rel
CFLAGS += -DNDEBUG -O3
endif
define wr_colour
@printf '\033[%sm%s\033[0m\n' $(2) $(1)
endef
CFLAGS += $(shell pkg-config --cflags glfw3) -Ilib/glad/include
LDFLAGS += $(shell pkg-config --libs glfw3) -lm
# compiles and execute the binary
SRC := $(shell echo src/**/*.c) lib/glad/src/gl.c
RES := $(shell echo res/**.glsl)
NAME += $(if $(filter 1,$(ISWIN)),.exe,)
DIR_BIN := bin/$(MARCH)-$(KERNEL)/$(VERSION)/$(PROF)
DIR_OBJ := obj/$(MARCH)-$(KERNEL)/$(VERSION)/$(PROF)
BIN := $(DIR_BIN)/$(NAME)
OBJ := $(SRC:%.c=$(DIR_OBJ)/%.o) $(RES:%=$(DIR_OBJ)/%.o)
DEP := $(OBJ:%.o=%.d)
.PHONY:
run: compile
cd $(dir $(TARGET)) && ./$(notdir $(TARGET))
compile: compile_commands $(TARGET) $(ASSETS)
$(BIN)
.NOTPARALLEL:
.PHONY:
compile: $(BIN)
.PHONY .NOTPARALLEL:
clean:
rm -rf obj/ bin/ compile_commands.json
@[ -d obj/ ] && rm -rv obj/ || true
@[ -d bin/ ] && rm -rv bin/ || true
# create the binary (linking step)
$(TARGET): $(OBJ)
@$(call wr_colour,"CC: '$(CC)'",94)
@$(call wr_colour,"CFLAGS: '$(CFLAGS)'",94)
@$(call wr_colour,"LDFLAGS: '$(LDFLAGS)'",94)
@$(call wr_colour,"linking to: '$@'",92)
@mkdir -p ${@D}
@$(CC) -o $(TARGET) $^ $(LDFLAGS)
@$(call wr_colour,"current profile: '$(PROF)'",93)
$(BIN): $(OBJ)
$(info [CC/LD] $@)
@mkdir -p $(@D)
@$(CC) -o $@ $^ $(LDFLAGS)
# create .o and .d files
$(DIR_OBJ)/%.o: src/%.c
@$(call wr_colour,"compiling $(notdir $@) from $(notdir $<)",92)
@mkdir -p ${@D}
@$(CC) $(CFLAGS) -c -MD -MP -std=$(STD) -x c -o $@ $<
$(DIR_OBJ)/%.o: %.c
$(info [CC] $@)
@mkdir -p $(@D)
@$(CC) $(CFLAGS) -o $@ $<
# copy assets
$(DIR_BIN)/%: assets/%
@mkdir -p ${@D}
@cp -v $< $@
$(DIR_OBJ)/res/%.o: res/%
$(info [LD] $@)
@mkdir -p $(@D)
@$(LD) -r -b binary -o $@ $<
# update compile commands if the makefile has been updated (for linting)
compile_commands: # default, empty rule
ifneq ($(shell which bear),)
ifneq ($(COMPILE_COMMANDS),)
ifeq ($(NOCMDS),)
.NOTPARALLEL .PHONY:
compile_commands: $(COMPILE_COMMANDS)
@[ "$(readlink compile_commands.json)" != "$<" ] && ln -sf $< compile_commands.json
.PHONY: x86_64-linux-gnu-gcc x86_64-w64-mingw32-gcc
x86_64-linux-gnu-gcc:; $(MAKE) $(CALL) $(MAKEFLAGS) CC=$@ MARCH=x86_64 KERNEL=linux
x86_64-w64-mingw32-gcc:; $(MAKE) $(CALL) $(MAKEFLAGS) CC=$@ MARCH=x86_64 KERNEL=mingw
.NOTPARALLEL:
$(COMPILE_COMMANDS): makefile
@$(call wr_colour,"regenerating compile_commands.json thus recompiling.",93)
@mkdir -p ${@D} # ensure the target directory exists
@touch $@ # create the file so it isn't retriggered (will just change modification time if already exists)
@bear --output $@ -- make -B compile NOCMDS=1 # rebuild the current target using bear, to create the compile commands
endif
endif
endif
# disable implicit rules
.SUFFIXES:
# include the dependencies
-include $(DEP)

1
res/sh.frag.glsl Normal file
View File

@@ -0,0 +1 @@
#version 330 core

1
res/sh.vert.glsl Normal file
View File

@@ -0,0 +1 @@
#version 330 core

View File

@@ -1,50 +0,0 @@
#pragma once
#include <stdint.h>
#include <stdnoreturn.h>
/* defines statuses in the 0..127, any higher/negative values are POSIX-reserved.
* The max value (or -1) shall mean the application is running, anything else shall mean an exit code of some kind */
enum gamestatus {
// clang-format off
STATUS_SUCCESS = 0, // 0; successful exit
STATUS_ERROR = 1, // miscellaneous error
ERROR_INIT = STATUS_ERROR | 2, // initialisation error
ERROR_STD = STATUS_ERROR | 64, // standard library error
ERROR_STD_INIT = ERROR_INIT | 64, // standard library initialisation error
ERROR_STD_MEMORY = ERROR_STD | 32, // memory error
ERROR_STD_MEMORY_INIT = ERROR_STD_INIT | 32, // memory initialization error
ERROR_SDL = STATUS_ERROR | 32, // SDL error
ERROR_SDL_INIT = ERROR_INIT | 32, // SDL initialization error
ERROR_SDL_RENDERING = ERROR_SDL | 16, // rendering error
ERROR_SDL_RENDERING_INIT = ERROR_SDL_INIT | 16, // rendering initialization error
ERROR_SDL_AUDIO = ERROR_SDL | 8, // audio error
ERROR_SDL_AUDIO_INIT = ERROR_SDL_INIT | 8, // audio initialization error
ERROR_SDL_FONT = ERROR_SDL | 4, // font error
ERROR_SDL_FONT_INIT = ERROR_SDL_INIT | 4, // font initialization error
STATUS_RUNNING = -1,
// clang-format on
};
#if __INCLUDE_LEVEL__ > 0
#include <SDL_messagebox.h>
#include <stdio.h>
#include <stdlib.h>
#include "util/macro.h"
#endif
#define debug(s, ...) printf("\033[95m" __FILE__ ":" MACRO_STR2(__LINE__) ": [DBG]: " s "\033[0m\n" __VA_OPT__(, __VA_ARGS__))
#define info(s, ...) printf(__FILE__ ":" MACRO_STR2(__LINE__) ": [INF]: " s "\n", __VA_OPT__(, __VA_ARGS__))
#define warn(s, ...) fprintf(stderr, "\033[93m" __FILE__ ":" MACRO_STR2(__LINE__) ": [WAR]: " s "\033[0m\n" __VA_OPT__(, __VA_ARGS__))
#define error(s, ...) fprintf(stderr, "\033[91m" __FILE__ ":" MACRO_STR2(__LINE__) ": [ERR]: " s "\033[0m\n" __VA_OPT__(, __VA_ARGS__))
#define fatal(c, s, ...) \
do { \
printf("\033[101m" __FILE__ ":" MACRO_STR2(__LINE__) ": [FAT]: " s "\033[0m\n" __VA_OPT__(, __VA_ARGS__)); \
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "something went wrong! :O", "view stderr for full details: \n" s, NULL); \
exit(c); \
} while (0)

View File

@@ -1,76 +0,0 @@
#include "game.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "../io/colour/colour8.h"
#include "../io/input.h"
#include "../io/window.h"
#include "../util/types.h"
#include "../util/vec.h"
#include "./tetromino/shapes.h"
#include "tetromino/placing.h"
#include "time.h"
static colour8 rowdat[COLUMNS * ROWS] = {0}; // contains the raw data of the rows, in no particular order
static struct gamedata dat = {0};
/* shuffle an array using the FisherYates shuffle algorithm.
* `nmemb` is the number of members.
* `membs` is the byte size of each member */
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, dat, membs);
}
}
void next_shape(void) {
// as long as we're not at the last shape, we can just increment
dat.pdat.sel = (i8vec2){COLUMNS / 2 - SHAPE_WIDTH / 2, 0};
dat.pdat.cur = dat.pdat.nxt[dat.pdat.idx];
dat.pdat.idx++;
if (dat.pdat.idx < TETC) return;
// shuffle all next shapes, preserving the last
dat.pdat.idx = 0;
shuffle(dat.pdat.nxt, TETC, sizeof(u8));
}
struct gamedata *game_init(void) {
srand(time(NULL));
// populate the data arrays
for (int i = 0; i < ROWS; i++)
dat.rows[i] = rowdat + i * COLUMNS;
for (int i = 0; i < TETC; i++)
dat.pdat.nxt[i] = i;
// initialise the placing data correctly
dat.pdat.sel = (i8vec2){COLUMNS / 2 - SHAPE_WIDTH / 2, 0};
shuffle(dat.pdat.nxt, TETC, sizeof(u8));
dat.pdat.cur = dat.pdat.nxt[dat.pdat.idx];
dat.pdat.idx++;
return &dat;
}
// called every time the game's state is updated
void game_update(int movdat, size_t time) {
static time_t drop_timeout = 0;
movdat |= MOVD & -!!time_poll(time, 200, &drop_timeout);
if (place_update(&dat, movdat))
window_close();
}
void game_free(void) {
}

View File

@@ -1,43 +0,0 @@
#pragma once
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "../io/colour/colour8.h"
#include "../util/types.h"
#include "../util/vec.h"
#include "tetromino/shapes.h"
// constants for pi(π) and tau(τ)
#define PI (M_PI) // π constant
#define TAU (M_PI * 2.0) // τ constant
#define PIf (M_PIf) // π 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
#define COLUMNS 10
#define ROWS 24
/* contains the placement data */
struct pdat {
u8 nxt[TETC]; // shuffled data representing the next shapes
i8vec2 sel; // position of the current shape
u8 idx; // the index of the current shape
u8 cur; // the current id of the shape
};
/* contains game data that's commonly shared */
struct gamedata {
struct pdat pdat;
colour8 *rows[ROWS];
u16 pnts;
};
/* increments to the next shape, shuffling the next shapes, if there isn't a next shape immediately after the current one. */
void next_shape(void);
struct gamedata *game_init(void);
void game_update(int movdat, size_t time);
void game_free(void);

View File

@@ -1,95 +0,0 @@
#include "placing.h"
#include <stdint.h>
#include <string.h>
#include <sys/cdefs.h>
#include "../../io/audio.h"
#include "../../io/input.h"
#include "../../util/types.h"
#include "../../util/vec.h"
#include "../game.h"
#include "shapes.h"
static int clear_rows(u8 *restrict *restrict rows) {
int count = 0;
u8 *cache[4]; /* the maximum amount of rows the user can clear at once is four */
for (int y = ROWS - 1; y >= 0; y--) {
int x = 0;
while (x < COLUMNS && rows[y][x] && count < 4) x++;
if (x >= COLUMNS) cache[count++] = rows[y];
else rows[y + count] = rows[y];
}
if (count) {
for (int i = 0; i < count; i++) {
memset(cache[i], 0, COLUMNS);
rows[i] = cache[i];
}
}
return count;
}
/* writes a shape to the screen */
static void plcmnt_place(u8 *restrict const *restrict row, u8 id, i8vec2 pos) {
u8 colour = colour_from_id(id);
i8vec2 bpos[4];
shape_getblocks(id, bpos);
bpos[0] += pos;
bpos[1] += pos;
bpos[2] += pos;
bpos[3] += pos;
row[bpos[0][VY]][bpos[0][VX]] = colour;
row[bpos[1][VY]][bpos[1][VX]] = colour;
row[bpos[2][VY]][bpos[2][VX]] = colour;
row[bpos[3][VY]][bpos[3][VX]] = colour;
}
static int plcmnt_valid(u8 *restrict const *restrict const rows, i8vec2 pos) {
return pos[VX] >= 0 && pos[VX] < COLUMNS &&
pos[VY] >= 0 && pos[VY] < ROWS &&
!rows[pos[VY]][pos[VX]];
}
static int plcmnt_intersect(u8 *restrict const *restrict const rows, const u8 id, i8vec2 pos) {
i8vec2 bpos[4];
shape_getblocks(id, bpos);
return !(plcmnt_valid(rows, pos + bpos[0]) &&
plcmnt_valid(rows, pos + bpos[1]) &&
plcmnt_valid(rows, pos + bpos[2]) &&
plcmnt_valid(rows, pos + bpos[3]));
}
int place_update(struct gamedata *gdat, int movdat) {
// store the current index and ID, only changes when placed (which yields no movement) and rotation (which occurs last)
int tmp;
u8 id = gdat->pdat.cur;
// update Y axis
tmp = !!(movdat & MOVD);
gdat->pdat.sel[VY] += tmp;
tmp = tmp && plcmnt_intersect(gdat->rows, id, gdat->pdat.sel);
if (tmp) {
gdat->pdat.sel[VY]--;
plcmnt_place(gdat->rows, id, gdat->pdat.sel);
gdat->pnts += clear_rows(gdat->rows) << 4; // clear the rows that have been completed
next_shape();
audio_play(AUDIO_ID_PLACE);
if (plcmnt_intersect(gdat->rows, gdat->pdat.cur, gdat->pdat.sel))
return 1;
}
// update X axis
tmp = !!(movdat & MOVR) - !!(movdat & MOVL);
gdat->pdat.sel[VX] += (tmp && !plcmnt_intersect(gdat->rows, id, (i8vec2){gdat->pdat.sel[VX] + tmp, gdat->pdat.sel[VY]})) * tmp;
// update roll
tmp = id ^ (((!!(movdat & MOVRR) - !!(movdat & MOVRL)) * 8 + id) & 31);
gdat->pdat.cur ^= (tmp && !plcmnt_intersect(gdat->rows, id ^ tmp, gdat->pdat.sel)) * tmp;
return 0;
}

View File

@@ -1,10 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "../game.h"
/* updates the movement of the `pdat` structure, updating the rows when colliding downwards.
* returns `0` if we successfully updated. Returns 1 if we couldn't update. (e.g. when a next block immediately collides) */
int place_update(struct gamedata *gdat, int movdat);

View File

@@ -1,83 +0,0 @@
#include "shapes.h"
#include "../../io/colour/colour8.h"
#include "../../util/types.h"
#include "../../util/vec.h"
#include "../../error.h"
void shape_getblocks(u8 id, i8vec2 *restrict out) {
struct blockdat {
u8 ax : 2, ay : 2;
u8 bx : 2, by : 2;
u8 cx : 2, cy : 2;
u8 dx : 2, dy : 2;
} dat;
switch (id) {
// O tetromino
case TET_O | TET_R0:
case TET_O | TET_R90:
case TET_O | TET_R180:
case TET_O | TET_R270: dat = (struct blockdat){1, 1, 2, 1, 1, 2, 2, 2}; break;
// I tetromino
case TET_I | TET_R0: dat = (struct blockdat){0, 1, 1, 1, 2, 1, 3, 1}; break;
case TET_I | TET_R90: dat = (struct blockdat){2, 0, 2, 1, 2, 2, 2, 3}; break;
case TET_I | TET_R180: dat = (struct blockdat){0, 2, 1, 2, 2, 2, 3, 2}; break;
case TET_I | TET_R270: dat = (struct blockdat){1, 0, 1, 1, 1, 2, 1, 3}; break;
// S tetromino
case TET_S | TET_R0: dat = (struct blockdat){1, 0, 2, 0, 0, 1, 1, 1}; break;
case TET_S | TET_R90: dat = (struct blockdat){1, 0, 1, 1, 2, 1, 2, 2}; break;
case TET_S | TET_R180: dat = (struct blockdat){1, 1, 2, 1, 0, 2, 1, 2}; break;
case TET_S | TET_R270: dat = (struct blockdat){0, 0, 0, 1, 1, 1, 1, 2}; break;
// Z tetromino
case TET_Z | TET_R0: dat = (struct blockdat){0, 0, 1, 0, 1, 1, 2, 1}; break;
case TET_Z | TET_R90: dat = (struct blockdat){2, 0, 1, 1, 2, 1, 1, 2}; break;
case TET_Z | TET_R180: dat = (struct blockdat){0, 1, 1, 1, 1, 2, 2, 2}; break;
case TET_Z | TET_R270: dat = (struct blockdat){1, 0, 0, 1, 1, 1, 0, 2}; break;
// T tetromino
case TET_T | TET_R0: dat = (struct blockdat){0, 1, 1, 1, 2, 1, 1, 2}; break;
case TET_T | TET_R90: dat = (struct blockdat){1, 0, 0, 1, 1, 1, 1, 2}; break;
case TET_T | TET_R180: dat = (struct blockdat){1, 0, 0, 1, 1, 1, 2, 1}; break;
case TET_T | TET_R270: dat = (struct blockdat){1, 0, 1, 1, 2, 1, 1, 2}; break;
// L tetromino
case TET_L | TET_R0: dat = (struct blockdat){1, 0, 1, 1, 1, 2, 2, 2}; break;
case TET_L | TET_R90: dat = (struct blockdat){0, 1, 1, 1, 2, 1, 0, 2}; break;
case TET_L | TET_R180: dat = (struct blockdat){0, 0, 1, 0, 1, 1, 1, 2}; break;
case TET_L | TET_R270: dat = (struct blockdat){2, 0, 0, 1, 1, 1, 2, 1}; break;
// J tetromino
case TET_J | TET_R0: dat = (struct blockdat){1, 0, 1, 1, 0, 2, 1, 2}; break;
case TET_J | TET_R90: dat = (struct blockdat){0, 0, 0, 1, 1, 1, 2, 1}; break;
case TET_J | TET_R180: dat = (struct blockdat){1, 0, 2, 0, 1, 1, 1, 2}; break;
case TET_J | TET_R270: dat = (struct blockdat){0, 1, 1, 1, 2, 1, 2, 2}; break;
default:
#ifndef NDEBUG
fatal(1, "something went wrong; couldn't reconise the ID.", );
#endif
break;
}
out[0] = (i8vec2){dat.ax, dat.ay};
out[1] = (i8vec2){dat.bx, dat.by};
out[2] = (i8vec2){dat.cx, dat.cy};
out[3] = (i8vec2){dat.dx, dat.dy};
}
colour8 colour_from_id(u8 id) {
switch (id & 7) {
case TET_O: return COLOUR8_YELLOW;
case TET_I: return COLOUR8_CYAN;
case TET_S: return COLOUR8_GREEN;
case TET_Z: return COLOUR8_RED;
case TET_T: return COLOUR8_MAGENTA;
case TET_L: return COLOUR8_ORANGE;
case TET_J: return COLOUR8_BLUE;
default: return COLOUR8_BLACK;
}
}

View File

@@ -1,28 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "../../io/colour/colour8.h"
#include "../../util/types.h"
#include "../../util/vec.h"
enum tetromino {
TET_O = 0,
TET_I = 1,
TET_S = 2,
TET_Z = 3,
TET_T = 4,
TET_L = 5,
TET_J = 6,
TET_R0 = 0,
TET_R90 = 8,
TET_R180 = 16,
TET_R270 = 24,
};
#define SHAPE_WIDTH 4
#define SHAPE_HEIGHT 4
#define TETC 7
void shape_getblocks(u8 id, i8vec2 *out);
colour8 colour_from_id(u8 id);

View File

@@ -1,39 +0,0 @@
#include "time.h"
#include <stdbool.h>
#include <time.h>
#if __has_include(<features.h>)
#include <features.h>
#endif
#if __has_include(<features.h>) && _POSIX_C_SOURCE >= 199309L
#include <bits/time.h>
static void gettime(struct timespec *ts) {
clock_gettime(CLOCK_MONOTONIC, ts);
}
#elif defined(_WIN32)
#include <profileapi.h>
#include <windows.h>
#include <winnt.h>
static void gettime(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);
}
#else
#error no implementation of a monotonic clock was available
#endif
time_t time_pull(void) {
struct timespec ts;
gettime(&ts);
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
int time_poll(time_t curr, time_t delta, time_t *restrict proj) {
bool tpass = curr >= *proj;
*proj += tpass * ((curr + delta) - *proj); // adds 0, or the difference to proj
return tpass;
}

View File

@@ -1,12 +0,0 @@
#pragma once
#include <sys/cdefs.h>
#include <time.h>
/* gets the current time in milliseconds */
time_t time_pull(void);
/* Polls the time whether a given timeout has passed, comparing against `curr` as the current time.
* if `curr` ≥ `*proj`, `curr` + `delta` is written to `*proj`. `1` is returned.
* otherwise, we just return `0`. */
__nonnull((3)) int time_poll(time_t curr, time_t delta, time_t *restrict proj);

View File

@@ -1,150 +0,0 @@
#include "audio.h"
#include <SDL_audio.h>
#include <SDL_error.h>
#include <SDL_stdinc.h>
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "../error.h"
#include "../util/compat.h"
#include "../util/types.h"
struct audioplayer {
const u8 *buf;
int len;
};
static struct audiodevice {
struct audioplayer audio[AUDIO_MAX];
u32 id;
SDL_AudioSpec spec;
} dev;
struct audiodata audio_dat[AUDIO_ID_COUNT] = {0}; // contains pointers to audio buffers.
static const char *const audio_path[AUDIO_ID_COUNT] = {
"korobeiniki.wav",
"place.wav",
};
/* mixes the audio output stream, using the different audio as sources */
static void audiomixer(void *const userdata, u8 *const stream, const int len) {
(void)userdata;
memset(stream, 0, len); // clear the playing audio
// update the counts for the audio array
for (unsigned i = 0; i < AUDIO_MAX; i++) {
if (dev.audio[i].len > 0) {
unsigned mixlen = SDL_min(len, dev.audio[i].len);
SDL_MixAudioFormat(stream, dev.audio[i].buf, dev.spec.format, mixlen, SDL_MIX_MAXVOLUME);
dev.audio[i].len -= mixlen;
dev.audio[i].buf += mixlen;
}
}
}
/* converts input audio buffer to one that matches the buffer of dev.
* `len` is a pointer to the current size, the new size will be written to this location.
* returns the pointer to the audio buffer to use, or NULL, when something went wrong.
* NULL will never be returned after the conversion */
static u8 *audio_cvt(const SDL_AudioSpec *spec, u8 *bufptr, unsigned *len) {
if (!bufptr) return NULL;
// init the converter
SDL_AudioCVT cvt;
if (SDL_BuildAudioCVT(&cvt, spec->format, spec->channels, spec->freq, dev.spec.format, dev.spec.channels, dev.spec.freq) < 0) {
error("could not build the audio converter! SDL Error: %s", SDL_GetError());
return NULL;
}
if (!cvt.needed) return bufptr; // ensure the conversion is necessary
// grow the inputted buffer for the conversion
cvt.len = *len;
cvt.buf = realloc(bufptr, cvt.len * cvt.len_mult);
if (!cvt.buf) {
warn("failed to grow the audio buffer to the new size of %u bytes! audio may be bugged", cvt.len);
return bufptr;
}
// convert the audio to the new format
if (SDL_ConvertAudio(&cvt) < 0) {
warn("couldn't convert an audio buffer to the set format, audio may be bugged! SDL Error: %s", SDL_GetError());
assert(cvt.buf);
return cvt.buf;
}
assert(cvt.buf);
// shrink to reduce memory footprint
*len = cvt.len_cvt;
bufptr = realloc(cvt.buf, *len);
return bufptr ? bufptr : cvt.buf;
}
/* computes the time in milliseconds of the audio fragment by dividing
* the audio byte length by the format's bit size, which is divided by
* the audio device's channels and frequency */
static inline u32 audio_btoms(u32 len) {
return (((1000 * len) / (SDL_AUDIO_BITSIZE(dev.spec.format) / 8)) / dev.spec.channels / dev.spec.freq);
}
/* loads a `struct audiodata` from `fpat` to `out`. */
static void audio_wav_load(const char *restrict fpat, struct audiodata *restrict out) {
debug("loading audio file '%s'...", fpat);
if (faccess(fpat, FA_R)) {
error("audio file either isn't readable or doesn't exist. path: '%s'!", fpat);
return;
}
// load the audio
u32 len;
u8 *ptr, *tmp;
SDL_AudioSpec spec;
SDL_LoadWAV(fpat, &spec, &ptr, &len);
// convert the audio data to the format reflecting dev
tmp = audio_cvt(&spec, ptr, &len);
if (!tmp) free(ptr); // free the buffer if NULL was returned; failure
*out = (struct audiodata){tmp, len, audio_btoms(len)};
}
/* loads the audio data into the buffer */
static inline void audio_load(void) {
for (size_t i = 0; i < AUDIO_ID_COUNT; i++)
audio_wav_load(audio_path[i], &audio_dat[i]);
}
void audio_play(u32 audio_id) {
if (!dev.id) return;
size_t i = 0;
while (i < AUDIO_MAX && dev.audio[i].len > 0) i++;
if (i >= AUDIO_MAX) return;
dev.audio[i] = (struct audioplayer){
audio_dat[audio_id].buf,
audio_dat[audio_id].len,
};
}
int audio_init(int freq, SDL_AudioFormat fmt, u8 ch, u16 samples) {
// initialise the audio device + specification
SDL_AudioSpec spec = {freq, fmt, ch, 0, samples, 0, 0, audiomixer, NULL};
unsigned id = SDL_OpenAudioDevice(NULL, 0, &spec, &spec, 0);
dev = (struct audiodevice){{0}, id, spec};
if (!id) return 1;
SDL_PauseAudioDevice(id, 0);
audio_load();
return 0;
}
void audio_free(void) {
SDL_CloseAudioDevice(dev.id);
dev = (struct audiodevice){0};
for (size_t i = 0; i < AUDIO_ID_COUNT; i++)
free((void *)audio_dat[i].buf);
}

View File

@@ -1,33 +0,0 @@
#pragma once
#include <SDL_audio.h>
#include <stdint.h>
#include "../util/types.h"
#define AUDIO_MAX 4 // maximum number of sound effects that are allowed to play at once
struct audiodata {
const u8 *buf; // pointer to the audio buffer
u32 len; // length in bytes of the audio buffer
u32 ms; // length in miliseconds of the audio buffer
};
enum audio_id {
AUDIO_ID_MUSIC,
AUDIO_ID_PLACE,
// leave at end, will contain count
AUDIO_ID_COUNT,
};
extern struct audiodata audio_dat[AUDIO_ID_COUNT];
/* loads the audio to be played, unless `AUDIO_MAX` has been reached, then this call will fail */
void audio_play(u32);
/* initialises the audio device, then loads the audio data */
int audio_init(int freq, SDL_AudioFormat fmt, u8 ch, u16 samples);
/* frees up resources held by the audio device and audio buffers */
void audio_free(void);

View File

@@ -1,42 +0,0 @@
#pragma once
#include <stdint.h>
#include "SDL_render.h"
// stores colour in a rgba format, each channel being a 8 bits wide.
typedef union {
uint32_t packed;
struct {
uint8_t a;
uint8_t b;
uint8_t g;
uint8_t r;
};
} colour32;
#define COLOUR32_BLACK ((colour32){0x000000FF})
#define COLOUR32_RED ((colour32){0xFF0000FF})
#define COLOUR32_YELLOW ((colour32){0xFFFF00FF})
#define COLOUR32_ORANGE ((colour32){0xFF6D00FF})
#define COLOUR32_GREEN ((colour32){0x00FF00FF})
#define COLOUR32_CYAN ((colour32){0x00FFFFFF})
#define COLOUR32_BLUE ((colour32){0x0000FFFF})
#define COLOUR32_MAGENTA ((colour32){0xFF00FFFF})
#define COLOUR32_WHITE ((colour32){0xFFFFFFFF})
// sets the render colour to a colour32 value
static inline void set_colour32(SDL_Renderer *const renderer, const colour32 c) {
(void)SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
}
// american macros:
#define color32 colour32
#define COLOR32_BLACK COLOUR32_BLACK
#define COLOR32_RED COLOUR32_RED
#define COLOR32_YELLOW COLOUR32_YELLOW
#define COLOR32_ORANGE COLOUR32_ORANGE
#define COLOR32_GREEN COLOUR32_GREEN
#define COLOR32_CYAN COLOUR32_CYAN
#define COLOR32_BLUE COLOUR32_BLUE
#define COLOR32_MAGENTA COLOUR32_MAGENTA
#define COLOR32_WHITE COLOUR32_WHITE

View File

@@ -1,53 +0,0 @@
#pragma once
#include <stdint.h>
#include "SDL_render.h"
// stores colour in a rrrgggbb format, which maps exactly to 8 bits
typedef uint8_t colour8;
/* rrrg ggbb */
#define COLOUR8_BLACK ((colour8)0x00) // 0000 0000
#define COLOUR8_RED ((colour8)0xE0) // 1110 0000
#define COLOUR8_YELLOW ((colour8)0xFC) // 1111 1100
#define COLOUR8_ORANGE ((colour8)0xEC) // 1111 1100
#define COLOUR8_GREEN ((colour8)0x1C) // 0001 1100
#define COLOUR8_CYAN ((colour8)0x1F) // 0001 1111
#define COLOUR8_BLUE ((colour8)0x03) // 0000 0011
#define COLOUR8_MAGENTA ((colour8)0xE3) // 1110 0011
#define COLOUR8_WHITE ((colour8)0xFF) // 1111 1111
// gets the red channel in 32 bit colour space
static inline uint8_t colour8_red32(const colour8 colour) {
return (colour >> 5) * (255 / 7);
}
// gets the green channel in 32 bit colour space
static inline uint8_t colour8_green32(const colour8 colour) {
return ((colour >> 2) & 7) * (255 / 7);
}
// gets the blue channel in 32 bit colour space
static inline uint8_t colour8_blue32(const colour8 colour) {
return (colour & 3) * (255 / 3);
}
// sets the render colour to a colour8 value
static inline void set_colour8(SDL_Renderer *const renderer, const colour8 c) {
(void)SDL_SetRenderDrawColor(renderer, colour8_red32(c), colour8_green32(c), colour8_blue32(c), 0xFF);
}
// american macros:
#define color8 colour8
#define color8_red32 colour8_red32
#define color8_green32 colour8_green32
#define color8_blue32 colour8_blue32
#define COLOR8_BLACK COLOUR8_BLACK
#define COLOR8_RED COLOUR8_RED
#define COLOR8_YELLOW COLOUR8_YELLOW
#define COLOR8_ORANGE COLOUR8_ORANGE
#define COLOR8_GREEN COLOUR8_GREEN
#define COLOR8_CYAN COLOUR8_CYAN
#define COLOR8_BLUE COLOUR8_BLUE
#define COLOR8_MAGENTA COLOUR8_MAGENTA
#define COLOR8_WHITE COLOUR8_WHITE

View File

@@ -1,82 +1,8 @@
#include "input.h"
#include <SDL_events.h>
#include <SDL_scancode.h>
#include <time.h>
#include "../game/time.h"
#include "../util/types.h"
#include "window.h"
/* processes an incoming scancode, returns the associated movement data, or performs the close action directly
* NOTE: if the action is mapped to multiple keys, pressing both and then releasing one, disables the action. Minor issue, won't fix. */
__attribute__((const)) static int procscancode(SDL_Scancode code) {
switch (code) {
case SDL_SCANCODE_Q:
return MOVRL;
case SDL_SCANCODE_E:
return MOVRR;
case SDL_SCANCODE_LEFT:
case SDL_SCANCODE_A:
case SDL_SCANCODE_H:
return MOVL;
case SDL_SCANCODE_RIGHT:
case SDL_SCANCODE_D:
case SDL_SCANCODE_L:
return MOVR;
case SDL_SCANCODE_DOWN:
case SDL_SCANCODE_S:
case SDL_SCANCODE_J:
case SDL_SCANCODE_SPACE:
return MOVD;
case SDL_SCANCODE_ESCAPE:
window_close();
return 0;
default: return 0;
}
}
static int timeout_mask(time_t time) {
static time_t timeout = 0, timeout_roll = 0;
int msk = 0;
// only add to the mask if time_poll returns `1`, negating becomes `-1`; 0b1111...
// this is masked with the desired movement action.
msk |= ((MOVR | MOVL | MOVD) & -!!time_poll(time, 64, &timeout));
msk |= ((MOVRL | MOVRR) & -!!time_poll(time, 100, &timeout_roll));
return msk;
}
int input_getdat(time_t time) {
static u8 movdat = 0, nmovdat = 0, lmovdat = 0; // stores the static movement data
int mov = movdat, nmov = nmovdat, lmov = lmovdat; // stores the runtime movement data for easy register access
// process the events
SDL_Event e;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_QUIT: window_close(); break;
case SDL_KEYDOWN: mov |= procscancode(e.key.keysym.scancode); break;
case SDL_KEYUP: nmov |= procscancode(e.key.keysym.scancode); break;
}
}
// compute the current movement
int mask = timeout_mask(time);
// handle releasing of keys
mov &= ~(nmov & lmov & mask); // only remove the keys that have been pressed since lmov
lmov = (mov & mask) | (lmov & ~mask); // set the value of lmov to the new value mov
nmov &= mov; // set nmov to only those in mov
int cmov = mov & mask;
// write to static variables (shrinking the values, and memory usage)
movdat = mov;
lmovdat = lmov;
nmovdat = nmov;
return cmov;
#include <GLFW/glfw3.h>
void key_callback(GLFWwindow *win, int key, int scancode, int action, int mods) {
(void)win, (void)key, (void)scancode, (void)action, (void)mods;
#ifndef NDEBUG
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(win, 1);
#endif
}

View File

@@ -1,17 +1,5 @@
#pragma once
#include <time.h>
/* 8 bit enumeration storing the movement data */
enum movdat {
MOVL = 1, // move left
MOVR = 2, // move right
MOVD = 4, // move down
MOVRL = 8, // move roll left
MOVRR = 16, // move roll right
MOVPL = 32, // move place
};
/* returns an OR'd string from `enum movdat`, containing the movement data.
* assumes that SDL has been initialized and a window has successfully been created. */
__attribute__((__pure__)) int input_getdat(time_t time);
#ifndef INPUT_H
#define INPUT_H 1
#include <GLFW/glfw3.h>
void key_callback(GLFWwindow *win, int key, int scancode, int action, int mods);
#endif

View File

@@ -1,125 +1,30 @@
#include "render.h"
#include <SDL_error.h>
#include <SDL_pixels.h>
#include <SDL_rect.h>
#include <SDL_render.h>
#include <SDL_surface.h>
#include <SDL_ttf.h>
#include <SDL_video.h>
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include <glad/gl.h>
#include "../error.h"
#include "../game/game.h"
#include "../game/tetromino/shapes.h"
#include "../util/types.h"
#include "../util/vec.h"
#include "colour/colour32.h"
#include "colour/colour8.h"
#include <GLFW/glfw3.h>
#define COLOUR_SCORE COLOUR32_YELLOW
#include "shader.h"
#include "../util/error.h"
SDL_Renderer *rend = NULL;
TTF_Font *font = NULL;
struct gamedata const *gdat = NULL;
GLuint pipe;
static SDL_Surface *score_surface = NULL;
static SDL_Texture *score_texture = NULL;
int render_init(void) {
pipe = glCreateProgram();
shader_init(pipe);
glLinkProgram(pipe);
glValidateProgram(pipe);
static inline i32 colpos(uint column) {
return column * BLOCK_WIDTH + 1 + TET_PADDING;
}
static inline i32 rowpos(uint row) {
return row * BLOCK_HEIGHT + 1 + TET_PADDING;
}
static void draw_score_text(void) {
static u16 prev_pts = 0xFFFF; // initialise to maximum, so the code below is triggered on first run
if (prev_pts ^ gdat->pnts) {
char score_text[6] = "0"; // max (base 10) digits of a u16 + null terminator
prev_pts = gdat->pnts;
if (gdat->pnts) sprintf(score_text, "%hu0", gdat->pnts);
SDL_FreeSurface(score_surface);
SDL_DestroyTexture(score_texture);
score_surface = TTF_RenderText_Solid(font, score_text, (SDL_Colour){COLOUR_SCORE.r, COLOUR_SCORE.g, COLOUR_SCORE.b, COLOUR_SCORE.a});
score_texture = SDL_CreateTextureFromSurface(rend, score_surface);
int len;
glGetProgramiv(pipe, GL_INFO_LOG_LENGTH, &len);
if (len > 0) {
char log[len];
glGetProgramInfoLog(pipe, len, &len, log);
log[len - 1] = '\0'; // terminate the string one character sooner since the log includes a newline
fatal("error whilst linking the pipe: '%s'", log);
}
assert(score_surface && score_texture);
SDL_Rect text_rect = {colpos(COLUMNS + 1), rowpos(0), score_surface->w, score_surface->h};
SDL_RenderCopy(rend, score_texture, NULL, &text_rect);
return 1;
}
static inline int draw_block(SDL_Renderer *const renderer, i8vec2 pos) {
const SDL_Rect block = {colpos(pos[VX]), rowpos(pos[VY]), BLOCK_WIDTH - 1, BLOCK_HEIGHT - 1};
return SDL_RenderFillRect(renderer, &block);
}
static void draw_shape(const u8 id, i8vec2 pos) {
set_colour8(rend, colour_from_id(id));
i8vec2 bpos[4];
shape_getblocks(id, bpos);
draw_block(rend, pos + bpos[0]);
draw_block(rend, pos + bpos[1]);
draw_block(rend, pos + bpos[2]);
draw_block(rend, pos + bpos[3]);
}
static void render_level(void) {
for (int y = 0; y < ROWS; y++) {
const u8 *row = gdat->rows[y];
for (int x = 0; x < COLUMNS; x++) {
if (row[x] != 0) {
set_colour8(rend, row[x]);
draw_block(rend, (i8vec2){x, y});
}
}
}
}
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());
gdat = game_data;
}
void render_update(void) {
set_colour32(rend, COLOUR32_BLACK);
SDL_RenderClear(rend);
set_colour32(rend, COLOUR32_WHITE);
static const SDL_Rect field_size = {TET_PADDING, TET_PADDING, TET_WIDTH + 1, TET_HEIGHT + 1};
SDL_RenderDrawRect(rend, &field_size);
if (font) draw_score_text();
render_level();
draw_shape(gdat->pdat.cur, gdat->pdat.sel);
draw_shape(gdat->pdat.nxt[gdat->pdat.idx], (i8vec2){COLUMNS + 1, 3});
SDL_RenderPresent(rend);
}
void render_free(void) {
assert(rend);
SDL_DestroyRenderer(rend);
rend = NULL;
TTF_CloseFont(font);
SDL_FreeSurface(score_surface);
SDL_DestroyTexture(score_texture);
font = NULL;
score_surface = NULL;
score_texture = NULL;
void render_update(GLFWwindow *win) {
}

View File

@@ -1,20 +1,8 @@
#pragma once
#ifndef RENDER_H
#define RENDER_H 1
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <SDL_video.h>
#include <stdint.h>
#include <sys/cdefs.h>
#include "../game/game.h"
#define PX_DENS 25 // pixel density; pixels per block
#define TET_PADDING 10 // padding around the tetris playing field
#define TET_WIDTH (COLUMNS * PX_DENS - TET_PADDING) // tetris playing field width
#define TET_HEIGHT (TET_WIDTH / COLUMNS * ROWS) // tetris playing field height
#define SCREEN_WIDTH ((COLUMNS + 6) * PX_DENS) // window width
#define SCREEN_HEIGHT ((COLUMNS) * PX_DENS / COLUMNS * ROWS) // window height
#define BLOCK_WIDTH (TET_WIDTH / COLUMNS) // width of a block
#define BLOCK_HEIGHT (TET_HEIGHT / ROWS) // height of a block
__nonnull((1, 2)) void render_init(SDL_Window *, struct gamedata const *);
void render_update(void); // causes a draw to occur, will also determine update rate
void render_free(void); // frees the memory allocated to the renderer in render_data
int render_init(void);
void render_update(GLFWwindow *win);
#endif

50
src/io/shader.c Normal file
View File

@@ -0,0 +1,50 @@
#include "shader.h"
#include <stdint.h>
#include <stdlib.h>
#include "../util/error.h"
/* for compiling a shader */
static GLuint shader_compile(GLenum type, const char *src, size_t len) {
int ilen = len;
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, &ilen);
glCompileShader(shader);
/* Repurposing ilen for the max length of the shader log */
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &ilen);
if (ilen > 0) {
char log[ilen];
glGetShaderInfoLog(shader, ilen, &ilen, log);
log[ilen - 1] = '\0'; // terminate the string one character sooner since the log includes a newline
error("error whilst compiling shader type '0X%X': '%s'", type, log);
}
return shader;
}
#define NAME_START(name) _binary_res_##name##_start /* name of a start variable */
#define NAME_END(name) _binary_res_##name##_end /* name of an end variable */
/* wrapper for calling `shader_compile`, but with some more sane parameters. */
#define ADD_SHADER(_type, _name, _store) \
do { \
extern const char NAME_START(_name)[]; \
extern const char NAME_END(_name)[]; \
(_store) = shader_compile(_type, NAME_START(_name), (size_t)NAME_END(_name) - (size_t)NAME_START(_name)); \
} while (0)
int shader_init(GLuint pipe) {
GLuint vs, fs;
ADD_SHADER(GL_VERTEX_SHADER, sh_vert_glsl, vs);
ADD_SHADER(GL_FRAGMENT_SHADER, sh_frag_glsl, fs);
glAttachShader(pipe, vs);
glAttachShader(pipe, fs);
glDeleteShader(vs);
glDeleteShader(fs);
return 1;
}

5
src/io/shader.h Normal file
View File

@@ -0,0 +1,5 @@
#ifndef SHADER_H
#define SHADER_H 1
#include <glad/gl.h>
int shader_init(GLuint pipe);
#endif

View File

@@ -1,60 +1,64 @@
#include "window.h"
#include <SDL.h>
#include <SDL_audio.h>
#include <SDL_error.h>
#include <SDL_video.h>
#include <assert.h>
#include <time.h>
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include "../error.h"
#include "../game/game.h"
#include "../game/time.h"
#include "SDL_ttf.h"
#include "audio.h"
#include "../tetris.h"
#include "../util/error.h"
#include "input.h"
#include "render.h"
static SDL_Window *win = NULL;
static bool close = false;
#define WIN_DPI 32
#define WIN_WIDTH (WIN_DPI * (TET_WIDTH + 3))
#define WIN_HEIGHT (WIN_DPI * TET_HEIGHT)
void window_init(struct gamedata const *gdat) {
assert(!win && !close);
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_INIT, "TTF could not initialize! TTF Error: %s", TTF_GetError());
static GLFWwindow *win = NULL;
win = SDL_CreateWindow("tetris clone", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (win == NULL)
fatal(ERROR_SDL_RENDERING_INIT, "Window failed to be created! SDL Error: %s", SDL_GetError());
int window_init(void) {
#ifndef NDEBUG
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
#endif
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_RED_BITS, 3);
glfwWindowHint(GLFW_GREEN_BITS, 3);
glfwWindowHint(GLFW_BLUE_BITS, 2);
glfwWindowHint(GLFW_ALPHA_BITS, 0);
win = glfwCreateWindow(WIN_WIDTH, WIN_HEIGHT, "Quinn's tetris", NULL, NULL);
if (!win) return 1;
render_init(win, gdat);
audio_init(32000, AUDIO_S16, 1, 4096);
glfwMakeContextCurrent(win);
if (!gladLoadGL(glfwGetProcAddress)) return 1;
glfwSwapInterval(1); /* Wait one screen update for redraw. A.k.a. "vsync" */
glfwSetKeyCallback(win, key_callback);
debug(
"version info:\n"
"\tvendor: %s\n"
"\trenderer: %s\n"
"\tversion: %s\n"
"\tshading lang: %s\n",
glGetString(GL_VENDOR),
glGetString(GL_RENDERER),
glGetString(GL_VERSION),
glGetString(GL_SHADING_LANGUAGE_VERSION));
return 0; //render_init();
}
void window_free(void) {
assert(win);
render_free();
SDL_DestroyWindow(win);
win = NULL;
close = false;
void window_loop(void) {
assert(win != NULL);
while (!glfwWindowShouldClose(win)) {
glfwWaitEventsTimeout(1.0 / 60.0);
audio_free();
}
void window_open(void) {
while (!close) {
size_t time = time_pull();
game_update(input_getdat(time), time);
render_update();
static time_t timeout = 0;
if (time_poll(time, audio_dat[AUDIO_ID_MUSIC].ms, &timeout))
audio_play(AUDIO_ID_MUSIC);
// render_update(win);
glfwSwapBuffers(win);
}
}
void window_close(void) {
close = true;
glfwDestroyWindow(win);
}

View File

@@ -1,12 +1,5 @@
#pragma once
#include "../game/game.h"
#define PX_DENS 25 // pixel density; pixels per block
#define SCREEN_WIDTH ((COLUMNS + 6) * PX_DENS) // window width
#define SCREEN_HEIGHT ((COLUMNS) * PX_DENS / COLUMNS * ROWS) // window height
void window_init(struct gamedata const *);
void window_open(void);
void window_close(void);
void window_free(void);
#ifndef WINDOW_H
#define WINDOW_H 1
int window_init(void);
void window_loop(void);
#endif

View File

@@ -1,25 +1,33 @@
#include <SDL.h>
#include <GLFW/glfw3.h>
#include <stdio.h>
#include <stdlib.h>
#include "error.h"
#include "game/game.h"
#include "io/window.h"
#include "util/error.h"
static void stop(void) {
debug("stopping...", );
window_close();
window_free();
SDL_Quit();
/* callback for GLFW errors */
static void error_callback(int err, const char *const msg) {
error("glfw (%i); \"%s\"", err, msg);
}
// entry-point of the application
int main(int argc, char **argv) {
(void)argc, (void)argv;
// register stop as exit function
atexit(stop);
window_init(game_init());
window_open();
static int init(void) {
glfwSetErrorCallback(error_callback);
glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE); // disable joystick buttons
if (!glfwInit()) return 1;
if (window_init()) return 1;
return 0;
}
void quit(void) {
glfwTerminate();
}
int main(int argc, char **argv) {
(void)argc, (void)argv;
printf("debug: [DBG], info: [INF], warning: [WAR], error: [ERR], fatal: [FAT]\n");
atexit(quit);
if (init()) fatal("failed to initialise the programme!");
window_loop();
quit();
return EXIT_SUCCESS;
}

81
src/tetris.c Normal file
View File

@@ -0,0 +1,81 @@
#include "tetris.h"
#include <string.h>
#include "util/intdef.h"
/* Board co-ordinates:
* 0 1 2 3
* 4 5 6 7
* 8 9 A B
* C D E F */
const u16 tetromino_shapes[7][4] = {
/* NONE CW_90 CW_180 CW_270 */
[TET_I] = {0x159D, 0x89AB, 0x26AE, 0x4567},
[TET_O] = {0x569A, 0x569A, 0x569A, 0x569A},
[TET_T] = {0x1456, 0x1569, 0x4569, 0x1459},
[TET_J] = {0x0456, 0x1259, 0x456A, 0x1589},
[TET_L] = {0x4536, 0x159A, 0x4568, 0x0159},
[TET_S] = {0x1245, 0x156A, 0x5689, 0x0459},
[TET_Z] = {0x0156, 0x2569, 0x459A, 0x1458},
};
void tetris_get_blocks(u8 tetromino, uint *restrict positions) {
uint shape = tetromino_shapes[tetromino & 7][tetromino >> 3];
uint *end = positions + 8;
while (positions < end) {
*positions += shape & 3;
shape >>= 2;
positions++;
}
}
void tetris_init_rows(const u8 *restrict data, const u8 *restrict *restrict out) {
const u8 *restrict *end = out + TET_HEIGHT;
while (out < end) {
*out = data;
data += TET_WIDTH;
out++;
}
}
int tetris_clear_rows(u8 *restrict *restrict rows) {
int count = 0;
u8 *cache[4]; /* the maximum amount of rows the user can clear at once is four */
for (int y = 0; y < TET_HEIGHT; y++) {
int x = 0;
while (x < TET_WIDTH && rows[y][x] && count < 4) x++;
if (x >= TET_WIDTH) cache[count++] = rows[y];
else rows[y - count] = rows[y];
}
if (count) {
for (int i = 0; i < count; i++) {
memset(cache[i], 0, TET_WIDTH);
rows[TET_HEIGHT - 1 - i] = cache[i];
}
}
return count;
}
int tetris_intersect(const u8 *restrict const *restrict rows, u8 tetromino, uint x, uint y) {
uint *pos = (uint[8]){x, y, x, y, x, y, x, y};
uint *end = pos + 8;
tetris_get_blocks(tetromino, pos);
for (; pos < end; pos += 2) {
if (*(pos + 0) >= TET_WIDTH) return 2;
if (*(pos + 1) >= TET_HEIGHT) return 2;
if (rows[*(pos + 1)][*pos]) break;
}
return pos < end;
}
void tetris_place(u8 *restrict *restrict rows, u8 tetromino, uint x, uint y) {
uint *pos = (uint[8]){x, y, x, y, x, y, x, y};
uint *end = pos + 8;
tetris_get_blocks(tetromino, pos);
tetromino &= 7; /* strip rotation data */
tetromino |= TET_NON_EMPTY;
for (; pos < end; pos += 2)
rows[*(pos + 1)][*pos] = tetromino;
}

59
src/tetris.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef TETRIS_H
#define TETRIS_H 1
#include "util/intdef.h"
#include "util/atrb.h"
#define TET_WIDTH 10
#define TET_HEIGHT 24 /* height may be 16—24 */
/* Defines tetromino indices.
* The `TET_R*` definitions specify various rotations.
* This is designed to be OR'd with the tetromino shape index.
* Shape index can be obtained via `val & 7`, and rotation index is
* obtained via `val >> 3`. */
enum tetromino {
TET_I = 0x00,
TET_O = 0x01,
TET_T = 0x02,
TET_J = 0x03,
TET_L = 0x04,
TET_S = 0x05,
TET_Z = 0x06,
TET_R0 = 0x00,
TET_R90 = 0x08,
TET_R180 = 0x10,
TET_R270 = 0x18,
/* Used when storing blocks to the board;
* allows to identify non-empty indices from empty ones.
* This value should not be used during general computation. */
TET_NON_EMPTY = 0x80,
};
/* Stores the co-ordinates of the four blocks in a 4x4 plane.
* Each co-ordinate is 4 bits wide in total. (2 bits for each axis).
* This is done over encoding the shape directly within the 16 bits (4²),
* since this is more easily parsed in practice. */
extern const u16 tetromino_shapes[7][4];
/* Adds the block position data to the numbers in `positions`.
* it is assumed `positions` has at least eight members and is initialised. `(X,Y)*4 = 8` */
void tetris_get_blocks(u8 tetromino, uint *restrict positions) NONNULL((2));
/* Initialises the rows of the Tetris play field.
* `data` is assumed to point to data of at least `TET_WIDTH * TET_HEIGHT` members.
* `out` is assumed to point to data of at least `TET_HEIGHT` members; the row pointers shall be outputted here. */
void tetris_init_rows(const u8 *restrict data, const u8 *restrict *restrict out) NONNULL((1, 2));
/* Finds up to 4 filled rows in `rows`, moving succeeding rows down and adding the cleared row on top.
* It is assumed `rows[0]` is the bottom row and `rows[TET_WIDTH-1]` is the top row.
* Returns the amount of rows cleared. */
int tetris_clear_rows(u8 *restrict *restrict rows) NONNULL((1));
/* Checks if `tetromino` intersects at (`x`, `y`) in the data within `rows` or is out-of-bounds,
* returns `1` if it overlaps within `rows`, `2` if it is out-of-bounds, `0` if no intersection is found. */
int tetris_intersect(const u8 *restrict const *restrict rows, u8 tetromino, uint x, uint y) NONNULL((1));
/* Writes the blocks of `tetromino` to `rows` at position `x` and `y`. */
void tetris_place(u8 *restrict *restrict rows, u8 tetromino, uint x, uint y) NONNULL((1));
#endif

View File

@@ -1,57 +0,0 @@
#pragma once
#if defined __unix__
#include <features.h>
#include <unistd.h>
#elif defined _WIN32
#include <io.h>
#else
#error platform not supported!
#endif
#ifdef _WIN32
#define PATH_SEP '\\' // contains the path separator as a character. Yes it is extremely annoying that this has to exist.
#define PATH_SEP_STR "\\" // contains the path separator as a string, useful for concatenation. Yes it is extremely annoying that this has to exist.
#define unixonly(_exec) // (no-op) executes inline code when __unix__ is defined, otherwise is no-op
#define winonly(_exec) _exec // executes inline code when _WIN32 is defined, otherwise is no-op
#else
#define PATH_SEP '/' // contains the path separator as a character. Yes it is extremely annoying that this has to exist.
#define PATH_SEP_STR "/" // contains the path separator as a string, useful for concatenation. Yes it is extremely annoying that this has to exist.
#define unixonly(_exec) _exec // executes inline code when __unix__ is defined, otherwise is no-op
#define winonly(_exec) // (no-op) executes inline code when _WIN32 is defined, otherwise is no-op
#endif
// define the constants if they haven't been
#ifndef F_OK
#define F_OK 0
#endif
#ifndef X_OK
#define X_OK 1
#endif
#ifndef W_OK
#define W_OK 2
#endif
#ifndef R_OK
#define R_OK 4
#endif
enum faccess_perms {
FA_F = F_OK, // test for file's existence
FA_X = X_OK, // test for executing permission
FA_W = W_OK, // test for write permissions
FA_R = R_OK, // test for read permissions
};
/* tests a files access with F_OK, X_OK, R_OK, W_OK OR'd together
returns 0 upon success. -1 when errno is set and anything else when one or more of the permissions isn't set */
static inline int faccess(const char *restrict fname, int perms) {
#if defined __unix__ && _POSIX_C_SOURCE >= 200809L
return access(fname, perms);
#elif defined _WIN32
return _access(fname, perms);
#else
#error platform unsupported!
#endif
}

51
src/util/error.c Normal file
View File

@@ -0,0 +1,51 @@
#include "error.h"
#include <stdarg.h>
#include <stdio.h>
#include "intdef.h"
static void error_log(FILE *restrict stream, const char *restrict pfx, uint ln, const char *restrict file, const char *restrict fmt, va_list ap) {
fprintf(stream, "(%s:%u) [%s] '", file, ln, pfx);
vfprintf(stream, fmt, ap);
fprintf(stream, "'\n");
}
void error_dbg(uint ln, const char *restrict file, const char *restrict fmt, ...) {
va_list ap;
va_start(ap, fmt);
error_log(stdout, "\033[95mDBG\033[0m", ln, file, fmt, ap);
va_end(ap);
}
void error_inf(uint ln, const char *restrict file, const char *restrict fmt, ...) {
va_list ap;
va_start(ap, fmt);
error_log(stdout, "\033[93mINF\033[0m", ln, file, fmt, ap);
va_end(ap);
}
void error_war(uint ln, const char *restrict file, const char *restrict fmt, ...) {
va_list ap;
va_start(ap, fmt);
error_log(stdout, "\033[91mWAR\033[0m", ln, file, fmt, ap);
va_end(ap);
}
void error_err(uint ln, const char *restrict file, const char *restrict fmt, ...) {
va_list ap;
va_start(ap, fmt);
error_log(stdout, "\033[mFAT\033[0m", ln, file, fmt, ap);
va_end(ap);
}
void error_fat(uint ln, const char *restrict file, const char *restrict fmt, ...) {
va_list ap;
va_start(ap, fmt);
error_log(stdout, "\033[mFAT\033[0m", ln, file, fmt, ap);
va_end(ap);
exit(EXIT_FAILURE);
}

22
src/util/error.h Normal file
View File

@@ -0,0 +1,22 @@
/* Copyright (c) 2025 Quinn
* Licensed under the MIT Licence. See LICENSE for details */
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include "atrb.h"
#include "intdef.h"
void error_dbg(uint ln, const char *restrict file, const char *restrict fmt, ...);
void error_inf(uint ln, const char *restrict file, const char *restrict fmt, ...);
void error_war(uint ln, const char *restrict file, const char *restrict fmt, ...);
void error_err(uint ln, const char *restrict file, const char *restrict fmt, ...);
void error_fat(uint ln, const char *restrict file, const char *restrict fmt, ...) NORET;
#define debug(s, ...) error_dbg(__LINE__, __FILE__, s, ##__VA_ARGS__)
#define info(s, ...) error_inf(__LINE__, __FILE__, s, ##__VA_ARGS__)
#define warn(s, ...) error_war(__LINE__, __FILE__, s, ##__VA_ARGS__)
#define error(s, ...) error_err(__LINE__, __FILE__, s, ##__VA_ARGS__)
#define fatal(s, ...) error_fat(__LINE__, __FILE__, s, ##__VA_ARGS__)