Last active
June 24, 2026 21:59
-
-
Save rygo6/11a8c8d29f91070d2af2805f7cc09f7c to your computer and use it in GitHub Desktop.
C99 Tetris - RayLib - Claude Opus 4.8 One-Shot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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