Initial Commit

This commit is contained in:
Cole
2024-01-11 12:55:43 -06:00
commit 982624bb5b
24 changed files with 746 additions and 0 deletions

13
src/Makefile Normal file
View File

@@ -0,0 +1,13 @@
CC=clang
CFLAGS=-Wall -Wextra -include raylib.h
LIBS=-lraylib -lGL -lm -lpthread -ldl -lrt -lX11
tetris-clone: main.c manager.c renderer.c app.c
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
.PHONY: clean
clean:
rm -f ./tetris-clone
rm -f ./*.o
rm -f ./*.gch

71
src/app.c Normal file
View File

@@ -0,0 +1,71 @@
#include <stdio.h>
#include "raylib.h"
#include "manager.h"
#include "renderer.h"
#include "tetromino.h"
#define TARGET_FPS 60
void EmptyGameBoard(void) {
EmptyAllBlocks();
int numberOfUpdatedBlocks = 0;
struct TetrominoBlock** updatedBlocks = GetUpdatedBlocks(&numberOfUpdatedBlocks);
}
void InitRaylib(void) {
InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Tetris Clone");
SetTargetFPS(TARGET_FPS);
}
void Init(void) {
InitRaylib();
InitManager();
InitRenderer();
EmptyGameBoard();
}
void ProcessInput(void) {
if (IsKeyPressed(KEY_SPACE)) {
RequestRotate();
}
if (IsKeyDown(KEY_A)) {
RequestMoveLeft();
} else if (IsKeyDown(KEY_D)) {
RequestMoveRight();
}
}
void Loop(void) {
int tickRate = 0; // Guaranteed a better way to do this XD
while(!WindowShouldClose()) {
while(!CantSpawnBlock()) {
ProcessInput();
if (tickRate == 0) {
Update();
tickRate = 5;
}
int numberOfUpdatedBlocks = 0;
struct TetrominoBlock** updatedBlocks = GetUpdatedBlocks(&numberOfUpdatedBlocks);
RenderBlocks(updatedBlocks, numberOfUpdatedBlocks);
tickRate--;
}
// TODO present Game over message and score
RenderGameOver();
}
}
void Cleanup() {
CleanupManager();
CloseWindow();
}
void start(void) {
Init();
Loop();
Cleanup();
}

6
src/app.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef TETRIS_CLONE_APP_H_
#define TETRIS_CLONE_APP_H_
void start(void);
#endif // TETRIS_CLONE_APP_H_

7
src/main.c Normal file
View File

@@ -0,0 +1,7 @@
#include "app.h"
int main(void) {
start();
return 0;
}

418
src/manager.c Normal file
View File

@@ -0,0 +1,418 @@
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define __USE_MISC
#include <math.h>
#include "manager.h"
#define FIELD_WIDTH 10
#define FIELD_HEIGHT 20
#define BLOCKS_WITHIN_A_TETROMINO 4
// should probably call these offsets
struct Offset {
int xOffset;
int yOffset;
};
struct Point {
int x;
int y;
};
struct Tetromino {
enum TetrominoType type;
struct TetrominoBlock* blocks[BLOCKS_WITHIN_A_TETROMINO];
};
// To find block (x,y) index = (x % FIELD_WIDTH) + (y * FIELD_WIDTH)
// Height doesn't matter but height is just the factor of how tall the board is.
// And in the end each row is just the next representation of width * units, height just determines row count.
// Doesn't factor into determining X,Y
struct TetrominoBlock* _blocks[FIELD_HEIGHT * FIELD_WIDTH];
struct TetrominoBlock* _updatedBlocks[FIELD_HEIGHT * FIELD_WIDTH] = { NULL };
int _updatedBlockLength = 0;
bool shouldSpawnTetromino;
struct Tetromino fallingTetromino;
const char kSpawnX = FIELD_WIDTH / 2 - 1;
const struct Offset kShiftDownOffset = { .xOffset = 0, .yOffset = 1 };
const struct Offset kShiftLeftOffset = { .xOffset = -1, .yOffset = 0 };
const struct Offset kShiftRightOffset = { .xOffset = 1, .yOffset = 0 };
bool moveLeft = false;
bool moveRight = false;
bool rotate = false;
bool cannotSpawn = false;
void CleanupManager(void) {
for(int i = 0; i < sizeof(_blocks) / sizeof(struct TetrominoBlock*); i++) {
free(_blocks[i]);
}
}
struct TetrominoBlock** GetUpdatedBlocks(int* numberOfUpdatedBlocks) {
*numberOfUpdatedBlocks = _updatedBlockLength;
return &_updatedBlocks[0];
}
void RequestRotate(void) {
rotate = true;
}
void RequestMoveLeft(void) {
moveLeft = true;
moveRight = false;
}
void RequestMoveRight(void) {
moveRight = true;
moveLeft = false;
}
struct TetrominoBlock* GetBlockAtPoint(struct Point point) {
return _blocks[(point.x % FIELD_WIDTH) + (point.y * FIELD_WIDTH)];
}
void RegisterUpdatedBlock(struct TetrominoBlock* pTetrominoBlock) {
if (_updatedBlockLength < FIELD_HEIGHT * FIELD_WIDTH) {
_updatedBlocks[_updatedBlockLength] = pTetrominoBlock;
_updatedBlockLength++;
}
}
void UpdateBlockType(struct TetrominoBlock* pTetrominoBlock, enum TetrominoType type) {
pTetrominoBlock->type = type;
RegisterUpdatedBlock(pTetrominoBlock);
}
void ShiftFallingTetrominoByOffset(struct Offset offset) {
for(int i = 0; i < BLOCKS_WITHIN_A_TETROMINO; i++) {
UpdateBlockType(fallingTetromino.blocks[i], EMPTY);
RegisterUpdatedBlock(fallingTetromino.blocks[i]);
}
for(int i = 0; i < BLOCKS_WITHIN_A_TETROMINO; i++) {
struct Point updatedPoint = { .x = fallingTetromino.blocks[i]->x + offset.xOffset, .y = fallingTetromino.blocks[i]->y + offset.yOffset};
fallingTetromino.blocks[i] = GetBlockAtPoint(updatedPoint);
UpdateBlockType(fallingTetromino.blocks[i], fallingTetromino.type);
RegisterUpdatedBlock(fallingTetromino.blocks[i]);
}
}
// I HATE THIS NAME... but it's verbose for now TODO - RENAME
bool DoesPointIntersectNonFallingBlock(struct Point point) {
bool result = false;
struct TetrominoBlock* blockAtPoint = GetBlockAtPoint(point);
if (blockAtPoint->type != EMPTY) {
result = true;
for(int i = 0; i < BLOCKS_WITHIN_A_TETROMINO; i++) {
if (fallingTetromino.blocks[i] == blockAtPoint) {
result = false;
break;
}
}
}
return result;
}
bool CanBlockFall(struct TetrominoBlock block) {
struct Point newBlockPoint = { .x = block.x, .y = block.y + 1};
if (newBlockPoint.y >= FIELD_HEIGHT) {
return false;
} else if (DoesPointIntersectNonFallingBlock(newBlockPoint)) {
return false;
} else {
return true;
}
}
bool FallingTetrominoCanFall(void) {
bool result = true;
for(int i = 0; i < BLOCKS_WITHIN_A_TETROMINO; i++) {
if (!CanBlockFall(*(fallingTetromino.blocks[i]))) {
result = false;
break;
}
}
return result;
}
void UpdateFallingTetromino(void) {
if(FallingTetrominoCanFall()) {
ShiftFallingTetrominoByOffset(kShiftDownOffset);
} else {
shouldSpawnTetromino = true;
}
}
bool CanBlockMoveLeft(struct TetrominoBlock block) {
struct Point newBlockPoint = { .x = block.x - 1, .y = block.y };
if (newBlockPoint.x < 0) {
return false;
} else if (DoesPointIntersectNonFallingBlock(newBlockPoint)) {
return false;
} else {
return true;
}
}
bool FallingTetrominoCanMoveLeft(void) {
bool result = true;
for(int i = 0; i < BLOCKS_WITHIN_A_TETROMINO; i++) {
if (!CanBlockMoveLeft(*(fallingTetromino.blocks[i]))) {
result = false;
break;
}
}
return result;
}
bool CanBlockMoveRight(struct TetrominoBlock block) {
struct Point newBlockPoint = { .x = block.x + 1, .y = block.y };
if (newBlockPoint.x >= FIELD_WIDTH) {
return false;
} else if (DoesPointIntersectNonFallingBlock(newBlockPoint)) {
return false;
} else {
return true;
}
}
bool FallingTetrominoCanMoveRight(void) {
bool result = true;
for(int i = 0; i < BLOCKS_WITHIN_A_TETROMINO; i++) {
if (!CanBlockMoveRight(*(fallingTetromino.blocks[i]))) {
result = false;
break;
}
}
return result;
}
struct Point GetRotatedPointForBlock(struct Point origin, struct TetrominoBlock block) {
// Only doing counter clockwise for now
struct Point newPoint;
newPoint.x = (block.x - origin.x) * cos(M_PI_2) - (block.y - origin.y) * sin(M_PI_2);
newPoint.y = (block.x - origin.x) * sin(M_PI_2) + (block.y - origin.y) * cos(M_PI_2);
return newPoint;
}
void RotateFallingTetromino(void) {
// Will be a bit buggy because i'm not checking for intersections
struct Point origin = { .x = fallingTetromino.blocks[0]->x, .y = fallingTetromino.blocks[0]->y };
for (int i = 0; i < BLOCKS_WITHIN_A_TETROMINO; i++) {
// Empty the old
UpdateBlockType(fallingTetromino.blocks[i], EMPTY);
RegisterUpdatedBlock(fallingTetromino.blocks[i]);
// Update the new
struct Point rotatedPointAdjustedForOrigin = GetRotatedPointForBlock(origin, *fallingTetromino.blocks[i]);
struct Point newRotatedPoint = { .x = rotatedPointAdjustedForOrigin.x + origin.x, .y = rotatedPointAdjustedForOrigin.y + origin.y };
fallingTetromino.blocks[i] = GetBlockAtPoint(newRotatedPoint);
UpdateBlockType(fallingTetromino.blocks[i], fallingTetromino.type);
RegisterUpdatedBlock(fallingTetromino.blocks[i]);
}
}
void TryRotateFallingTetromino(void) {
// TODO IMPLEMENT
if (fallingTetromino.type == O_TYPE) {
return;
} else {
RotateFallingTetromino();
}
}
void SpawnBlocksAtPoints(enum TetrominoType type, struct Offset* offsets) {
fallingTetromino.type = type;
int index = 0;
while(index < BLOCKS_WITHIN_A_TETROMINO) {
struct Point spawnPoint;
spawnPoint.x = kSpawnX + offsets->xOffset;
spawnPoint.y = offsets->yOffset;
struct TetrominoBlock* spawnBlock = GetBlockAtPoint(spawnPoint);
if (spawnBlock->type != EMPTY) {
cannotSpawn = true;
return;
}
UpdateBlockType(spawnBlock, type);
// I'm not sure if this makes more sense to be here or return a list of spawnedBlocks to be handled in Parent function
fallingTetromino.blocks[index] = spawnBlock;
index++;
offsets++;
}
}
enum TetrominoType GenerateRandomTetrominoType() {
srand(time(NULL));
return (enum TetrominoType) rand() % 6;
}
void SpawnTetromino() {
enum TetrominoType type = GenerateRandomTetrominoType();
struct Offset offsets[BLOCKS_WITHIN_A_TETROMINO];
if (type == O_TYPE) {
offsets[0].xOffset = 0;
offsets[0].yOffset = 0;
offsets[1].xOffset = 1;
offsets[1].yOffset = 0;
offsets[2].xOffset = 0;
offsets[2].yOffset = 1;
offsets[3].xOffset = 1;
offsets[3].yOffset = 1;
} else if (type == J_TYPE) {
offsets[0].xOffset = 0;
offsets[0].yOffset = 0;
offsets[1].xOffset = 0;
offsets[1].yOffset = 1;
offsets[2].xOffset = 1;
offsets[2].yOffset = 1;
offsets[3].xOffset = 2;
offsets[3].yOffset = 1;
} else if (type == L_TYPE) {
offsets[0].xOffset = 0;
offsets[0].yOffset = 1;
offsets[1].xOffset = 1;
offsets[1].yOffset = 1;
offsets[2].xOffset = 2;
offsets[2].yOffset = 1;
offsets[3].xOffset = 2;
offsets[3].yOffset = 0;
} else if (type == I_TYPE) {
offsets[0].xOffset = 0;
offsets[0].yOffset = 0;
offsets[1].xOffset = 0;
offsets[1].yOffset = 1;
offsets[2].xOffset = 0;
offsets[2].yOffset = 2;
offsets[3].xOffset = 0;
offsets[3].yOffset = 3;
} else if (type == S_TYPE) {
offsets[0].xOffset = 0;
offsets[0].yOffset = 1;
offsets[1].xOffset = 1;
offsets[1].yOffset = 1;
offsets[2].xOffset = 1;
offsets[2].yOffset = 0;
offsets[3].xOffset = 2;
offsets[3].yOffset = 0;
} else if (type == Z_TYPE) {
offsets[0].xOffset = 0;
offsets[0].yOffset = 0;
offsets[1].xOffset = 1;
offsets[1].yOffset = 0;
offsets[2].xOffset = 1;
offsets[2].yOffset = 1;
offsets[3].xOffset = 2;
offsets[3].yOffset = 1;
} else if (type == T_TYPE) {
offsets[0].xOffset = 1;
offsets[0].yOffset = 0;
offsets[1].xOffset = 0;
offsets[1].yOffset = 1;
offsets[2].xOffset = 1;
offsets[2].yOffset = 1;
offsets[3].xOffset = 2;
offsets[3].yOffset = 1;
}
SpawnBlocksAtPoints(type, offsets);
}
void DeregisterUpdatedBlocks() {
_updatedBlockLength = 0;
}
void Update(void) {
DeregisterUpdatedBlocks();
if (shouldSpawnTetromino) {
moveLeft = false;
moveRight = false;
SpawnTetromino();
shouldSpawnTetromino = false;
} else {
// TODO move left and right refactor naming
if(rotate) {
TryRotateFallingTetromino();
rotate = false;
}
if (moveRight && FallingTetrominoCanMoveRight()) {
ShiftFallingTetrominoByOffset(kShiftRightOffset);
moveRight = false;
}
if (moveLeft && FallingTetrominoCanMoveLeft()) {
ShiftFallingTetrominoByOffset(kShiftLeftOffset);
moveLeft = false;
}
UpdateFallingTetromino();
}
}
bool CantSpawnBlock(void) {
return cannotSpawn;
}
void EmptyAllBlocks(void) {
for (int i = 0; i < FIELD_HEIGHT * FIELD_WIDTH; i++) {
UpdateBlockType(_blocks[i], EMPTY);
}
}
void AllocBlocks(void) {
for (int i = 0; i < FIELD_HEIGHT * FIELD_WIDTH; i++) {
struct TetrominoBlock* block = calloc(1, sizeof(struct TetrominoBlock));
block->x = i % FIELD_WIDTH;
block->y = i / FIELD_WIDTH;
_blocks[i] = block;
}
}
void InitManager(void) {
AllocBlocks();
shouldSpawnTetromino = true;
}

18
src/manager.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef TETRIS_CLONE_MANAGER_H_
#define TETRIS_CLONE_MANAGER_H_
#include <stdbool.h>
#include "tetromino.h"
void InitManager(void);
void EmptyAllBlocks(void);
bool CantSpawnBlock(void);
void Update(void);
void RequestRotate(void);
void RequestMoveLeft(void);
void RequestMoveRight(void);
struct TetrominoBlock** GetUpdatedBlocks(int* lengthOfBlocksUpdated);
void CleanupManager(void);
#endif

76
src/renderer.c Normal file
View File

@@ -0,0 +1,76 @@
#include <stdio.h>
#include "raylib.h"
#include "renderer.h"
#define PNG_RES(name) "resources/" #name ".png"
#define BLOCK_SIZE 32
Texture2D yellowTexture;
Texture2D blueTexture;
Texture2D lightBlueTexture;
Texture2D redTexture;
Texture2D purpleTexture;
Texture2D greenTexture;
Texture2D orangeTexture;
Texture2D emptyTexture;
Texture2D gameOverTexture;
void InitRenderer(void) {
yellowTexture = LoadTexture(PNG_RES(yellow));
blueTexture = LoadTexture(PNG_RES(blue));
lightBlueTexture = LoadTexture(PNG_RES(light-blue));
redTexture = LoadTexture(PNG_RES(red));
purpleTexture = LoadTexture(PNG_RES(purple));
greenTexture = LoadTexture(PNG_RES(green));
orangeTexture = LoadTexture(PNG_RES(orange));
emptyTexture = LoadTexture(PNG_RES(black));
gameOverTexture = LoadTexture(PNG_RES(gameOver));
}
Texture2D* GetTextureFromTetrominoType(enum TetrominoType type) {
switch (type) {
case O_TYPE:
return &yellowTexture;
case I_TYPE:
return &lightBlueTexture;
case T_TYPE:
return &purpleTexture;
case J_TYPE:
return &blueTexture;
case L_TYPE:
return &orangeTexture;
case S_TYPE:
return &greenTexture;
case Z_TYPE:
return &redTexture;
default:
return &emptyTexture;
}
}
void RenderTetrominoBlock(struct TetrominoBlock blockToRender) {
Texture2D* textureToRender = GetTextureFromTetrominoType(blockToRender.type);
DrawTexture(*textureToRender, blockToRender.x * BLOCK_SIZE, blockToRender.y * BLOCK_SIZE, WHITE);
}
void RenderBlocks(struct TetrominoBlock** updatedBlocks, int length) {
BeginDrawing();
while(length--) {
RenderTetrominoBlock(*(*updatedBlocks));
updatedBlocks++;
}
EndDrawing();
}
void RenderGameOver(void) {
BeginDrawing();
ClearBackground(BLACK);
DrawText("Game Over", SCREEN_WIDTH / 2 - MeasureText("Game Over", 24) / 2, 200, 24, RED);
DrawText("Score: N/A", SCREEN_WIDTH / 2 - MeasureText("Score: N/A", 18) / 2, 232, 18, RED);
EndDrawing();
}

13
src/renderer.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef TETRIS_CLONE_RENDERER_H_
#define TETRIS_CLONE_RENDERER_H_
#include "tetromino.h"
#define SCREEN_HEIGHT 640
#define SCREEN_WIDTH 320
void InitRenderer(void);
void RenderBlocks(struct TetrominoBlock** updatedBlocks, int length);
void RenderGameOver(void);
#endif

BIN
src/resources/black.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

BIN
src/resources/blue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

BIN
src/resources/green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

BIN
src/resources/orange.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

BIN
src/resources/purple.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

BIN
src/resources/red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 B

BIN
src/resources/yellow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

BIN
src/tetris-clone Executable file

Binary file not shown.

21
src/tetromino.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef TETRIS_CLONE_TETROMINO_H_
#define TETRIS_CLONE_TETROMINO_H_
enum TetrominoType {
I_TYPE,
J_TYPE,
L_TYPE,
O_TYPE,
S_TYPE,
T_TYPE,
Z_TYPE,
EMPTY
};
struct TetrominoBlock {
unsigned char x;
unsigned char y;
enum TetrominoType type;
};
#endif