Skip to content

Instantly share code, notes, and snippets.

@rygo6
Last active June 24, 2026 21:59
Show Gist options
  • Select an option

  • Save rygo6/11a8c8d29f91070d2af2805f7cc09f7c to your computer and use it in GitHub Desktop.

Select an option

Save rygo6/11a8c8d29f91070d2af2805f7cc09f7c to your computer and use it in GitHub Desktop.
C99 Tetris - RayLib - Claude Opus 4.8 One-Shot
// Tetris in C99 with raylib.
// Created entirely by Claude Opus 4.8 with the prompt
// "create a C99 applicaiton with raylib that lets me play tetris"
#include "raylib.h"
#include <stdlib.h>
#include <string.h>
#define COLS 10
#define ROWS 20
#define CELL 32
#define HIDDEN 2 // spawn rows above the visible field
#define BOARD_W (COLS * CELL)
#define BOARD_H (ROWS * CELL)
#define MARGIN 24
#define PANEL_W 200
#define SCREEN_W (MARGIN + BOARD_W + MARGIN + PANEL_W + MARGIN)
#define SCREEN_H (MARGIN + BOARD_H + MARGIN)
// The 7 tetrominoes, each as 4 rotation states of 4 (x,y) cells in a 4x4 box.
// Layout follows the SRS-ish convention; values are column,row offsets.
static const int SHAPES[7][4][4][2] = {
// I
{{{0,1},{1,1},{2,1},{3,1}}, {{2,0},{2,1},{2,2},{2,3}},
{{0,2},{1,2},{2,2},{3,2}}, {{1,0},{1,1},{1,2},{1,3}}},
// J
{{{0,0},{0,1},{1,1},{2,1}}, {{1,0},{2,0},{1,1},{1,2}},
{{0,1},{1,1},{2,1},{2,2}}, {{1,0},{1,1},{0,2},{1,2}}},
// L
{{{2,0},{0,1},{1,1},{2,1}}, {{1,0},{1,1},{1,2},{2,2}},
{{0,1},{1,1},{2,1},{0,2}}, {{0,0},{1,0},{1,1},{1,2}}},
// O
{{{1,0},{2,0},{1,1},{2,1}}, {{1,0},{2,0},{1,1},{2,1}},
{{1,0},{2,0},{1,1},{2,1}}, {{1,0},{2,0},{1,1},{2,1}}},
// S
{{{1,0},{2,0},{0,1},{1,1}}, {{1,0},{1,1},{2,1},{2,2}},
{{1,1},{2,1},{0,2},{1,2}}, {{0,0},{0,1},{1,1},{1,2}}},
// T
{{{1,0},{0,1},{1,1},{2,1}}, {{1,0},{1,1},{2,1},{1,2}},
{{0,1},{1,1},{2,1},{1,2}}, {{1,0},{0,1},{1,1},{1,2}}},
// Z
{{{0,0},{1,0},{1,1},{2,1}}, {{2,0},{1,1},{2,1},{1,2}},
{{0,1},{1,1},{1,2},{2,2}}, {{1,0},{0,1},{1,1},{0,2}}},
};
static const Color COLORS[8] = {
{15, 15, 24, 255}, // 0 empty
{0, 240, 240, 255}, // I cyan
{0, 0, 240, 255}, // J blue
{240, 160, 0, 255}, // L orange
{240, 240, 0, 255}, // O yellow
{0, 240, 0, 255}, // S green
{160, 0, 240, 255}, // T purple
{240, 0, 0, 255}, // Z red
};
typedef struct {
int type; // 0..6
int rot; // 0..3
int x, y; // position of the 4x4 box top-left, in board cells
} Piece;
static int board[ROWS + HIDDEN][COLS]; // 0 empty, else color index 1..7
static Piece cur, next;
static int score, lines, level;
static double fallTimer, fallInterval;
static bool gameOver, paused;
static int bag[7];
static int bagIndex = 7; // forces a refill on first draw
static int RandRange(int lo, int hi) { return GetRandomValue(lo, hi); }
static void ShuffleBag(void) {
for (int i = 0; i < 7; i++) bag[i] = i;
for (int i = 6; i > 0; i--) {
int j = RandRange(0, i);
int t = bag[i]; bag[i] = bag[j]; bag[j] = t;
}
bagIndex = 0;
}
static int NextType(void) {
if (bagIndex >= 7) ShuffleBag();
return bag[bagIndex++];
}
static Piece SpawnPiece(int type) {
Piece p = {0};
p.type = type;
p.rot = 0;
p.x = 3;
p.y = 0; // box top sits in hidden rows
return p;
}
// True if piece at given state collides with walls, floor or settled blocks.
static bool Collides(const Piece *p) {
for (int i = 0; i < 4; i++) {
int cx = p->x + SHAPES[p->type][p->rot][i][0];
int cy = p->y + SHAPES[p->type][p->rot][i][1];
if (cx < 0 || cx >= COLS || cy >= ROWS + HIDDEN) return true;
if (cy < 0) continue;
if (board[cy][cx]) return true;
}
return false;
}
static void LockPiece(void) {
for (int i = 0; i < 4; i++) {
int cx = cur.x + SHAPES[cur.type][cur.rot][i][0];
int cy = cur.y + SHAPES[cur.type][cur.rot][i][1];
if (cy >= 0 && cy < ROWS + HIDDEN && cx >= 0 && cx < COLS)
board[cy][cx] = cur.type + 1;
}
}
static void ClearLines(void) {
int cleared = 0;
for (int y = ROWS + HIDDEN - 1; y >= 0; y--) {
bool full = true;
for (int x = 0; x < COLS; x++) if (!board[y][x]) { full = false; break; }
if (full) {
for (int yy = y; yy > 0; yy--) memcpy(board[yy], board[yy - 1], sizeof(board[0]));
memset(board[0], 0, sizeof(board[0]));
cleared++;
y++; // recheck the row that shifted down
}
}
if (cleared > 0) {
static const int table[5] = {0, 100, 300, 500, 800};
score += table[cleared] * level;
lines += cleared;
level = 1 + lines / 10;
fallInterval = 0.8 - (level - 1) * 0.07;
if (fallInterval < 0.08) fallInterval = 0.08;
}
}
static void NewPiece(void) {
cur = next;
next = SpawnPiece(NextType());
if (Collides(&cur)) gameOver = true;
}
static void TryRotate(int dir) {
Piece t = cur;
t.rot = (cur.rot + dir + 4) % 4;
int kicks[5] = {0, -1, 1, -2, 2}; // simple wall kicks
for (int k = 0; k < 5; k++) {
t.x = cur.x + kicks[k];
if (!Collides(&t)) { cur = t; return; }
}
}
static void TryMove(int dx) {
Piece t = cur;
t.x += dx;
if (!Collides(&t)) cur = t;
}
// Returns true if the piece moved down, false if it locked.
static bool SoftDrop(void) {
Piece t = cur;
t.y += 1;
if (!Collides(&t)) { cur = t; return true; }
LockPiece();
ClearLines();
NewPiece();
return false;
}
static void HardDrop(void) {
while (true) {
Piece t = cur;
t.y += 1;
if (Collides(&t)) break;
cur = t;
score += 2;
}
LockPiece();
ClearLines();
NewPiece();
fallTimer = 0;
}
static int GhostY(void) {
Piece t = cur;
while (true) {
Piece n = t;
n.y += 1;
if (Collides(&n)) break;
t = n;
}
return t.y;
}
static void ResetGame(void) {
memset(board, 0, sizeof(board));
score = 0; lines = 0; level = 1;
fallInterval = 0.8;
fallTimer = 0;
gameOver = false; paused = false;
bagIndex = 7;
next = SpawnPiece(NextType());
NewPiece();
}
static void DrawCell(int px, int py, int colorIndex) {
Rectangle r = {(float)px, (float)py, CELL, CELL};
DrawRectangleRec(r, COLORS[colorIndex]);
DrawRectangleLinesEx(r, 1, (Color){0, 0, 0, 90});
DrawRectangle(px + 2, py + 2, CELL - 4, 4, (Color){255, 255, 255, 40}); // top highlight
}
static void DrawPieceAt(int type, int rot, int originX, int originY, int colorIndex) {
for (int i = 0; i < 4; i++) {
int cx = SHAPES[type][rot][i][0];
int cy = SHAPES[type][rot][i][1];
DrawCell(originX + cx * CELL, originY + cy * CELL, colorIndex);
}
}
int main(void) {
InitWindow(SCREEN_W, SCREEN_H, "Tetris");
SetTargetFPS(60);
SetRandomSeed((unsigned int)(GetTime() * 1000000.0) ^ 0x9E3779B9u);
ResetGame();
const int boardX = MARGIN;
const int boardY = MARGIN;
const int panelX = MARGIN + BOARD_W + MARGIN;
double moveTimer = 0, moveInterval = 0.09; // horizontal autorepeat
while (!WindowShouldClose()) {
double dt = GetFrameTime();
if (gameOver) {
if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_SPACE)) ResetGame();
} else if (IsKeyPressed(KEY_P)) {
paused = !paused;
} else if (!paused) {
if (IsKeyPressed(KEY_LEFT)) { TryMove(-1); moveTimer = -0.16; }
if (IsKeyPressed(KEY_RIGHT)) { TryMove(1); moveTimer = -0.16; }
if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT)) {
moveTimer += dt;
if (moveTimer >= moveInterval) {
moveTimer = 0;
if (IsKeyDown(KEY_LEFT)) TryMove(-1);
if (IsKeyDown(KEY_RIGHT)) TryMove(1);
}
} else moveTimer = 0;
if (IsKeyPressed(KEY_UP) || IsKeyPressed(KEY_X)) TryRotate(1);
if (IsKeyPressed(KEY_Z) || IsKeyPressed(KEY_LEFT_CONTROL)) TryRotate(-1);
if (IsKeyPressed(KEY_SPACE)) HardDrop();
fallTimer += dt;
double interval = IsKeyDown(KEY_DOWN) ? fallInterval * 0.08 : fallInterval;
if (fallTimer >= interval) {
fallTimer = 0;
if (SoftDrop() && IsKeyDown(KEY_DOWN)) score += 1;
}
}
BeginDrawing();
ClearBackground((Color){24, 24, 32, 255});
// Board background and grid.
DrawRectangle(boardX, boardY, BOARD_W, BOARD_H, COLORS[0]);
for (int x = 0; x <= COLS; x++)
DrawLine(boardX + x * CELL, boardY, boardX + x * CELL, boardY + BOARD_H, (Color){40, 40, 55, 255});
for (int y = 0; y <= ROWS; y++)
DrawLine(boardX, boardY + y * CELL, boardX + BOARD_W, boardY + y * CELL, (Color){40, 40, 55, 255});
// Settled blocks (skip hidden rows).
for (int y = 0; y < ROWS; y++)
for (int x = 0; x < COLS; x++)
if (board[y + HIDDEN][x])
DrawCell(boardX + x * CELL, boardY + y * CELL, board[y + HIDDEN][x]);
if (!gameOver) {
// Ghost piece.
int gy = GhostY();
for (int i = 0; i < 4; i++) {
int cx = cur.x + SHAPES[cur.type][cur.rot][i][0];
int cy = gy + SHAPES[cur.type][cur.rot][i][1] - HIDDEN;
if (cy >= 0) {
Rectangle r = {(float)(boardX + cx * CELL), (float)(boardY + cy * CELL), CELL, CELL};
DrawRectangleLinesEx(r, 2, Fade(COLORS[cur.type + 1], 0.45f));
}
}
// Active piece.
for (int i = 0; i < 4; i++) {
int cx = cur.x + SHAPES[cur.type][cur.rot][i][0];
int cy = cur.y + SHAPES[cur.type][cur.rot][i][1] - HIDDEN;
if (cy >= 0) DrawCell(boardX + cx * CELL, boardY + cy * CELL, cur.type + 1);
}
}
// Side panel.
DrawText("NEXT", panelX, boardY, 24, RAYWHITE);
DrawRectangle(panelX, boardY + 34, 4 * CELL, 4 * CELL, COLORS[0]);
DrawPieceAt(next.type, 0, panelX, boardY + 34, next.type + 1);
int ty = boardY + 34 + 4 * CELL + 30;
DrawText("SCORE", panelX, ty, 24, RAYWHITE);
DrawText(TextFormat("%d", score), panelX, ty + 28, 28, COLORS[5]);
DrawText("LINES", panelX, ty + 76, 24, RAYWHITE);
DrawText(TextFormat("%d", lines), panelX, ty + 104, 28, COLORS[1]);
DrawText("LEVEL", panelX, ty + 152, 24, RAYWHITE);
DrawText(TextFormat("%d", level), panelX, ty + 180, 28, COLORS[3]);
DrawText("Move: \xE2\x86\x90 \xE2\x86\x92", panelX, ty + 236, 18, GRAY);
DrawText("Rotate: \xE2\x86\x91 / Z X", panelX, ty + 258, 18, GRAY);
DrawText("Soft drop: \xE2\x86\x93", panelX, ty + 280, 18, GRAY);
DrawText("Hard drop: Space", panelX, ty + 302, 18, GRAY);
DrawText("Pause: P", panelX, ty + 324, 18, GRAY);
if (paused) {
DrawRectangle(boardX, boardY + BOARD_H / 2 - 30, BOARD_W, 60, Fade(BLACK, 0.7f));
DrawText("PAUSED", boardX + BOARD_W / 2 - MeasureText("PAUSED", 36) / 2,
boardY + BOARD_H / 2 - 18, 36, RAYWHITE);
}
if (gameOver) {
DrawRectangle(boardX, boardY + BOARD_H / 2 - 60, BOARD_W, 120, Fade(BLACK, 0.78f));
DrawText("GAME OVER", boardX + BOARD_W / 2 - MeasureText("GAME OVER", 36) / 2,
boardY + BOARD_H / 2 - 44, 36, COLORS[7]);
const char *msg = "Press Enter to restart";
DrawText(msg, boardX + BOARD_W / 2 - MeasureText(msg, 20) / 2,
boardY + BOARD_H / 2 + 8, 20, RAYWHITE);
}
EndDrawing();
}
CloseWindow();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment