Compare commits

..

No commits in common. "80c46a13ba11d851061078399fb679674b239add" and "1659c25ee9d51f91d50c3e8f2d3cc32f8e7f5745" have entirely different histories.

14 changed files with 306 additions and 777 deletions

View File

@ -1,26 +0,0 @@
# 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 Executable file → Normal file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

67
Program.cs Executable file → Normal file
View File

@ -1,6 +1,4 @@
using System; using System;
using System.Media;
using System.Threading;
namespace Tetris namespace Tetris
{ {
@ -70,11 +68,18 @@ namespace Tetris
{ {
private static int speed = 400; private static int speed = 400;
public static int ShowSpeed() { return speed; } public static int ShowSpeed()
{
public static void SpeedUp() { speed -= 50; } return speed;
} }
public static void SpeedUp()
{
speed -= 50;
}
}
public class Shape public class Shape
{ {
private int[,] current_shape; private int[,] current_shape;
@ -86,7 +91,6 @@ namespace Tetris
private int x, y; private int x, y;
private Matrix matrix; private Matrix matrix;
public Shape(Matrix matrix) public Shape(Matrix matrix)
{ {
Random random = new Random(); Random random = new Random();
@ -96,6 +100,7 @@ namespace Tetris
current_shape = shape[randomRotation]; current_shape = shape[randomRotation];
this.matrix = matrix; this.matrix = matrix;
matrix.IncreaseScore(10); matrix.IncreaseScore(10);
// TODO: Implement random color
this.x = 0; // Starting position this.x = 0; // Starting position
this.y = 8; // Center of the grid this.y = 8; // Center of the grid
} }
@ -143,34 +148,13 @@ namespace Tetris
} }
public void Rotate() public void Rotate()
{
int nextRotation = (randomRotation + 1) % shape.Count;
int[,] nextShape = shape[nextRotation];
if (CanRotate(nextShape))
{ {
Clear(); Clear();
current_shape = nextShape; randomRotation = (randomRotation + 1) % shape.Count;
randomRotation = nextRotation; // !IMPORTANT: Check if rotation is possible
current_shape = shape[randomRotation];
Draw(); 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) private bool IsPartOfShape(int matrixX, int matrixY)
{ {
@ -184,6 +168,7 @@ namespace Tetris
return false; return false;
} }
private bool CanMove(int newX, int newY) private bool CanMove(int newX, int newY)
{ {
for (int i = 0; i < current_shape.GetLength(0); i++) for (int i = 0; i < current_shape.GetLength(0); i++)
@ -191,9 +176,11 @@ namespace Tetris
int newBlockX = newX + current_shape[i, 0]; int newBlockX = newX + current_shape[i, 0];
int newBlockY = newY + current_shape[i, 1]; int newBlockY = newY + current_shape[i, 1];
if (newBlockX >= Matrix.HEIGHT || newBlockX < 0 || newBlockY < 0 || if (newBlockX >= Matrix.HEIGHT || newBlockX < 0 || newBlockY < 0 || newBlockY >= Matrix.WIDTH)
newBlockY >= Matrix.WIDTH || {
(matrix.GetBlock(newBlockX, newBlockY) != ' ' && !IsPartOfShape(newBlockX, newBlockY))) return false;
}
if (matrix.GetBlock(newBlockX, newBlockY) != ' ' && !IsPartOfShape(newBlockX, newBlockY))
{ {
return false; return false;
} }
@ -210,7 +197,6 @@ namespace Tetris
} }
} }
public class Matrix public class Matrix
{ {
public const int HEIGHT = 22; public const int HEIGHT = 22;
@ -253,8 +239,7 @@ namespace Tetris
Console.Write("│ "); Console.Write("│ ");
for (int j = 0; j < WIDTH; j++) for (int j = 0; j < WIDTH; j++)
{ {
Console.ForegroundColor = Console.ForegroundColor = matrix[i, j] == ' ' ? ConsoleColor.DarkBlue : ConsoleColor.DarkBlue;
matrix[i, j] == ' ' ? ConsoleColor.DarkBlue : ConsoleColor.DarkBlue;
Console.Write(matrix[i, j]); Console.Write(matrix[i, j]);
} }
Console.ResetColor(); Console.ResetColor();
@ -265,8 +250,14 @@ namespace Tetris
Console.WriteLine("Score: " + score); Console.WriteLine("Score: " + score);
} }
public int GetScore() { return score; } public int GetScore()
public void IncreaseScore(int score) { this.score += score; } {
return score;
}
public void IncreaseScore(int score)
{
this.score += score;
}
public void CheckForCompleteLines() public void CheckForCompleteLines()
{ {

23
README.md Executable file → Normal file
View File

@ -1,26 +1,3 @@
# Tetris # Tetris
[![.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 ## 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

View File

@ -1,119 +0,0 @@
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 Executable file → Normal file
View File

@ -7,13 +7,4 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </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> </Project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

0
Tetris.sln Executable file → Normal file
View File

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

@ -1,11 +1,9 @@
# Tetris Game # Tetris Game
Version: 0.1 Version: 0.1
Author: Filip Kohout Author: Filip Kohout
Date: 20/09/2024 Date: 20/09/2024
## Table of Contents ## Table of Contents
1. Document Edit History 1. Document Edit History
2. Introduction 2. Introduction
3. Service Development 3. Service Development
@ -21,7 +19,6 @@ Date: 20/09/2024
| Version | Author | Comment Message | | Version | Author | Comment Message |
|---------|----------------|-------------------------| |---------|----------------|-------------------------|
| 0.1 | Filip Kohout | First game estimates | | 0.1 | Filip Kohout | First game estimates |
| 0.2 | Filip Kohout | Defined blocks |
--- ---
@ -72,7 +69,6 @@ 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. 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: Key purposes:
- Provide an engaging and simple game for retro gamers or users with minimal system resources. - 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. - Offer an accessible gaming option that works on most operating systems.
- Allow players to compete for high scores in a lightweight, resource-efficient environment. - Allow players to compete for high scores in a lightweight, resource-efficient environment.
@ -90,19 +86,8 @@ Functional requirements define the core behavior and features of the Tetris game
- The grid should be displayed using characters or symbols for blocks. - The grid should be displayed using characters or symbols for blocks.
2. **Tetromino Types** 2. **Tetromino Types**
- The game must include all seven standard tetromino shapes: I, O, T, L, J, S, Z (shapes are specified in the image below) - The game must include all seven standard tetromino shapes: I, O, T, L, J, S, Z.
- Tetrominoes should be represented using characters. - 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** 3. **Tetromino Movement**
- Players must be able to move tetrominoes left and right using keyboard inputs. - Players must be able to move tetrominoes left and right using keyboard inputs.

View File

@ -1,270 +0,0 @@
# **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.
---

View File

BIN
song.wav

Binary file not shown.