Initial Commit
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
./src/tetris-clone
|
||||||
18
.vscode/c_cpp_properties.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/**",
|
||||||
|
"~/src/raylib/src/**",
|
||||||
|
"/usr/include/**"
|
||||||
|
],
|
||||||
|
"defines": [],
|
||||||
|
"compilerPath": "/usr/bin/gcc",
|
||||||
|
"cStandard": "c99",
|
||||||
|
"cppStandard": "gnu++17",
|
||||||
|
"intelliSenseMode": "linux-gcc-x64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
6
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"*.m": "c",
|
||||||
|
"tetromino.h": "c"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 [fullname]
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
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 Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
51
README.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Tetris Clone
|
||||||
|
|
||||||
|
Creating a Tetris clone in C to practice C programming and learn more about Raylib (awesome library btw!).
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This game is a clone of the popular game Tetris. This clone features what you would expect from a simplistic version of Tetris, namely: falling blocks, which upon completion of a row disappear, score keeping and levels with incremental difficulty.
|
||||||
|
|
||||||
|
I hope you enjoy!
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
* Describe any prerequisites, libraries, OS version, etc., needed before installing program.
|
||||||
|
|
||||||
|
### Installing
|
||||||
|
|
||||||
|
* How/where to download your program
|
||||||
|
* Any modifications needed to be made to files/folders
|
||||||
|
|
||||||
|
### Executing program
|
||||||
|
|
||||||
|
* How to run the program
|
||||||
|
* Step-by-step bullets
|
||||||
|
```
|
||||||
|
code blocks for commands
|
||||||
|
```
|
||||||
|
|
||||||
|
## Help
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
|
||||||
|
Contributors names and contact info
|
||||||
|
|
||||||
|
John Landers [jcolelanders@gmail.com](mailto:jcolelanders@gmail.com)
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
* 0.1
|
||||||
|
* Initial Release
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the LICENSE file for details
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
Inspiration, code snippets, etc.
|
||||||
|
* [Tetris](https://en.wikipedia.org/wiki/Tetris)
|
||||||
|
* [raylib](https://github.com/raysan5/raylib)
|
||||||
6
TODO
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
- Rotate (Partially Implemented/Buggy. Should try to understand Rotation Matrix better.)
|
||||||
|
- Empty rows that are full should "break"
|
||||||
|
- Shift rows down after row "breaks"
|
||||||
|
- Track and Display Score. During and After game
|
||||||
|
- display Upcoming block
|
||||||
|
- Introduce levels/speed up with every level
|
||||||
13
src/Makefile
Normal 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
@@ -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
@@ -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
@@ -0,0 +1,7 @@
|
|||||||
|
#include "app.h"
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
start();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
418
src/manager.c
Normal 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
@@ -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
@@ -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
@@ -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
|
After Width: | Height: | Size: 881 B |
BIN
src/resources/blue.png
Normal file
|
After Width: | Height: | Size: 900 B |
BIN
src/resources/green.png
Normal file
|
After Width: | Height: | Size: 897 B |
BIN
src/resources/light-blue.png
Normal file
|
After Width: | Height: | Size: 900 B |
BIN
src/resources/orange.png
Normal file
|
After Width: | Height: | Size: 900 B |
BIN
src/resources/purple.png
Normal file
|
After Width: | Height: | Size: 138 B |
BIN
src/resources/red.png
Normal file
|
After Width: | Height: | Size: 899 B |
BIN
src/resources/yellow.png
Normal file
|
After Width: | Height: | Size: 901 B |
BIN
src/tetris-clone
Executable file
21
src/tetromino.h
Normal 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
|
||||||