Compare commits

...

10 Commits

Author SHA1 Message Date
80c46a13ba last commit
Some checks failed
.NET / build (push) Has been cancelled
2025-01-12 15:41:06 +01:00
120c3a90e1 Rotation check 2024-11-13 17:01:28 +01:00
e6ec2633e1 Idk 2024-11-13 16:39:36 +01:00
5b905616c1 shapes edit, SRS and FS 2024-10-19 12:31:42 +02:00
1b03e31ffc Merge remote-tracking branch 'origin/master' 2024-10-09 21:40:29 +02:00
781fb1376d Remove sound player, edit readme 2024-10-09 21:40:22 +02:00
a23a99e93d
Create dotnet.yml 2024-10-09 19:25:45 +00:00
ef31b3cb03 SRS specifications edit 2024-10-09 21:21:33 +02:00
3f06d2eb2e sound 2024-10-09 12:17:20 +02:00
47afe2c0f9 blocks in external file images srs 2024-10-09 11:40:02 +02:00
14 changed files with 776 additions and 305 deletions

26
.github/workflows/dotnet.yml vendored Executable file
View File

@ -0,0 +1,26 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
name: .NET
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore

0
.gitignore vendored Normal file → Executable file
View File

BIN
ARS-pieces.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

BIN
GamePrev.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

605
Program.cs Normal file → Executable file
View File

@ -1,323 +1,332 @@
using System;
using System.Media;
using System.Threading;
namespace Tetris
{
public class Tetris
public class Tetris
{
private static int keyDelay = 50; // Delay between keypress handling (in ms)
private static DateTime lastKeyPressTime = DateTime.Now;
public static void Main(string[] args)
{
private static int keyDelay = 50; // Delay between keypress handling (in ms)
private static DateTime lastKeyPressTime = DateTime.Now;
Matrix matrix = new Matrix();
Shape shape = new Shape(matrix);
int previousScore = 0;
public static void Main(string[] args)
while (true)
{
matrix.Print();
shape.Draw();
HandleInput(shape);
if (!shape.MoveDown())
{
Matrix matrix = new Matrix();
Shape shape = new Shape(matrix);
int previousScore = 0;
while (true)
{
matrix.Print();
shape.Draw();
HandleInput(shape);
if (!shape.MoveDown())
{
shape = new Shape(matrix);
matrix.CheckForCompleteLines();
if (matrix.IsGameOver() == true)
{
Console.WriteLine("Game Over!");
break;
}
}
int score = matrix.GetScore();
if (score >= previousScore + 800)
{
Speed.SpeedUp();
previousScore = matrix.GetScore();
}
System.Threading.Thread.Sleep(Speed.ShowSpeed());
}
shape = new Shape(matrix);
matrix.CheckForCompleteLines();
if (matrix.IsGameOver() == true)
{
Console.WriteLine("Game Over!");
break;
}
}
// TODO: Improve input handling
private static void HandleInput(Shape shape)
int score = matrix.GetScore();
if (score >= previousScore + 800)
{
for (int i = 0; i < 5; i++)
{
if (Console.KeyAvailable)
{
ConsoleKeyInfo key = Console.ReadKey(true);
if (key.Key == ConsoleKey.LeftArrow)
shape.MoveLeft();
else if (key.Key == ConsoleKey.RightArrow)
shape.MoveRight();
else if (key.Key == ConsoleKey.UpArrow)
shape.Rotate();
else if (key.Key == ConsoleKey.DownArrow)
shape.MoveDown();
}
}
Speed.SpeedUp();
previousScore = matrix.GetScore();
}
System.Threading.Thread.Sleep(Speed.ShowSpeed());
}
}
public class Speed()
// TODO: Improve input handling
private static void HandleInput(Shape shape)
{
private static int speed = 400;
public static int ShowSpeed()
for (int i = 0; i < 5; i++)
{
if (Console.KeyAvailable)
{
return speed;
}
ConsoleKeyInfo key = Console.ReadKey(true);
public static void SpeedUp()
{
speed -= 50;
if (key.Key == ConsoleKey.LeftArrow)
shape.MoveLeft();
else if (key.Key == ConsoleKey.RightArrow)
shape.MoveRight();
else if (key.Key == ConsoleKey.UpArrow)
shape.Rotate();
else if (key.Key == ConsoleKey.DownArrow)
shape.MoveDown();
}
}
}
}
public class Speed()
{
private static int speed = 400;
public static int ShowSpeed() { return speed; }
public static void SpeedUp() { speed -= 50; }
}
public class Shape
{
private int[,] current_shape;
private int randomRotation;
private int randomShape;
private static List<List<int[,]>> shapes = ShapesData.shapes;
private static List<int[,]> shape;
private int x, y;
private Matrix matrix;
public Shape(Matrix matrix)
{
Random random = new Random();
randomShape = random.Next(0, shapes.Count);
shape = shapes[randomShape];
randomRotation = random.Next(0, shape.Count);
current_shape = shape[randomRotation];
this.matrix = matrix;
matrix.IncreaseScore(10);
this.x = 0; // Starting position
this.y = 8; // Center of the grid
}
public class Shape
public void Draw()
{
private int[,] current_shape;
private int randomRotation;
private int randomShape;
private static List<List<int[,]>> shapes = ShapesData.shapes;
private static List<int[,]> shape;
private int x, y;
private Matrix matrix;
public Shape(Matrix matrix)
{
Random random = new Random();
randomShape = random.Next(0, shapes.Count);
shape = shapes[randomShape];
randomRotation = random.Next(0, shape.Count);
current_shape = shape[randomRotation];
this.matrix = matrix;
matrix.IncreaseScore(10);
// TODO: Implement random color
this.x = 0; // Starting position
this.y = 8; // Center of the grid
}
public void Draw()
{
for (int i = 0; i < current_shape.GetLength(0); i++)
{
matrix.SetBlock(x + current_shape[i, 0], y + current_shape[i, 1], '█');
}
}
public bool MoveDown()
{
if (CanMove(x + 1, y))
{
Clear();
x++;
Draw();
return true;
}
return false;
}
public void MoveLeft()
{
if (CanMove(x, y - 1))
{
Clear();
y--;
y--;
Draw();
}
}
public void MoveRight()
{
if (CanMove(x, y + 1))
{
Clear();
y++;
y++;
Draw();
}
}
public void Rotate()
{
Clear();
randomRotation = (randomRotation + 1) % shape.Count;
// !IMPORTANT: Check if rotation is possible
current_shape = shape[randomRotation];
Draw();
}
private bool IsPartOfShape(int matrixX, int matrixY)
{
for (int i = 0; i < current_shape.GetLength(0); i++)
{
if (x + current_shape[i, 0] == matrixX && y + current_shape[i, 1] == matrixY)
{
return true;
}
}
return false;
}
private bool CanMove(int newX, int newY)
{
for (int i = 0; i < current_shape.GetLength(0); i++)
{
int newBlockX = newX + current_shape[i, 0];
int newBlockY = newY + current_shape[i, 1];
if (newBlockX >= Matrix.HEIGHT || newBlockX < 0 || newBlockY < 0 || newBlockY >= Matrix.WIDTH)
{
return false;
}
if (matrix.GetBlock(newBlockX, newBlockY) != ' ' && !IsPartOfShape(newBlockX, newBlockY))
{
return false;
}
}
return true;
}
private void Clear()
{
for (int i = 0; i < current_shape.GetLength(0); i++)
{
matrix.ClearBlock(x + current_shape[i, 0], y + current_shape[i, 1]);
}
}
for (int i = 0; i < current_shape.GetLength(0); i++)
{
matrix.SetBlock(x + current_shape[i, 0], y + current_shape[i, 1], '█');
}
}
public class Matrix
public bool MoveDown()
{
public const int HEIGHT = 22;
public const int WIDTH = 20;
private char[,] matrix;
private int score = 0;
public Matrix()
{
matrix = new char[HEIGHT, WIDTH];
Clear();
}
public void SetBlock(int x, int y, char c)
{
if (IsInBounds(x, y))
{
matrix[x, y] = c;
}
}
public void ClearBlock(int x, int y)
{
if (IsInBounds(x, y))
{
matrix[x, y] = ' ';
}
}
public char GetBlock(int x, int y)
{
return IsInBounds(x, y) ? matrix[x, y] : 'E';
}
public void Print()
{
Console.Clear();
for (int i = 0; i < HEIGHT; i++)
{
Console.Write("│ ");
for (int j = 0; j < WIDTH; j++)
{
Console.ForegroundColor = matrix[i, j] == ' ' ? ConsoleColor.DarkBlue : ConsoleColor.DarkBlue;
Console.Write(matrix[i, j]);
}
Console.ResetColor();
Console.WriteLine(" │");
}
Console.WriteLine(" ──────────────────────");
Console.WriteLine("Score: " + score);
}
public int GetScore()
{
return score;
}
public void IncreaseScore(int score)
{
this.score += score;
}
public void CheckForCompleteLines()
{
for (int i = 0; i < HEIGHT; i++)
{
bool isComplete = true;
for (int j = 0; j < WIDTH; j++)
{
if (matrix[i, j] == ' ')
{
isComplete = false;
break;
}
}
if (isComplete)
{
// Remove line
IncreaseScore(100);
for (int j = 0; j < WIDTH; j++)
{
matrix[i, j] = ' ';
}
// Move all lines above one down
for (int k = i; k > 0; k--)
{
for (int j = 0; j < WIDTH; j++)
{
matrix[k, j] = matrix[k - 1, j];
}
}
}
}
}
public bool IsGameOver()
{
for (int i = 0; i < WIDTH; i++)
{
if (matrix[0, i] != ' ')
{
return true;
}
}
return false;
}
public void Clear()
{
for (int i = 0; i < HEIGHT; i++)
{
for (int j = 0; j < WIDTH; j++)
{
matrix[i, j] = ' ';
}
}
}
private bool IsInBounds(int x, int y)
{
return x >= 0 && x < HEIGHT && y >= 0 && y < WIDTH;
}
if (CanMove(x + 1, y))
{
Clear();
x++;
Draw();
return true;
}
return false;
}
public void MoveLeft()
{
if (CanMove(x, y - 1))
{
Clear();
y--;
y--;
Draw();
}
}
public void MoveRight()
{
if (CanMove(x, y + 1))
{
Clear();
y++;
y++;
Draw();
}
}
public void Rotate()
{
int nextRotation = (randomRotation + 1) % shape.Count;
int[,] nextShape = shape[nextRotation];
if (CanRotate(nextShape))
{
Clear();
current_shape = nextShape;
randomRotation = nextRotation;
Draw();
}
}
private bool CanRotate(int[,] newShape)
{
for (int i = 0; i < newShape.GetLength(0); i++)
{
int newX = x + newShape[i, 0];
int newY = y + newShape[i, 1];
if (newX >= Matrix.HEIGHT || newX < 0 || newY < 0 || newY >= Matrix.WIDTH ||
(matrix.GetBlock(newX, newY) != ' ' && !IsPartOfShape(newX, newY)))
{
return false;
}
}
return true;
}
private bool IsPartOfShape(int matrixX, int matrixY)
{
for (int i = 0; i < current_shape.GetLength(0); i++)
{
if (x + current_shape[i, 0] == matrixX && y + current_shape[i, 1] == matrixY)
{
return true;
}
}
return false;
}
private bool CanMove(int newX, int newY)
{
for (int i = 0; i < current_shape.GetLength(0); i++)
{
int newBlockX = newX + current_shape[i, 0];
int newBlockY = newY + current_shape[i, 1];
if (newBlockX >= Matrix.HEIGHT || newBlockX < 0 || newBlockY < 0 ||
newBlockY >= Matrix.WIDTH ||
(matrix.GetBlock(newBlockX, newBlockY) != ' ' && !IsPartOfShape(newBlockX, newBlockY)))
{
return false;
}
}
return true;
}
private void Clear()
{
for (int i = 0; i < current_shape.GetLength(0); i++)
{
matrix.ClearBlock(x + current_shape[i, 0], y + current_shape[i, 1]);
}
}
}
public class Matrix
{
public const int HEIGHT = 22;
public const int WIDTH = 20;
private char[,] matrix;
private int score = 0;
public Matrix()
{
matrix = new char[HEIGHT, WIDTH];
Clear();
}
public void SetBlock(int x, int y, char c)
{
if (IsInBounds(x, y))
{
matrix[x, y] = c;
}
}
public void ClearBlock(int x, int y)
{
if (IsInBounds(x, y))
{
matrix[x, y] = ' ';
}
}
public char GetBlock(int x, int y)
{
return IsInBounds(x, y) ? matrix[x, y] : 'E';
}
public void Print()
{
Console.Clear();
for (int i = 0; i < HEIGHT; i++)
{
Console.Write("│ ");
for (int j = 0; j < WIDTH; j++)
{
Console.ForegroundColor =
matrix[i, j] == ' ' ? ConsoleColor.DarkBlue : ConsoleColor.DarkBlue;
Console.Write(matrix[i, j]);
}
Console.ResetColor();
Console.WriteLine(" │");
}
Console.WriteLine(" ──────────────────────");
Console.WriteLine("Score: " + score);
}
public int GetScore() { return score; }
public void IncreaseScore(int score) { this.score += score; }
public void CheckForCompleteLines()
{
for (int i = 0; i < HEIGHT; i++)
{
bool isComplete = true;
for (int j = 0; j < WIDTH; j++)
{
if (matrix[i, j] == ' ')
{
isComplete = false;
break;
}
}
if (isComplete)
{
// Remove line
IncreaseScore(100);
for (int j = 0; j < WIDTH; j++)
{
matrix[i, j] = ' ';
}
// Move all lines above one down
for (int k = i; k > 0; k--)
{
for (int j = 0; j < WIDTH; j++)
{
matrix[k, j] = matrix[k - 1, j];
}
}
}
}
}
public bool IsGameOver()
{
for (int i = 0; i < WIDTH; i++)
{
if (matrix[0, i] != ' ')
{
return true;
}
}
return false;
}
public void Clear()
{
for (int i = 0; i < HEIGHT; i++)
{
for (int j = 0; j < WIDTH; j++)
{
matrix[i, j] = ' ';
}
}
}
private bool IsInBounds(int x, int y)
{
return x >= 0 && x < HEIGHT && y >= 0 && y < WIDTH;
}
}
}

25
README.md Normal file → Executable file
View File

@ -1,3 +1,26 @@
# Tetris
## Simple tetris game
[![.NET](https://github.com/foglar/Tetris/actions/workflows/dotnet.yml/badge.svg?branch=master)](https://github.com/foglar/Tetris/actions/workflows/dotnet.yml)
## Compilation and running
```bash
git clone https://github.com/foglar/Tetris.git
cd Tetris
dotnet run
```
> [!NOTE]
> For some weird cause it won't run when executed through Visual Studio on Windows.
> Run it in terminal instead with command above.
## Simple tetris game
- [SRS](./TetrominoSRS.md)
- [FS](./TetrominoFS.md)
## Not fulfilled requirements
- [ ] 7.7 Background Music - Difficult to implement both for Windows and Linux
- [ ] 7.8 Tetromino Colors - Difficult to implement with current implementation
- [ ] 7.11 Next Tetromino Preview - Not enough time

119
ShapesData.cs Executable file
View File

@ -0,0 +1,119 @@
namespace Tetris;
public static class ShapesData
{
public static readonly List<List<int[,]>> shapes = new List<List<int[,]>>
{
// Square block (only one rotation)
new List<int[,]>
{
new int[,]
{
{0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 0}, {1, 1}, {1, 2}, {1, 3}
}
},
// Long block (two rotations)
new List<int[,]>
{
new int[,]
{
{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, -1}, {1, -2}, {1, 4}, {1, 5}
},
new int[,]
{
{0, 0}, {1, 0}, {2, 0}, {3, 0}, {0, 1}, {1, 1}, {2, 1}, {3, 1}
}
},
// L block right (four rotations)
new List<int[,]>
{
new int[,]
{
{0, -2}, {0, -1}, {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}
},
new int[,]
{
{1, -2}, {1, -1}, {1, 0}, {1, 1}, {0, 0}, {0, 1}, {-1, 0}, {-1, 1}
},
new int[,]
{
{1, -2}, {1, -1}, {1, 0}, {1, 1}, {1, 2}, {1, 3}, {0, -2}, {0, -1}
},
new int[,]
{
{-1, 0}, {-1, 1}, {1, 0}, {1, 1}, {0, 0}, {0, 1}, {-1, 2}, {-1, 3}
}
},
// L block left (four rotations)
new List<int[,]>
{
new int[,]
{
{0, -2}, {0, -1}, {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, -2}, {1, -1}
},
new int[,]
{
{-1, 0}, {-1, 1}, {1, 0}, {1, 1}, {0, 0}, {0, 1}, {-1, -1}, {-1, -2}
},
new int[,]
{
{1, -2}, {1, -1}, {1, 0}, {1, 1}, {1, 2}, {1, 3}, {0, 2}, {0, 3}
},
new int[,]
{
{-1, 0}, {-1, 1}, {1, 0}, {1, 1}, {0, 0}, {0, 1}, {1, 2}, {1, 3}
}
},
// Z block left (two rotations)
new List<int[,]>
{
new int[,]
{
{1, -2}, {1, -1}, {0, 0}, {0, 1}, {1, 0}, {1, 1}, {0, 2}, {0, 3}
},
new int[,]
{
{0, 0}, {0, 1}, {1, 0}, {1, 1}, {0, -1}, {0, -2}, {-1, -1}, {-1, -2}
}
},
// T block (four rotations)
new List<int[,]>
{
new int[,]
{
{0, -2}, {0, -1}, {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 0}, {1, 1}
},
new int[,]
{
{-1, 0}, {-1, 1}, {0, 0}, {0, 1}, {1, 0}, {1, 1}, {0, -1}, {0, -2}
},
new int[,]
{
{1,-2}, {1,-1}, {1,0}, {1,1}, {1, 2}, {1, 3}, {0, 0}, {0, 1}
},
new int[,]
{
{-1, 0}, {-1, 1}, {0, 0}, {0, 1}, {1, 0}, {1, 1}, {0, 2}, {0, 3}
}
},
// Z block right (two rotations)
new List<int[,]>
{
new int[,]
{
{0, -2}, {0, -1}, {0, 0}, {0, 1}, {1, 0}, {1, 1}, {1, 2}, {1, 3}
},
new int[,]
{
{0, 0}, {0, 1}, {1, 0}, {1, 1}, {0, 2}, {0, 3}, {-1 , 2}, {-1, 3}
}
}
};
}

9
Tetris.csproj Normal file → Executable file
View File

@ -7,4 +7,13 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
<DefineConstants>_WINDOWS</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="System.Windows.Extensions" Version="8.0.0" />
</ItemGroup>
</Project>

BIN
Tetris.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

0
Tetris.sln Normal file → Executable file
View File

270
TetrominoFS.md Executable file
View File

@ -0,0 +1,270 @@
# **Tetris Game Functional Specification**
---
## **1. Document Control**
| Version | Date | Author | Comments |
| ------- | ---------- | ------------ | ---------------- |
| 1.0 | 16.10.2024 | Filip Kohout | Initial Draft |
| 1.1 | 23.10.2024 | Filip Kohout | UI preview Added |
---
## **2. Table of Contents**
- [**Tetris Game Functional Specification**](#tetris-game-functional-specification)
- [**1. Document Control**](#1-document-control)
- [**2. Table of Contents**](#2-table-of-contents)
- [**3. Introduction**](#3-introduction)
- [**4. Scope**](#4-scope)
- [**5. Definitions and Acronyms**](#5-definitions-and-acronyms)
- [**6. System Overview**](#6-system-overview)
- [**7. Functional Requirements**](#7-functional-requirements)
- [**7.1. Game Loop**](#71-game-loop)
- [**7.2. Tetromino Movement and Rotation**](#72-tetromino-movement-and-rotation)
- [**7.3. Collision Detection**](#73-collision-detection)
- [**7.4. Line Completion**](#74-line-completion)
- [**7.5. Score Calculation**](#75-score-calculation)
- [**7.6. Game Over Condition**](#76-game-over-condition)
- [**7.7. Background Music**](#77-background-music)
- [**7.8. Tetromino Colors**](#78-tetromino-colors)
- [**7.9. Speeding Based on Score**](#79-speeding-based-on-score)
- [**7.10. Terminal Block Size**](#710-terminal-block-size)
- [**7.11. Next Tetromino Preview**](#711-next-tetromino-preview)
- [**8. Non-Functional Requirements**](#8-non-functional-requirements)
- [**9. Assumptions and Dependencies**](#9-assumptions-and-dependencies)
---
## **3. Introduction**
The purpose of this document is to describe the functional requirements for the Tetris game. This document serves as a guideline for developers to implement core features and functionality based on the SRS and provided workflow diagram. The game is developed using C# and runs in a terminal environment.
---
## **4. Scope**
The Tetris game is designed to simulate the classic Tetris gameplay. It involves moving tetromino shapes to fill horizontal lines. The game ends when there is no space to place a new shape. The scope covers basic gameplay, user input handling, scoring, music, block size, and the logic for handling game-ending conditions.
---
## **5. Definitions and Acronyms**
- **Tetromino**: A geometric shape composed of four squares connected orthogonally.
- **Game Loop**: The core loop responsible for updating and rendering the game state.
- **Grid**: A 2D array representing the game board.
- **SRS**: Software Requirements Specification.
---
## **6. System Overview**
The Tetris game will be a single-player application where the user interacts with falling blocks (tetrominoes) to form horizontal lines. The user will be able to move tetrominoes left, right, down, and rotate them. The score will increase as the player completes lines, and the speed of the game will increase based on the score. The game will end when there is no room to place new tetrominoes. The game will also feature background music and tetrominoes will be displayed in different colors.
User will start the game by executing binary, then control block by using arrow keys or wasd keys, x,z and spacebar
- Left and Right key (equivalent a,d or z,x) - rotating block
- Space bar (equivalent down arrow or s key) - make block fall faster
- W and Up arrow key can be used for rotating block or have pausing capability
![game loop](./Tetris.png)
---
## **7. Functional Requirements**
### **7.1. Game Loop**
**Description**:
The game loop is responsible for continuously updating the game state and rendering the current state to the screen. It manages the flow of the game, including the movement of tetrominoes, collision detection, and checking for game-over conditions.
**Functional Requirement ID**: FR-001
**Inputs**: None
**Outputs**: Updated game state (rendered grid, falling tetromino, score)
**Behavior**:
- The game loop will run until the game-over condition is met.
- The game loop calls `drawWindow()` to render the current state.
- It handles user inputs via the `handleUserInput()` function.
- Calls `moveBlockDown()` every game tick to advance the falling tetromino.
![Tetris Blocks Image](./ARS-pieces.png)
---
### **7.2. Tetromino Movement and Rotation**
**Description**:
Users can move tetrominoes left, right, down, or rotate them using keyboard inputs. Movement is constrained by the boundaries of the grid and other placed blocks.
**Functional Requirement ID**: FR-002
**Inputs**: Arrow keys for movement, rotation keys for rotating tetrominoes
**Outputs**: Updated tetromino position on the grid
**Behavior**:
- The `handleUserInput()` function will detect key presses.
- Left and right arrow keys will trigger `moveBlockLeftRight()` to shift the tetromino.
- The down arrow will trigger faster descent using `moveBlockDown()`.
- The rotate button will rotate the tetromino via the `rotateBlock()` function.
---
### **7.3. Collision Detection**
**Description**:
The system checks for collisions between falling tetrominoes and other blocks or the boundaries of the grid.
**Functional Requirement ID**: FR-003
**Inputs**: Current tetromino position, grid state
**Outputs**: Boolean (collision detected or not)
**Behavior**:
- `checkCollision()` will validate whether the tetromino can move to a new position without overlapping a filled block or exceeding grid boundaries.
- If a collision is detected, the tetromino will either stop moving (when falling) or be prevented from moving (left/right/rotation).
---
### **7.4. Line Completion**
**Description**:
When a horizontal line is fully filled with blocks, the system will clear the line and shift blocks above it downward.
**Functional Requirement ID**: FR-004
**Inputs**: Grid state after tetromino placement
**Outputs**: Updated grid, score
**Behavior**:
- `checkCompletedLines()` will scan each row of the grid after a tetromino is placed.
- Completed lines will be cleared, and blocks above will move down.
- The score will be updated based on the number of lines cleared (see 7.5).
---
### **7.5. Score Calculation**
**Description**:
The system calculates the player's score based on the number of lines cleared.
**Functional Requirement ID**: FR-005
**Inputs**: Number of lines cleared
**Outputs**: Updated score displayed on the screen
**Behavior**:
- `updateScore()` will add points for each line cleared.
- Points awarded are based on the number of lines cleared simultaneously.
![Game preview](./GamePrev.png)
---
### **7.6. Game Over Condition**
**Description**:
The game will end when there is no room to place a new tetromino at the top of the grid.
**Functional Requirement ID**: FR-006
**Inputs**: Grid state
**Outputs**: Game over screen, final score
**Behavior**:
- `checkGameOver()` will check whether a new tetromino can be placed on the grid.
- If it overlaps an existing block, the game will end, and `endGame()` will display the game over message and final score.
---
### **7.7. Background Music**
**Description**:
The game will feature background music that plays throughout the game. The music will loop continuously and can be paused or resumed by the player.
**Functional Requirement ID**: FR-007
**Inputs**: Player action (pause/resume)
**Outputs**: Background music played or paused
**Behavior**:
- Background music will start automatically when the game starts.
- `toggleMusic()` function will allow the player to pause or resume music using a specific key (e.g., "M").
- The music will loop until the game ends or is paused.
---
### **7.8. Tetromino Colors**
**Description**:
Each tetromino will have a distinct color to enhance the visual appeal of the game.
**Functional Requirement ID**: FR-008
**Inputs**: Tetromino type
**Outputs**: Colored tetromino displayed on the screen
**Behavior**:
- The game will generate a random color for each tetromino.
- Colors will be predefined for each type of tetromino (e.g., I-tetromino might be cyan, O-tetromino might be yellow, etc.).
- `generateColorForShape()` will assign a color when a new tetromino is generated.
---
### **7.9. Speeding Based on Score**
**Description**:
The game speed will increase as the players score rises, creating a more challenging experience over time.
**Functional Requirement ID**: FR-009
**Inputs**: Player score
**Outputs**: Updated game speed (faster falling tetrominoes)
**Behavior**:
- `increaseSpeed()` will adjust the speed of falling tetrominoes based on the score.
- The speed will increase after every predefined score threshold (e.g., every 100 points).
- The game loop's tick rate will be adjusted to decrease the time between tetromino movements.
---
### **7.10. Terminal Block Size**
**Description**:
Due to the proportions of characters in a terminal environment, each tetromino block will be represented by three horizontal characters stacked vertically.
**Functional Requirement ID**: FR-010
**Inputs**: None
**Outputs**: Properly sized tetromino displayed in terminal
**Behavior**:
- Each individual block of a tetromino will be represented by three consecutive rows of characters, maintaining proportional appearance.
- `drawBlock()` will ensure that the width of the single block is three charachters long.
---
### **7.11. Next Tetromino Preview**
**Description**:
The player will be able to see a preview of the next tetromino that will spawn after the current one is placed. This allows the player to strategize and plan their moves.
**Functional Requirement ID**: FR-011
**Inputs**: None
**Outputs**: Display of the next tetromino on the screen
**Behavior**:
- The next tetromino will be generated and stored when the current tetromino is generated.
- The next tetromino will be displayed in a preview box next to the game grid.
- Once the current tetromino is placed, the next tetromino will become the active tetromino, and a new "next" tetromino will be generated and displayed.
---
## **8. Non-Functional Requirements**
- **Responsiveness**: The game must run at 60 FPS and handle user input with no visible delay.
- **Scalability**: The game should run smoothly on different terminal sizes and operating systems.
- **User Experience**: The user interface must be simple and intuitive, with clear feedback for user actions. The terminal display must account for proper block proportions as specified.
---
## **9. Assumptions and Dependencies**
- The game assumes a modern system with a keyboard input method.
- It is dependent on a graphical library for rendering and input handling (e.g., `Console` or a library in C# for terminal manipulation).
- Music playback assumes that the system supports audio output.
---

27
Tetromino.md → TetrominoSRS.md Normal file → Executable file
View File

@ -1,10 +1,12 @@
# Tetris Game
# Tetris Game
Version: 0.1
Author: Filip Kohout
Date: 20/09/2024
## Table of Contents
1. Document Edit History
## Table of Contents
1. Document Edit History
2. Introduction
3. Service Development
4. Service Purpose
@ -14,15 +16,16 @@ Date: 20/09/2024
---
## Document Edit History
## Document Edit History
| Version | Author | Comment Message |
|---------|----------------|-------------------------|
| 0.1 | Filip Kohout | First game estimates |
| 0.2 | Filip Kohout | Defined blocks |
---
## Introduction
## Introduction
**Purpose of the document:** The purpose of the document is to describe all the functional and non-functional requirements for the CLI-based Tetris game development project. This document serves as a guide for programmers, testers, and stakeholders, providing a clear overview of the system's requirements to ensure the game's development adheres to the design and expectations.
@ -69,6 +72,7 @@ The game will rely on simple keyboard input for interaction and console output f
The primary purpose of this project is to create a minimalist version of Tetris that can run in any terminal or command-line interface. This game is designed for users who enjoy retro-style gaming experiences without needing a graphical environment. It also provides a fun way to practice logical thinking and quick decision-making, as the game increases in difficulty with every level.
Key purposes:
- Provide an engaging and simple game for retro gamers or users with minimal system resources.
- Offer an accessible gaming option that works on most operating systems.
- Allow players to compete for high scores in a lightweight, resource-efficient environment.
@ -86,8 +90,19 @@ Functional requirements define the core behavior and features of the Tetris game
- The grid should be displayed using characters or symbols for blocks.
2. **Tetromino Types**
- The game must include all seven standard tetromino shapes: I, O, T, L, J, S, Z.
- The game must include all seven standard tetromino shapes: I, O, T, L, J, S, Z (shapes are specified in the image below)
- Tetrominoes should be represented using characters.
- Definitions:
- Tetromino shapes and definitions of the shapes
- Shape I - shape 4 squares long (red)
- Shape O - shape of square created from 4 squares (yellow)
- Shape T - shape of "T" constructed from 4 squares (light blue)
- Shape L - shape that looks like L from 4 squares (orange)
- Shape J - shape that is like L but mirrored (dark blue)
- Shape S - shape that looks like S constructed in two rows (pink)
- Shape Z - shape that looks like mirrored S (green)
![tetris block images](./ARS-pieces.png)
3. **Tetromino Movement**
- Players must be able to move tetrominoes left and right using keyboard inputs.

0
image.png Executable file
View File

BIN
song.wav Executable file

Binary file not shown.