Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: new tetris example from tinyc.games
Browse files Browse the repository at this point in the history
just some minor adjustments on the small tetris implementation by
@superjer and it runs smoothly in cjit, tested both on linux and
windows
jaromil committed Dec 22, 2024
1 parent 92b60c9 commit a6b0acd
Showing 15 changed files with 2,141 additions and 0 deletions.
6 changes: 6 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
@@ -9,6 +9,12 @@ precedence = "aggregate"
SPDX-FileCopyrightText = "2024 Dyne.org foundation"
SPDX-License-Identifier = "GPL-3.0-or-later"

[[annotations]]
path = ["examples/tinyc.games/**", "examples/tetris/**"]
precedence = "aggregate"
SPDX-FileCopyrightText = "Copyright (c) 2016 Jer Wilson"
SPDX-License-Identifier = "MIT"

[[annotations]]
path = [".*", ".github/**"]
precedence = "aggregate"
354 changes: 354 additions & 0 deletions examples/tetris/graphics.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
#include "tet.h"
#include "utils.c"
#include "font.c"

#define VBUFLEN 40000

unsigned main_prog_id;
GLuint main_vao;
GLuint main_vbo;
float vbuf[VBUFLEN];
int vbuf_n;
float color_r, color_g, color_b;

// render a line of text optionally with a %d value in it
void text(char *fstr, int value)
{
if (!fstr) return;
char str[100];
snprintf(str, 99, fstr, value);
font_begin(win_x, win_y);
font_add_text(str, text_x, text_y, 3 * bs / 4);
font_end(1, 1, 1);
text_y += bs * 125 / 100 + (fstr[strlen(fstr) - 1] == ' ' ? bs : 0);
}

void draw_setup()
{
fprintf(stderr, "GLSL version on this system is %s\n", (char *)glGetString(GL_SHADING_LANGUAGE_VERSION));
unsigned int vertex = file2shader(GL_VERTEX_SHADER, "examples/tetris/shaders/main.vert");
unsigned int fragment = file2shader(GL_FRAGMENT_SHADER, "examples/tetris/shaders/main.frag");
main_prog_id = glCreateProgram();
glAttachShader(main_prog_id, vertex);
glAttachShader(main_prog_id, fragment);
glLinkProgram(main_prog_id);
check_program_errors(main_prog_id, "main");
glDeleteShader(vertex);
glDeleteShader(fragment);
glGenVertexArrays(1, &main_vao);
glGenBuffers(1, &main_vbo);
}

void vertex(float x, float y, float r, float g, float b)
{
if (vbuf_n >= VBUFLEN - 5) return;
vbuf[vbuf_n++] = x;
vbuf[vbuf_n++] = y;
vbuf[vbuf_n++] = r;
vbuf[vbuf_n++] = g;
vbuf[vbuf_n++] = b;
}

void rect(float x, float y, float w, float h)
{
vertex(x , y , color_r, color_g, color_b);
vertex(x + w, y , color_r, color_g, color_b);
vertex(x , y + h, color_r, color_g, color_b);
vertex(x , y + h, color_r, color_g, color_b);
vertex(x + w, y , color_r, color_g, color_b);
vertex(x + w, y + h, color_r, color_g, color_b);
}

void draw_start()
{
glViewport(0, 0, win_x, win_y);
glClear(GL_COLOR_BUFFER_BIT);
}

void draw_end()
{
glUseProgram(main_prog_id);

float near = -1.f;
float far = 1.f;
float x = 1.f / (win_x / 2.f);
float y = -1.f / (win_y / 2.f);
float z = -1.f / ((far - near) / 2.f);
float tz = -(far + near) / (far - near);
float ortho[] = {
x, 0, 0, 0,
0, y, 0, 0,
0, 0, z, 0,
-1, 1, tz, 1,
};
glUniformMatrix4fv(glGetUniformLocation(main_prog_id, "proj"), 1, GL_FALSE, ortho);

glBindVertexArray(main_vao);
glBindBuffer(GL_ARRAY_BUFFER, main_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof vbuf, vbuf, GL_STATIC_DRAW);

// show GL where the position data is
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);
glEnableVertexAttribArray(0);

// show GL where the color data is
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(2 * sizeof(float)));
glEnableVertexAttribArray(1);

glDrawArrays(GL_TRIANGLES, 0, vbuf_n / 5);
if (vbuf_n > VBUFLEN * 3 / 4)
fprintf(stderr, "vbuf fullness (%d/%d)\n", vbuf_n, VBUFLEN);
vbuf_n = 0;
}

// set the current draw color to the color assoc. with a shape
void set_color_from_shape(int shape, int shade)
{
color_r = MAX((colors[shape] >> 16 & 0xFF) + shade, 0) / 255.f;
color_g = MAX((colors[shape] >> 8 & 0xFF) + shade, 0) / 255.f;
color_b = MAX((colors[shape] >> 0 & 0xFF) + shade, 0) / 255.f;
}

void set_color(int r, int g, int b)
{
color_r = r / 255.f;
color_g = g / 255.f;
color_b = b / 255.f;
}

void draw_menu()
{
if (state != MAIN_MENU && state != NUMBER_MENU) return;

menu_pos = MAX(menu_pos, 0);
menu_pos = MIN(menu_pos, state == NUMBER_MENU ? 3 : 2);
p = play; // just grab first player :)

set_color(0, 0, 0);
rect(p->held.x,
p->held.y + p->box_w + bs2 + line_height * (menu_pos + 1),
p->board_w,
line_height);
draw_end();

text_x = p->held.x;
text_y = p->held.y + p->box_w + bs2;
if (state == MAIN_MENU)
{
text("Main Menu" , 0);
text("Endless" , 0);
text("Garbage Race" , 0);
text("Quit" , 0);
}
else if (state == NUMBER_MENU)
{
text("How many players?", 0);
text("1" , 0);
text("2" , 0);
text("3" , 0);
text("4" , 0);
}
}

void draw_particles()
{
set_color(254, 254, 254);
for (int i = 0; i < NPARTS; i++)
{
if (parts[i].r <= 0.5f)
continue;
rect(parts[i].x, parts[i].y, parts[i].r, parts[i].r);
}
}

// draw a single mino (square) of a shape
void draw_mino(int x, int y, int shape, int outline, int part)
{
if (!part) return;
int bw = MAX(1, outline ? bs / 10 : bs / 6);
set_color_from_shape(shape, -50);
rect(x, y, bs, bs);
set_color_from_shape(shape, outline ? -255 : 0);
rect( // horizontal band
x + (part & 8 ? 0 : bw),
y + bw,
bs - (part & 8 ? 0 : bw) - (part & 2 ? 0 : bw),
bs - bw - bw);
rect( // vertical band
x + (part & 32 ? 0 : bw),
y + (part & 1 ? 0 : bw),
bs - (part & 32 ? 0 : bw) - (part & 16 ? 0 : bw),
bs - (part & 1 ? 0 : bw) - (part & 4 ? 0 : bw));
}

#define CENTER 1
#define OUTLINE 2

void draw_shape(int x, int y, int color, int rot, int flags)
{
for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++)
draw_mino(
x + bs * i + ((flags & CENTER) ? bs2 + bs2 * center[2 * color ] : 0),
y + bs * j + ((flags & CENTER) ? bs + bs2 * center[2 * color + 1] : 0),
color,
flags & OUTLINE,
is_solid_part(color, rot, i, j)
);
}

// draw everything in the game on the screen for current player
void draw_player()
{
if (state == MAIN_MENU || state == NUMBER_MENU) return;

int x = p->board_x + bs * p->shake_x;
int y = p->board_y + bs * p->shake_y;

// draw background, black boxes
set_color(16, 26, 24);
rect(p->held.x, p->held.y, p->box_w, p->box_w);
rect(x, y, p->board_w, bs * VHEIGHT);

// find ghost piece position
int ghost_y = p->it.y;
while (ghost_y < BHEIGHT && !collide(p->it.x, ghost_y + 1, p->it.rot))
ghost_y++;

// draw shadow
if (p->it.color)
{
struct shadow shadow = shadows[p->it.rot][p->it.color];
int top = MAX(0, p->it.y + shadow.y - 5);
set_color(8, 13, 12);
rect(x + bs * (p->it.x + shadow.x),
y + bs * top,
bs * shadow.w,
MAX(0, bs * (ghost_y - top + shadow.y - 5)));
}

// draw hard drop beam
float loss = .1f * (tick - p->beam_tick);
if (loss < 1.f && p->beam.color)
{
struct shadow shadow = shadows[p->beam.rot][p->beam.color];
int rw = bs * shadow.w;
int rh = bs * (p->beam.y + shadow.y - 5);
int lossw = (1.f - ((1.f - loss) * (1.f - loss))) * rw;
int lossh = loss < .5f ? 0.f : (1.f - ((1.f - loss) * (1.f - loss))) * rh;
set_color(66, 74, 86);
rect(x + bs * (p->beam.x + shadow.x) + lossw / 2,
y + lossh,
rw - lossw,
rh - lossh);
}

// draw pieces on board
for (int i = 0; i < BWIDTH; i++) for (int j = 0; j < BHEIGHT; j++)
draw_mino(x + bs * i, y + bs * (j-5) - p->row[j].offset,
p->row[j].col[i].color, 0, p->row[j].col[i].part);

// draw queued garbage
p->top_garb = y + bs * VHEIGHT;
int garb_height = 0;
for (int i = 0; i < GARB_LVLS; i++)
for (int j = 0; j < p->garbage[i]; j++)
{
draw_mino(x - 3 * bs2, (p->top_garb -= bs), (3 - i) + 9, 0, '@');
garb_height++;
}
if (p->flash > 8)
{
int flash_sq = p->flash * p->flash;
set_color(flash_sq, flash_sq, flash_sq);
rect(x - 3 * bs2 - bs4, p->top_garb - bs4, bs + bs4 + bs4, garb_height * bs + bs2);
}
p->top_garb = y + bs * VHEIGHT;
for (int i = 0; i < GARB_LVLS; i++)
for (int j = 0; j < p->garbage[i]; j++)
draw_mino(x - 3 * bs2, (p->top_garb -= bs), (3 - i) + 9, 0, '@');

// draw falling piece & ghost
draw_shape(x + bs * p->it.x, y + bs * (ghost_y - 5), p->it.color, p->it.rot, OUTLINE);
draw_shape(x + bs * p->it.x, y + bs * (p->it.y - 5), p->it.color, p->it.rot, 0);

// draw next pieces
for (int n = 0; n < 5; n++)
draw_shape(p->preview_x, p->preview_y + 3 * bs * n, p->next[n], 0, CENTER);

// draw held piece
draw_shape(p->held.x, p->held.y, p->held.color, 0, CENTER);

draw_end();

// draw scores etc
text_x = p->held.x;
text_y = p->held.y + p->box_w + bs2;
text("%d pts ", p->score);
text("%d lines ", p->lines);

int secs = p->ticks / 120 % 60;
int mins = p->ticks / 120 / 60 % 60;
char minsec[80];
sprintf(minsec, "%d:%02d.%02d ", mins, secs, p->ticks % 120 * 1000 / 1200);
text(minsec, 0);
text(p->dev_name, 0);
if (p->combo > 1) text("%d combo ", p->combo);
if (p->tspin == TSPIN_FULL)
text("T-SPIN", 0);
else if (p->tspin == TSPIN_MINI)
text("T-SPIN MINI", 0);

if (p->reward)
{
text_x = p->reward_x - bs;
text_y = p->reward_y--;
text("%d", p->reward);
}

text_x = x + bs2;
text_y = y + bs2 * 19;
if (p->countdown_time > 0)
text(countdown_msg[p->countdown_time / CTDN_TICKS], 0);

if (state == ASSIGN)
text(p >= play + assign_me ? "Press button to join" : p->dev_name, 0);

if (state == GAMEOVER) text("Game over", 0);
}

void reflow()
{
float strength = 0.0005f * (1 + rand() % 10);
for (int n = 0; n < NFLOWS; n++)
{
flows[n].x = rand() % win_x;
flows[n].y = rand() % win_y;
flows[n].r = rand() % 100 + 100;
flows[n].vx = (rand() % 10 - 5) * strength;
flows[n].vy = (rand() % 10 - 5) * strength;
}
}

// recalculate sizes and positions on resize
void resize(int x, int y)
{
win_x = x;
win_y = y;
bs = MIN(win_x / (nplay * 22), win_y / 24);
bs2 = bs / 2;
bs4 = bs / 4;
line_height = bs * 125 / 100;
int n = 0;
for (p = play; p < play + nplay; p++, n++)
{
p->board_x = (x / (nplay * 2)) * (2 * n + 1) - bs2 * BWIDTH;
p->board_y = (y / 2) - bs2 * VHEIGHT;
p->board_w = bs * 10;
p->box_w = bs * 5;
p->held.x = p->board_x - p->box_w - bs2;
p->held.y = p->board_y;
p->preview_x = p->board_x + p->board_w + bs2;
p->preview_y = p->board_y;
}
reflow();
}
249 changes: 249 additions & 0 deletions examples/tetris/input.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
#include "tet.h"
#include <audio.h>

#define WASD -1
#define ARROW_KEYS -2

void resize(int, int);

void down()
{
p->down = 1;
p->move_cooldown = 0;
}

void left()
{
p->left = 1;
p->move_cooldown = 0;
}

void right()
{
p->right = 1;
p->move_cooldown = 0;
}

// spin the falling piece left or right, if possible
void spin(int dir)
{
int new_rot = (p->it.rot + dir) % 4;
int k = new_rot * 20 + (dir == 1 ? 0 : 10) + (p->it.color == 4 ? 80 : 0);

for (int i = 0; i < 5; i++)
{
int kx = kicks[k++];
int ky = kicks[k++];
if (!collide(p->it.x + kx, p->it.y + ky, new_rot))
{
p->it.rot = new_rot;
p->it.x += kx;
p->it.y += ky;
if (p->grounded && p->grounded_moves < 15)
{
p->idle_time = 0;
p->grounded_moves++;
}
audio_tone(TRIANGLE, C5, B5, 1, 1, 1, 1);
return;
}
}
audio_tone(SQUARE, C5, B5, 1, 1, 1, 1);
}

// move the falling piece as far down as it will go
void hard()
{
for (; !collide(p->it.x, p->it.y + 1, p->it.rot); p->it.y++)
p->score++;
p->idle_time = 100;
p->beam = p->it;
p->beam_tick = tick;
p->shake_y += .25f;
audio_tone(TRIANGLE, A1, E3, 5, 5, 5, 90);
}

// hold a piece for later
void hold()
{
if (p->hold_uses++) return;
SWAP(p->held.color, p->it.color);
reset_fall();
}

void joy_setup()
{
for (int i = 0; i < SDL_NumJoysticks(); i++)
{
if (!SDL_IsGameController(i))
{
printf("Controller not supported: %s", SDL_JoystickNameForIndex(i));
printf(" - Google SDL_GAMECONTROLLERCONFIG to fix this\n");
continue;
}
SDL_GameController *cont = SDL_GameControllerOpen(i);
printf("Controller added: %s %p\n", SDL_GameControllerNameForIndex(i), (void*)cont);
}
SDL_GameControllerEventState(SDL_ENABLE);
}

// set current player to match an input device
void set_player_from_device(int device)
{
for (int i = 0; i < NPLAY; i++)
{
if (play[i].device < 0)
p = play + i; // default to any keyboard

if (play[i].device == device)
{
p = play + i;
return;
}
}
}

// figure out which "device" from key pressed, i.e. WASD or Arrow keys
int device_from_key()
{
switch(event.key.keysym.sym) {
case SDLK_w: case SDLK_a: case SDLK_s: case SDLK_d: case SDLK_z: case SDLK_x:
case SDLK_TAB: case SDLK_CAPSLOCK: case SDLK_LSHIFT:
return WASD;
default:
return ARROW_KEYS;
}
}

int menu_input(int key_or_button)
{
switch (key_or_button)
{
case SDLK_s: case SDLK_DOWN:
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: menu_pos++; break;

case SDLK_w: case SDLK_UP:
case SDL_CONTROLLER_BUTTON_DPAD_UP: menu_pos--; break;

case SDLK_RETURN: case SDLK_z:
case SDL_CONTROLLER_BUTTON_A:
if (state == MAIN_MENU)
{
if (menu_pos == 0) garbage_race = 0;
if (menu_pos == 1) garbage_race = 1;
if (menu_pos == 2) exit(0);
state = NUMBER_MENU;
menu_pos = 0;
}
else
{
nplay = menu_pos + 1;
resize(win_x, win_y);
state = ASSIGN;
assign_me = 0;
}
break;
}
return 0;
}

int assign(int device)
{
for (int i = 0; i < assign_me; i++)
if (play[i].device == device)
return 0;

play[assign_me].device = device;
sprintf(play[assign_me].dev_name, "%.10s",
device == WASD ? "WASD keys" :
device == ARROW_KEYS ? "Arrow keys" :
SDL_GameControllerName(SDL_GameControllerFromInstanceID(device)));

if (++assign_me == nplay)
{
state = PLAY;
assign_me = 0;
seed = rand();
for (p = play; p < play + nplay; p++)
new_game();
}
return 0;
}

// handle a key press from a player
int key_down()
{
if (event.key.repeat) return 0;
if (state == MAIN_MENU || state == NUMBER_MENU) return menu_input(event.key.keysym.sym);
if (state == GAMEOVER) return (state = MAIN_MENU);
if (state == ASSIGN) return assign(device_from_key());

set_player_from_device(device_from_key());

if (!p->it.color || p->countdown_time >= CTDN_TICKS) return 0;

switch (event.key.keysym.sym)
{
case SDLK_a: case SDLK_LEFT: left(); break;
case SDLK_s: case SDLK_DOWN: down(); break;
case SDLK_d: case SDLK_RIGHT: right(); break;

case SDLK_w: case SDLK_UP: hard(); break;
case SDLK_z: case SDLK_CAPSLOCK: case SDLK_COMMA: spin(3); break;
case SDLK_x: case SDLK_LSHIFT: case SDLK_PERIOD: spin(1); break;
case SDLK_TAB: case SDLK_SLASH: hold(); break;

case SDLK_l:
p->level++;
p->lines += 10;
break;
}

return 0;
}

void key_up()
{
if (state == ASSIGN) return;
set_player_from_device(device_from_key());

switch (event.key.keysym.sym)
{
case SDLK_a: case SDLK_LEFT: p->left = 0; break;
case SDLK_d: case SDLK_RIGHT: p->right = 0; break;
case SDLK_s: case SDLK_DOWN: p->down = 0; break;
}
}

int joy_down()
{
if (state == ASSIGN) return assign(event.cbutton.which);
if (state == GAMEOVER) return (state = MAIN_MENU);
if (state == MAIN_MENU || state == NUMBER_MENU) return menu_input(event.cbutton.button);
set_player_from_device(event.cbutton.which);

if (p->it.color) switch(event.cbutton.button)
{
case SDL_CONTROLLER_BUTTON_A: spin(3); break;
case SDL_CONTROLLER_BUTTON_B: spin(1); break;
case SDL_CONTROLLER_BUTTON_DPAD_UP: hard(); break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: down(); break;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: left(); break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: right(); break;
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: hold(); break;
}
return 0;
}

void joy_up()
{
if (state == ASSIGN) return;
set_player_from_device(event.cbutton.which);

switch(event.cbutton.button)
{
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: p->down = 0; break;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: p->left = 0; break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: p->right = 0; break;
}
}
678 changes: 678 additions & 0 deletions examples/tetris/main.c

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions examples/tetris/shaders/main.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#version 330 core
in vec3 color2;

out vec4 color;

void main()
{
color = vec4(color2.xyz, 1.0);
}
13 changes: 13 additions & 0 deletions examples/tetris/shaders/main.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#version 330 core
layout (location = 0) in vec2 pos;
layout (location = 1) in vec3 color;

out vec3 color2;

uniform mat4 proj;

void main()
{
gl_Position = proj * vec4(pos.xy, 0.0, 1.0);
color2 = color;
}
191 changes: 191 additions & 0 deletions examples/tetris/tet.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#pragma once
#include <SDL2/SDL.h>

#define GL3_PROTOTYPES 1

#ifdef __APPLE__
#include <OpenGL/gl3.h>
#else
#include <GL/glew.h>
#endif

#define BWIDTH 10 // board width, height
#define BHEIGHT 25
#define VHEIGHT 20 // visible height
#define BAG_SZ 7 // bag size
#define GARB_LVLS 4 // levels of queued garbage
#define NPLAY 4
#define NPARTS 1000
#define NFLOWS 20
#define CTDN_TICKS 96
#define SHOW_FPS 0

// collision test results
enum { NONE = 0, WALL, NORMAL };

enum { TSPIN_NONE = 0, TSPIN_FULL, TSPIN_MINI };

// Bits in each letter indicate which sides connect when drawing
// A 1000001 - up
// B 1000010 - right
// D 1000100 - down
// H 1001000 - left
// 1010000 - connect left corner where up or down connects - for square piece
// 1100000 - ... right ...

char shapes[] =
".... D... ..D. .Vl. .... BL.. .FH. ;D;. "
".... CJH. BJI. .Si. BJJH .CH. BI.. BKH. "
".... .... .... .... .... .... .... ,.,. "
".... .... .... .... .... .... .... .... "

".... .FH. .D.. .Vl. ..D. ..D. .D.. ,D;. "
".... .E.. .E.. .Si. ..E. .FI. .CL. .GH. "
".... .A.. .CH. .... ..E. .A.. ..A. ,A;. "
".... .... .... .... ..A. .... .... .... "

".... .... .... .Vl. .... .... .... ,.,. "
".... BJL. FJH. .Si. .... BL.. .FH. BNH. "
".... ..A. A... .... BJJH .CH. BI.. ;A;. "
".... .... .... .... .... .... .... .... "

".... .D.. BL.. .Vl. .D.. .D.. D... ;D,. "
".... .E.. .E.. .Si. .E.. FI.. CL.. BM.. "
".... BI.. .A.. .... .E.. A... .A.. ;A,. "
".... .... .... .... .A.. .... .... .... ";

struct shadow { int x, w, y; } shadows[4][8] = { // pre-computed shadow positions for each piece
{{0,0,0}, {0,3,1}, {0,3,1}, {1,2,0}, {0,4,1}, {0,3,1}, {0,3,1}, {0,3,1}},
{{0,0,0}, {1,2,0}, {1,2,2}, {1,2,0}, {2,1,0}, {1,2,1}, {1,2,1}, {1,2,1}},
{{0,0,0}, {0,3,1}, {0,3,1}, {1,2,0}, {0,4,2}, {0,3,2}, {0,3,2}, {0,3,1}},
{{0,0,0}, {0,2,2}, {0,2,0}, {1,2,0}, {1,1,0}, {0,2,1}, {0,2,1}, {0,2,1}},
};

// helps center shapes in preview box
int center[] = {0,0, 1,1, 1,1, 0,1, 0,0, 1,1, 1,1, 1,1};

int colors[] = {
0x000000, // unused
0x1983c4, // J
0xfa8333, // L
0xffca39, // square
0x1be7ff, // line
0xff5a5f, // Z
0x89c926, // S
0x88488f, // T
0xffffff, // shine color
0x6f7866, // garbage colors
0x9fa896,
0xffca39,
0xff5a5f,
};

int kicks[] = { // clockwise counterclockwise
0,0, -1, 0, -1, 1, 0,-2, -1,-2, 0,0, 1, 0, 1, 1, 0,-2, 1,-2, // rot 0
0,0, -1, 0, -1,-1, 0, 2, -1, 2, 0,0, -1, 0, -1,-1, 0, 2, -1, 2, // rot 1
0,0, 1, 0, 1, 1, 0,-2, 1,-2, 0,0, -1, 0, -1, 1, 0,-2, -1,-2, // rot 2
0,0, 1, 0, 1,-1, 0, 2, 1, 2, 0,0, 1, 0, 1,-1, 0, 2, 1, 2, // rot 3
// line-clockwise line-counterclockwise
0,0, 2, 0, -1, 0, 2,-1, -1, 2, 0,0, 1, 0, -2, 0, 1, 2, -2,-1, // rot 0
0,0, -2, 0, 1, 0, -2, 1, 1,-2, 0,0, 1, 0, -2, 0, 1, 2, -2,-1, // rot 1
0,0, -1, 0, 2, 0, -1,-2, 2, 1, 0,0, -2, 0, 1, 0, -2, 1, 1,-2, // rot 2
0,0, 2, 0, -1, 0, 2,-1, -1, 2, 0,0, -1, 0, 2, 0, -1,-2, 2, 1, // rot 3
};

float combo_bonus[] = {
1.f, 1.5f, 2.f, 3.f, 4.f, 5.f, 6.f, 8.f, 10.f, 12.f, 15.f, 20.f,
25.f, 30.f, 40.f, 50.f, 75.f, 100.f
};
#define MAX_COMBO ((sizeof combo_bonus / sizeof *combo_bonus) - 1)

int rewards[] = {0, 100, 250, 500, 1000}; // points for clearing 0,1,2,3,4 lines

int speeds[] = {100, 80, 70, 60, 52, 46, 40, 35, 30, 26, 22, 18, 15, 12, 10, 8, 6, 5, 4, 3, 2};
#define MAX_SPEED ((sizeof speeds / sizeof *speeds) - 1)

char countdown_msg[][20] = {" Go!", " - 1 -", " - 2 -", " - 3 -"};

struct piece { int x, y, rot, color; };
struct spot { int color, part; };
struct row {
struct spot col[BWIDTH];
int fullness;
int special;
int offset;
};

struct player {
struct row row[BHEIGHT]; // the board, excluding the falling piece
int left, right, down; // true when holding a direction
int move_cooldown; // cooldown before hold-to-repeat movement
struct piece it; // current falling piece - "it"
struct piece beam; // hard drop beam
struct piece held; // shape in the hold box
int beam_tick; // tick that beam was created
int hold_uses; // have we swapped with the hold already?
int bag[BAG_SZ]; // "bag" of upcoming pieces
int bag_idx; // last position used up in bag
int next[5]; // next pieces in preview (take from bag)
int grounded; // is piece on the ground?
int grounded_moves; // how many moves have we made on the ground?
int last_dx_tick; // tick of most recent left/right movement
int lines, score, best; // scoring
int combo; // clears in-a-row
int reward, reward_x, reward_y; // for hovering points indicator
int garbage[GARB_LVLS + 1]; // queued garbage, e.g. received from opponents
int garbage_tick; // keeps track of when to age garbage
int garbage_remaining; // how many lines of garbage remain to clear to win
int garbage_bits; // fractions of garbage attached to each particle
int top_garb; // highest position of garbage stack drawn
int level; // difficultly level (lines/10)
int countdown_time; // ready-set-go countdown
int idle_time; // how long the player has been idle in ticks
int shiny_lines;
int shine_time; // delay in ticks before clearing line(s)
int dead_time; // delay in ticks after game over
int board_x, board_y, board_w; // positions and sizes of things
int preview_x, preview_y; // position of preview
int box_w; // width of hold box / preview box
int ticks; // counts up while game is going
int seed1, seed2; // make garbage and bags fair
float shake_x, shake_y; // amount the board is offset by shaking
int flash; // flashing from receiving garbage
int tspin;
int device; // SDL's input device id
char dev_name[80]; // input device "name"
} play[NPLAY], *p; // one per player

struct particle { float x, y, r, vx, vy; int opponent, bits; };
struct particle parts[NPARTS];
struct particle flows[NFLOWS];

enum state { MAIN_MENU = 0, NUMBER_MENU, ASSIGN, PLAY, GAMEOVER} state;
int win_x = 1000; // window size
int win_y = 750;
int bs, bs2, bs4; // individual block size, in half, in quarters
int tick; // counts up one per frame
int nplay = 1; // number of players
int assign_me; // who is getting an input device assigned?
int menu_pos; // current position in menu
int text_x, text_y; // position of text drawing
int line_height; // text line height
int garbage_race;
int npart;
int seed;

SDL_GLContext ctx;
SDL_Event event;
SDL_Window *win;
SDL_Renderer *renderer;

void do_events();
void setup();
void update_player();
void move(int dx, int dy, int gravity);
void reset_fall();
void bake();
void new_game();
int is_solid_part(int shape, int rot, int i, int j);
int is_tspin_part(int shape, int rot, int i, int j);
int collide(int x, int y, int rot);
void update_particles();
15 changes: 15 additions & 0 deletions examples/tetris/timer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#define TIMER_NAMES \
X(SDL_Delay), \
X(do_events), \
X(update_player), \
X(update_particles), \
X(draw_start), \
X(draw_menu), \
X(draw_player), \
X(draw_particles), \
X(draw_end), \
X(SDL_GL_SwapWindow),

#include <timer.c>

#undef TIMER_NAMES
84 changes: 84 additions & 0 deletions examples/tinyc.games/audio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// tinyc.games audio - copyright 2023 Jer Wilson
//
// 1. Add SDL_INIT_AUDIO to your SDL_Init()
// 2. Call audioinit() before playing sound
// 3. Play tones with audio_tone()

#include <math.h>
#include <SDL2/SDL_audio.h>
#include "audio.h"

void audio_tone(int shape, int note_lo, int note_hi,
double attack, double decay, double sustain, double release)
{
int note = note_lo + rand() % (note_hi - note_lo + 1);
if (note < A0 || note > C6) return;

envs[rand() % NUM_ENVS] = (struct envelope){
.shape = shape,
.start_freq = music_notes[note].frequency,
.volume = 1.0 + 0.08 * music_notes[note].wavelength,
.attack = (attack ) / 1000.f,
.decay = (attack + decay ) / 1000.f,
.sustain = (attack + decay + sustain ) / 1000.f,
.release = (attack + decay + sustain + release) / 1000.f,
};
}

static void mix_audio(void *unused, unsigned char *stream, int len)
{
short *out = (short*)stream;

for (int j = 0; j < len/2; j++)
{
int samp = 0;

for (int n = 0; n < NUM_ENVS; n++)
{
struct envelope *e = envs + n;
if (e->pos >= e->release)
continue;

double freq = e->start_freq;
double wl = 1.0 / freq;
double wl2 = wl / 2.0;
double frac = fmod(e->pos, wl);
double t = e->pos;
e->pos += 1.0 / 44100;

double veloc = e->volume * 2200;
if (t <= e->attack ) veloc *= t / e->attack * 1.5;
else if (t <= e->decay ) veloc *= (e->decay - t) / (e->decay - e->attack) * .5 + 1.0;
else if (t <= e->sustain) ;
else if (t <= e->release) veloc *= (e->release - t) / (e->release - e->sustain);

switch (e->shape)
{
case SINE: samp += veloc * sin(frac * freq * 6.28318531); break;
case SQUARE: samp += frac > wl2 ? veloc : -veloc; break;
case TRIANGLE: samp += (4 * freq * (frac > wl2 ? (wl - frac) : frac) - 1) * veloc; break;
}
}

out[j] = samp > 32767 ? 32768 :
samp < -32767 ? -32767 :
samp;
}
}

void audio_init()
{
SDL_AudioDeviceID id;
SDL_AudioSpec actual, desired = {
.freq = 44100,
.format = AUDIO_S16LSB,
.channels = 1,
.samples = 512,
.callback = mix_audio,
};
id = SDL_OpenAudioDevice(NULL, 0, &desired, &actual, SDL_AUDIO_ALLOW_ANY_CHANGE);
if (!id)
fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError());
else
SDL_PauseAudioDevice(id, 0);
}
100 changes: 100 additions & 0 deletions examples/tinyc.games/audio.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#pragma once

#define NUM_ENVS 20

enum shape {SINE, SQUARE, TRIANGLE};

struct envelope {
enum shape shape;
double start_freq;
double volume;
double attack, decay, sustain, release; // envelope timings
double pos; // current play position
double noiseval;
int noisectr;
int noisesign;
} envs[NUM_ENVS];

enum notes {
A0, As0, B0, C1, Cs1, D1, Ds1, E1, F1, Fs1, G1, Gs1,
A1, As1, B1, C2, Cs2, D2, Ds2, E2, F2, Fs2, G2, Gs2,
A2, As2, B2, C3, Cs3, D3, Ds3, E3, F3, Fs3, G3, Gs3,
A3, As3, B3, C4, Cs4, D4, Ds4, E4, F4, Fs4, G4, Gs4,
A4, As4, B4, C5, Cs5, D5, Ds5, E5, F5, Fs5, G5, Gs5,
A5, As5, B5, C6,
};

struct {
char name[3];
int octave;
double frequency;
double wavelength;
} music_notes[] = {
{"A" , 0, 27.500, 12.374}, // Piano low
{"A#", 0, 29.135, 11.680},
{"B" , 0, 30.868, 11.024},
{"C" , 1, 32.703, 10.405},
{"C#", 1, 34.648, 9.821},
{"D" , 1, 36.708, 9.270},
{"D#", 1, 38.891, 8.750},
{"E" , 1, 41.203, 8.259},
{"F" , 1, 43.654, 7.795},
{"F#", 1, 46.249, 7.358},
{"G" , 1, 48.999, 6.945},
{"G#", 1, 51.913, 6.555},
{"A" , 1, 55.000, 6.187},
{"A#", 1, 58.270, 5.840},
{"B" , 1, 61.735, 5.512},
{"C" , 2, 65.406, 5.203},
{"C#", 2, 69.296, 4.911},
{"D" , 2, 73.416, 4.635},
{"D#", 2, 77.782, 4.375},
{"E" , 2, 82.407, 4.129},
{"F" , 2, 87.307, 3.898},
{"F#", 2, 92.499, 3.679},
{"G" , 2, 97.999, 3.472},
{"G#", 2, 103.826, 3.278},
{"A" , 2, 110.000, 3.094},
{"A#", 2, 116.541, 2.920},
{"B" , 2, 123.471, 2.756},
{"C" , 3, 130.813, 2.601},
{"C#", 3, 138.591, 2.455},
{"D" , 3, 146.832, 2.318},
{"D#", 3, 155.563, 2.187},
{"E" , 3, 164.814, 2.065},
{"F" , 3, 174.614, 1.949},
{"F#", 3, 184.997, 1.839},
{"G" , 3, 195.998, 1.736},
{"G#", 3, 207.652, 1.639},
{"A" , 3, 220.000, 1.547},
{"A#", 3, 233.082, 1.460},
{"B" , 3, 246.942, 1.378},
{"C" , 4, 261.626, 1.301}, // middle C
{"C#", 4, 277.183, 1.228},
{"D" , 4, 293.665, 1.159},
{"D#", 4, 311.127, 1.094},
{"E" , 4, 329.628, 1.032},
{"F" , 4, 349.228, 0.974},
{"F#", 4, 369.994, 0.920},
{"G" , 4, 391.995, 0.868},
{"G#", 4, 415.305, 0.819},
{"A" , 4, 440.000, 0.773}, // A 440
{"A#", 4, 466.164, 0.730},
{"B" , 4, 493.883, 0.689},
{"C" , 5, 523.251, 0.650},
{"C#", 5, 554.365, 0.614},
{"D" , 5, 587.330, 0.579},
{"D#", 5, 622.254, 0.547},
{"E" , 5, 659.255, 0.516},
{"F" , 5, 698.456, 0.487},
{"F#", 5, 739.989, 0.460},
{"G" , 5, 783.991, 0.434},
{"G#", 5, 830.609, 0.410},
{"A" , 5, 880.000, 0.387},
{"A#", 5, 932.328, 0.365},
{"B" , 5, 987.767, 0.345},
};

void audio_init();
void audio_tone(int shape, int note_lo, int note_hi,
double attack, double decay, double sustain, double release);
282 changes: 282 additions & 0 deletions examples/tinyc.games/font.c

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions examples/tinyc.games/shaders/font.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#version 330 core
in vec2 uv;
out vec4 color;

uniform vec3 incolor;
uniform sampler2D tex;

void main()
{
color = vec4(incolor, texture(tex, uv).r);
}
11 changes: 11 additions & 0 deletions examples/tinyc.games/shaders/font.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#version 330 core
layout (location = 0) in vec4 pos;
out vec2 uv;

uniform mat4 proj;

void main()
{
gl_Position = proj * vec4(pos.xy, 0.0, 1.0);
uv = pos.zw;
}
71 changes: 71 additions & 0 deletions examples/tinyc.games/timer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include <stdio.h>
#include <stdbool.h>
#include <SDL2/SDL.h>

// You must X-define TIMER_NAMES before including this file, e.g:
//
// #define TIMER_NAMES X(create_hmap), X(update_world), X(update_player),
//

#ifndef TIMER_NAMES
#define TIMER_NAMES
#endif

#define TIMER(name) { \
unsigned long long now = SDL_GetPerformanceCounter(); \
if (!timer_then) timer_then = now; \
timer_times[ timer_curr_id ] += now - timer_then; \
timer_curr_id = timer_ ## name; \
timer_then = now; \
}

// FIXME: push id onto stack if re-entering
#define TIMECALL(f, args) { \
unsigned long long now = SDL_GetPerformanceCounter(); \
if (!timer_then) timer_then = now; \
timer_times[ timer_curr_id ] += now - timer_then; \
timer_then = now; \
(f)args; \
now = SDL_GetPerformanceCounter(); \
timer_times[ timer_ ## f ] += now - timer_then; \
timer_curr_id = timer_; \
timer_then = now; \
}

enum timernames {
#define X(x) timer_ ## x
TIMER_NAMES
#undef X
timer_
};

char timernamesprint[][80] = {
#define X(x) #x
TIMER_NAMES
#undef X
"uncounted"
};

int timer_curr_id = timer_;
unsigned long long timer_then = 0;
unsigned long long timer_times[timer_ + 1] = { 0 };

void timer_print(char *buf, size_t n, bool show_all)
{
char *p = buf;
int i = 0;
unsigned long long sum = 0;
unsigned long long freq = SDL_GetPerformanceFrequency();

for (i = 0; i <= timer_; i++)
sum += timer_times[i];

for (i = 0; i <= timer_; i++)
{
float secs = (float)timer_times[i] / (float)freq;
float pct = 100.f * (float)timer_times[i] / sum;
if ((show_all && secs > 0.f) || pct >= 0.1f || secs >= 0.01f)
p += snprintf(p, n - (p-buf),
"%6.1f %2.0f%% %s\n", secs, pct, timernamesprint[i]);
}
}
67 changes: 67 additions & 0 deletions examples/tinyc.games/utils.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#pragma once

#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define SWAP(a,b) {int *x = &(a); int *y = &(b); int t = *x; *x = *y; *y = t;}
#define CLAMP(lo,x,hi) ((x) < (lo) ? (lo) : (x) > (hi) ? (hi) : (x))

int check_shader_errors(GLuint shader, char *name)
{
GLint success;
GLchar log[1024];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (success) return 0;
glGetShaderInfoLog(shader, 1024, NULL, log);
fprintf(stderr, "ERROR in %s shader program: %s\n", name, log);
exit(1);
return 1;
}

int check_program_errors(GLuint shader, char *name)
{
GLint success;
GLchar log[1024];
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if (success) return 0;
glGetProgramInfoLog(shader, 1024, NULL, log);
fprintf(stderr, "ERROR in %s shader: %s\n", name, log);
exit(1);
return 1;
}

// please free() the returned string
char *file2str(char *filename)
{
FILE *f;

#if defined(_MSC_VER) && _MSC_VER >= 1400
if (fopen_s(&f, filename, "r"))
f = NULL;
#else
f = fopen(filename, "r");
#endif

if (!f) goto bad;
fseek(f, 0, SEEK_END);
size_t sz = ftell(f);
rewind(f);
char *buf = calloc(sz + 1, sizeof *buf);
if (fread(buf, 1, sz, f) != sz) goto bad;
fclose(f);
return buf;

bad:
fprintf(stderr, "Failed to open/read %s\n", filename);
return NULL;
}

unsigned int file2shader(unsigned int type, char *filename)
{
char *code = file2str(filename);
unsigned int id = glCreateShader(type);
glShaderSource(id, 1, (const char *const *)&code, NULL);
glCompileShader(id);
check_shader_errors(id, filename);
free(code);
return id;
}

0 comments on commit a6b0acd

Please sign in to comment.