From b4575440714d3ec24fc2794784324fd1d7b16a6a Mon Sep 17 00:00:00 2001
From: ISman601 <33d9228@gmail.com>
Date: Fri, 5 Jun 2026 11:45:04 +0300
Subject: [PATCH] first commit
---
.idea/.gitignore | 8 +
.idea/Lab9_UNIX.iml | 2 +
.idea/material_theme_project_new.xml | 12 +
.idea/misc.xml | 7 +
.idea/modules.xml | 8 +
.idea/vcs.xml | 6 +
CMakeLists.txt | 34 +++
core/Engine.cpp | 119 +++++++++
core/Engine.h | 50 ++++
core/ExampleGame.cpp | 347 +++++++++++++++++++++++++++
core/ExampleGame.h | 56 +++++
core/Figure.cpp | 89 +++++++
core/Figure.h | 49 ++++
core/GameField.cpp | 189 +++++++++++++++
core/GameField.h | 33 +++
core/IBlock.cpp | 37 +++
core/IBlock.h | 9 +
core/PaintDevice.cpp | 107 +++++++++
core/PaintDevice.h | 38 +++
core/Point.h | 17 ++
core/Size.cpp | 35 +++
core/Size.h | 24 ++
core/Square.cpp | 52 ++++
core/Square.h | 30 +++
core/Tetris.cpp | 120 +++++++++
core/Tetris.h | 34 +++
core/Vector2.cpp | 121 ++++++++++
core/Vector2.h | 78 ++++++
main.cpp | 14 ++
29 files changed, 1725 insertions(+)
create mode 100644 .idea/.gitignore
create mode 100644 .idea/Lab9_UNIX.iml
create mode 100644 .idea/material_theme_project_new.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/modules.xml
create mode 100644 .idea/vcs.xml
create mode 100644 CMakeLists.txt
create mode 100644 core/Engine.cpp
create mode 100644 core/Engine.h
create mode 100644 core/ExampleGame.cpp
create mode 100644 core/ExampleGame.h
create mode 100644 core/Figure.cpp
create mode 100644 core/Figure.h
create mode 100644 core/GameField.cpp
create mode 100644 core/GameField.h
create mode 100644 core/IBlock.cpp
create mode 100644 core/IBlock.h
create mode 100644 core/PaintDevice.cpp
create mode 100644 core/PaintDevice.h
create mode 100644 core/Point.h
create mode 100644 core/Size.cpp
create mode 100644 core/Size.h
create mode 100644 core/Square.cpp
create mode 100644 core/Square.h
create mode 100644 core/Tetris.cpp
create mode 100644 core/Tetris.h
create mode 100644 core/Vector2.cpp
create mode 100644 core/Vector2.h
create mode 100644 main.cpp
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/Lab9_UNIX.iml b/.idea/Lab9_UNIX.iml
new file mode 100644
index 0000000..f08604b
--- /dev/null
+++ b/.idea/Lab9_UNIX.iml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 0000000..fa223c7
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..0b76fe5
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..a6cbdd4
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..38a520e
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 4.0)
+project(Lab9_UNIX)
+
+set(CMAKE_CXX_STANDARD 20)
+
+find_package(Curses REQUIRED)
+
+include_directories(${CURSES_INCLUDE_DIR})
+
+add_executable(Lab9_UNIX main.cpp
+ core/Vector2.cpp
+ core/Vector2.h
+ core/PaintDevice.cpp
+ core/PaintDevice.h
+ core/Size.cpp
+ core/Size.h
+ core/Square.cpp
+ core/Square.h
+ core/Engine.cpp
+ core/Engine.h
+ core/ExampleGame.cpp
+ core/ExampleGame.h
+ core/Point.h
+ core/Figure.cpp
+ core/Figure.h
+ core/IBlock.cpp
+ core/IBlock.h
+ core/GameField.cpp
+ core/GameField.h
+ core/Tetris.cpp
+ core/Tetris.h)
+
+target_link_libraries(Lab9_UNIX ${CURSES_LIBRARIES})
+
diff --git a/core/Engine.cpp b/core/Engine.cpp
new file mode 100644
index 0000000..31ddf47
--- /dev/null
+++ b/core/Engine.cpp
@@ -0,0 +1,119 @@
+#include "Engine.h"
+
+#include
+#include
+
+Engine::ErrorCode Engine::run()
+{
+ ErrorCode errorCode =
+ ErrorCode::success;
+
+ auto lastUpdate =
+ std::chrono::steady_clock::now();
+
+ auto lag =
+ std::chrono::milliseconds(0);
+
+ const auto timeQuantum =
+ std::chrono::milliseconds(15);
+
+ while (!end())
+ {
+ // INPUT
+ //=================================
+
+ updateInput();
+
+ //=================================
+
+ // UPDATE
+ //=================================
+
+ auto now =
+ std::chrono::steady_clock::now();
+
+ lag +=
+ std::chrono::duration_cast
+ <
+ std::chrono::milliseconds
+ >
+ (
+ now - lastUpdate
+ );
+
+ lastUpdate = now;
+
+ while (lag >= timeQuantum)
+ {
+ lag -= timeQuantum;
+
+ update(
+ static_cast(
+ timeQuantum.count()
+ )
+ );
+ }
+
+ //=================================
+
+ // RENDER
+ //=================================
+
+ if (!m_PaintDevice.ready())
+ {
+ errorCode =
+ ErrorCode::paint_device_not_ready;
+
+ break;
+ }
+
+ m_PaintDevice.clear();
+
+ render(m_PaintDevice);
+
+ m_PaintDevice.render();
+
+ //=================================
+
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(1)
+ );
+ }
+
+ return errorCode;
+}
+
+void Engine::updateInput()
+{
+ int key = getch();
+
+ while (key != ERR)
+ {
+ if (
+ m_TrackedKeys.find(key)
+ !=
+ m_TrackedKeys.end()
+ )
+ {
+ on_button_press(key);
+ }
+
+ key = getch();
+ }
+}
+
+void Engine::track_key(int key)
+{
+ m_TrackedKeys.insert(key);
+}
+
+void Engine::untrack_key(int key)
+{
+ auto it =
+ m_TrackedKeys.find(key);
+
+ if (it != m_TrackedKeys.end())
+ {
+ m_TrackedKeys.erase(it);
+ }
+}
\ No newline at end of file
diff --git a/core/Engine.h b/core/Engine.h
new file mode 100644
index 0000000..c71c730
--- /dev/null
+++ b/core/Engine.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include
+
+#include "PaintDevice.h"
+
+class Engine
+{
+public:
+ enum class ErrorCode
+ {
+ success = 0,
+ paint_device_not_ready
+ };
+
+ virtual ~Engine() = default;
+
+ ErrorCode run();
+
+protected:
+ void track_key(int key);
+
+ void untrack_key(int key);
+
+ PaintDevice& paint_device()
+ {
+ return m_PaintDevice;
+ }
+
+ virtual bool end() const = 0;
+
+ virtual void on_button_press(
+ int button
+ ) = 0;
+
+ virtual void update(
+ int dt
+ ) = 0;
+
+ virtual void render(
+ PaintDevice& paintDevice
+ ) = 0;
+
+private:
+ void updateInput();
+
+ PaintDevice m_PaintDevice;
+
+ std::set m_TrackedKeys;
+};
\ No newline at end of file
diff --git a/core/ExampleGame.cpp b/core/ExampleGame.cpp
new file mode 100644
index 0000000..6cfd2be
--- /dev/null
+++ b/core/ExampleGame.cpp
@@ -0,0 +1,347 @@
+#include "ExampleGame.h"
+
+#include
+
+void Bullet::render(PaintDevice& paintDevice)
+{
+ paintDevice.set_char(
+ m_Position,
+ '|'
+ );
+}
+
+void Bullet::update(const int dt)
+{
+ m_Lag += dt;
+
+ const int quant = 50;
+
+ if (m_Lag <= quant)
+ {
+ return;
+ }
+
+ m_Lag -= quant;
+
+ m_Position.y()--;
+}
+
+Gun::Gun()
+{
+ m_Body =
+ {
+ Vector2(5, 0),
+ Vector2(5, 1),
+
+ Vector2(4, 2),
+ Vector2(4, 3),
+ Vector2(4, 4),
+
+ Vector2(6, 2),
+ Vector2(6, 3),
+ Vector2(6, 4),
+
+ Vector2(3, 5),
+ Vector2(7, 5),
+
+ Vector2(2, 6),
+ Vector2(5, 6),
+ Vector2(8, 6),
+
+ Vector2(1, 7),
+ Vector2(5, 7),
+ Vector2(9, 7),
+
+ Vector2(0, 8),
+ Vector2(1, 8),
+ Vector2(2, 8),
+
+ Vector2(8, 8),
+ Vector2(9, 8),
+ Vector2(10, 8),
+
+ Vector2(3, 9),
+ Vector2(7, 9),
+
+ Vector2(3, 10),
+ Vector2(7, 10),
+
+ Vector2(4, 11),
+ Vector2(6, 11),
+
+ Vector2(4, 12),
+ Vector2(5, 12),
+ Vector2(6, 12)
+ };
+}
+
+std::vector Gun::fire()
+{
+ std::vector bullets;
+
+ if (m_CooldownCenter == 0)
+ {
+ bullets.push_back(Bullet());
+
+ bullets.back().m_Position =
+ Vector2(
+ m_Position.x() + 4,
+ m_Position.y() + 1
+ );
+
+ bullets.push_back(Bullet());
+
+ bullets.back().m_Position =
+ Vector2(
+ m_Position.x() + 6,
+ m_Position.y() + 1
+ );
+
+ m_CooldownCenter = 300;
+ }
+
+ if (m_CooldownSide == 0)
+ {
+ bullets.push_back(Bullet());
+
+ bullets.back().m_Position =
+ Vector2(
+ m_Position.x(),
+ m_Position.y() + 7
+ );
+
+ bullets.push_back(Bullet());
+
+ bullets.back().m_Position =
+ Vector2(
+ m_Position.x() + 10,
+ m_Position.y() + 7
+ );
+
+ m_CooldownSide = 150;
+ }
+
+ return bullets;
+}
+
+void Gun::update(const int dt)
+{
+ m_CooldownCenter -= dt;
+ m_CooldownSide -= dt;
+
+ if (m_CooldownCenter < 0)
+ {
+ m_CooldownCenter = 0;
+ }
+
+ if (m_CooldownSide < 0)
+ {
+ m_CooldownSide = 0;
+ }
+}
+
+void Gun::render(PaintDevice& paintDevice)
+{
+ for (const Vector2& point : m_Body)
+ {
+ paintDevice.set_char(
+ Vector2(
+ m_Position.x() + point.x(),
+ m_Position.y() + point.y()
+ ),
+ '#'
+ );
+ }
+}
+
+void Enemy::render(PaintDevice& paintDevice)
+{
+ for (const Vector2& point : m_Enemys)
+ {
+ paintDevice.set_char(
+ point,
+ 'V'
+ );
+ }
+}
+
+void Enemy::update(const int dt)
+{
+ m_Lag += dt;
+
+ const int quant = 800;
+
+ if (m_Lag <= quant)
+ {
+ return;
+ }
+
+ m_Lag -= quant;
+
+ for (Vector2& point : m_Enemys)
+ {
+ point.y()++;
+ }
+
+ for (int i = 0; i < 20; ++i)
+ {
+ if (rand() % 2)
+ {
+ m_Enemys.push_back(
+ Vector2(i, 0)
+ );
+ }
+ }
+}
+
+bool Enemy::hit(Vector2 point)
+{
+ for (const Vector2& enemy : m_Enemys)
+ {
+ if (enemy == point)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void Enemy::remove(Vector2 point)
+{
+ for (int i = 0; i < m_Enemys.size(); ++i)
+ {
+ if (
+ point.x() == m_Enemys[i].x()
+ &&
+ point.y() == m_Enemys[i].y()
+ )
+ {
+ for (int j = i; j < m_Enemys.size() - 1; ++j)
+ {
+ m_Enemys[j] = m_Enemys[j + 1];
+ }
+
+ m_Enemys.pop_back();
+
+ return;
+ }
+ }
+}
+
+ExampleGame::ExampleGame()
+{
+ paint_device().resize(
+ Size(
+ m_Width,
+ m_Height
+ )
+ );
+
+ track_key(KEY_LEFT);
+ track_key(KEY_RIGHT);
+
+ m_Gun.m_Position =
+ Vector2(
+ m_Width / 2 - 2,
+ m_Height - 14
+ );
+}
+
+void ExampleGame::on_button_press(const int button)
+{
+ switch (button)
+ {
+ case KEY_LEFT:
+ {
+ m_Gun.m_Position.x()--;
+
+ if (m_Gun.m_Position.x() < 0)
+ {
+ m_Gun.m_Position.x() = 0;
+ }
+
+ break;
+ }
+
+ case KEY_RIGHT:
+ {
+ m_Gun.m_Position.x()++;
+
+ if (m_Gun.m_Position.x() > m_Width - 11)
+ {
+ m_Gun.m_Position.x() = m_Width - 11;
+ }
+
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+}
+
+void ExampleGame::update(const int dt)
+{
+ m_Enemy.update(dt);
+
+ for (int i = 0; i < m_Bullets.size(); i++)
+ {
+ m_Bullets[i].update(dt);
+ }
+
+ for (int i = 0; i < m_Bullets.size(); i++)
+ {
+ if (m_Bullets[i].m_Position.y() < 0)
+ {
+ for (int j = i; j < m_Bullets.size() - 1; j++)
+ {
+ m_Bullets[j] = m_Bullets[j + 1];
+ }
+
+ m_Bullets.pop_back();
+ }
+ }
+
+ for (int i = 0; i < m_Bullets.size(); i++)
+ {
+ if (m_Enemy.hit(m_Bullets[i].m_Position))
+ {
+ m_Enemy.remove(
+ m_Bullets[i].m_Position
+ );
+
+ for (int j = i; j < m_Bullets.size() - 1; ++j)
+ {
+ m_Bullets[j] = m_Bullets[j + 1];
+ }
+
+ m_Bullets.pop_back();
+ }
+ }
+
+ m_Gun.update(dt);
+
+ std::vector newBullets =
+ m_Gun.fire();
+
+ m_Bullets.insert(
+ m_Bullets.end(),
+ newBullets.begin(),
+ newBullets.end()
+ );
+}
+
+void ExampleGame::render(PaintDevice& paintDevice)
+{
+ for (Bullet& bullet : m_Bullets)
+ {
+ bullet.render(paintDevice);
+ }
+
+ m_Enemy.render(paintDevice);
+
+ m_Gun.render(paintDevice);
+}
\ No newline at end of file
diff --git a/core/ExampleGame.h b/core/ExampleGame.h
new file mode 100644
index 0000000..1f84a62
--- /dev/null
+++ b/core/ExampleGame.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "Engine.h"
+#include
+
+class Bullet {
+public:
+ void render(PaintDevice& paintDevice);
+ void update(const int dt);
+
+ Vector2 m_Position;
+ int m_Lag = 0;
+};
+
+class Gun {
+public:
+ Gun();
+ std::vector fire();
+ void render(PaintDevice& paintDevice);
+ void update(const int dt);
+
+ Vector2 m_Position;
+ std::vector m_Body;
+
+ int m_CooldownCenter = 0;
+ int m_CooldownSide = 0;
+};
+
+class Enemy {
+public:
+ void render(PaintDevice& paintDevice);
+ void update(const int dt);
+ int m_Lag = 0;
+ bool hit(Vector2 point);
+ void remove(Vector2 point);
+ Vector2 first() const { return m_Enemys.front(); }
+ bool empty() const { return m_Enemys.empty(); }
+private:
+ std::vector m_Enemys;
+};
+
+class ExampleGame : public Engine {
+public:
+ ExampleGame();
+private:
+ virtual bool end() const { return !m_Enemy.empty() && m_Enemy.first().y() >= m_Height; }
+ virtual void on_button_press(const int button);
+ virtual void update(const int dt);
+ virtual void render(PaintDevice& paintDevice);
+
+ const size_t m_Width = 20;
+ const size_t m_Height = 40;
+ Gun m_Gun;
+ std::vector m_Bullets;
+ Enemy m_Enemy;
+};
diff --git a/core/Figure.cpp b/core/Figure.cpp
new file mode 100644
index 0000000..a05602c
--- /dev/null
+++ b/core/Figure.cpp
@@ -0,0 +1,89 @@
+#include "Figure.h"
+
+Figure::Figure(Point position)
+ :
+ m_Position(position)
+{
+}
+
+void Figure::update(double dt)
+{
+ m_TimeFromLastUpdate += dt;
+
+ if (m_TimeFromLastUpdate >= m_TimeForUpdate)
+ {
+ m_TimeFromLastUpdate = 0;
+
+ ++m_Position.y;
+ }
+}
+
+void Figure::render(PaintDevice& paintDevice)
+{
+ for (const Point& point : m_Body[m_CurrentRotate])
+ {
+ Vector2 v(
+ point.x + m_Position.x,
+ point.y + m_Position.y
+ );
+
+ paintDevice.set_char(
+ v,
+ 0x25D8
+ );
+ }
+}
+
+void Figure::move_left()
+{
+ --m_Position.x;
+}
+
+void Figure::move_right()
+{
+ ++m_Position.x;
+}
+
+void Figure::boost()
+{
+ m_TimeForUpdate = 50;
+}
+
+void Figure::rotate()
+{
+ ++m_CurrentRotate;
+
+ if (m_CurrentRotate >= m_Body.size())
+ {
+ m_CurrentRotate = 0;
+ }
+}
+
+void Figure::backup()
+{
+ m_PositionBackup = m_Position;
+
+ m_CurrentRotateBackup = m_CurrentRotate;
+}
+
+void Figure::restore()
+{
+ m_Position = m_PositionBackup;
+
+ m_CurrentRotate = m_CurrentRotateBackup;
+}
+
+Point Figure::get_position() const
+{
+ return m_Position;
+}
+
+void Figure::set_position(Point position)
+{
+ m_Position = position;
+}
+
+const std::vector& Figure::get_body() const
+{
+ return m_Body[m_CurrentRotate];
+}
\ No newline at end of file
diff --git a/core/Figure.h b/core/Figure.h
new file mode 100644
index 0000000..4adf792
--- /dev/null
+++ b/core/Figure.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include
+
+#include "Point.h"
+#include "PaintDevice.h"
+
+class Figure
+{
+protected:
+ Point m_Position;
+
+ Point m_PositionBackup;
+
+ double m_TimeFromLastUpdate = 0;
+ double m_TimeForUpdate = 500;
+
+ std::vector> m_Body;
+
+ size_t m_CurrentRotate = 0;
+ size_t m_CurrentRotateBackup = 0;
+
+public:
+ explicit Figure(Point position);
+
+ virtual ~Figure() = default;
+
+ void update(double dt);
+
+ void render(PaintDevice& paintDevice);
+
+ void move_left();
+
+ void move_right();
+
+ void boost();
+
+ void rotate();
+
+ void backup();
+
+ void restore();
+
+ Point get_position() const;
+
+ void set_position(Point position);
+
+ const std::vector& get_body() const;
+};
\ No newline at end of file
diff --git a/core/GameField.cpp b/core/GameField.cpp
new file mode 100644
index 0000000..15557d2
--- /dev/null
+++ b/core/GameField.cpp
@@ -0,0 +1,189 @@
+#include "GameField.h"
+
+void GameField::resize(
+ size_t width,
+ size_t height
+)
+{
+ m_Width = width;
+ m_Height = height;
+
+ m_Field =
+ std::vector<
+ std::vector
+ >(
+ m_Height - 2,
+ std::vector(
+ m_Width - 2,
+ 0x0387
+ )
+ );
+}
+void GameField::render(
+ PaintDevice& paintDevice
+)
+{
+ for (int x = 1; x < m_Width - 1; x++)
+ {
+ paintDevice.set_char(
+ Vector2(x,0),
+ '-'
+ );
+
+ paintDevice.set_char(
+ Vector2(
+ x,
+ m_Height - 1
+ ),
+ '-'
+ );
+ }
+
+ for (int y = 1; y < m_Height - 1; y++)
+ {
+ paintDevice.set_char(
+ Vector2(0,y),
+ '|'
+ );
+
+ paintDevice.set_char(
+ Vector2(
+ m_Width - 1,
+ y
+ ),
+ '|'
+ );
+ }
+
+ paintDevice.set_char(
+ Vector2(0,0),
+ '+'
+ );
+
+ paintDevice.set_char(
+ Vector2(
+ m_Width - 1,
+ 0
+ ),
+ '+'
+ );
+
+ paintDevice.set_char(
+ Vector2(
+ 0,
+ m_Height - 1
+ ),
+ '+'
+ );
+
+ paintDevice.set_char(
+ Vector2(
+ m_Width - 1,
+ m_Height - 1
+ ),
+ '+'
+ );
+ for (size_t y = 0; y < m_Field.size(); y++)
+ {
+ for (size_t x = 0;
+ x < m_Field[y].size();
+ x++)
+ {
+ paintDevice.set_char(
+ Vector2(
+ x + 1,
+ y + 1
+ ),
+ m_Field[y][x]
+ );
+ }
+ }
+}
+
+bool GameField::has_collision(
+ const Figure& figure)
+{
+ Point position =
+ figure.get_position();
+
+ for (const Point& point :
+ figure.get_body())
+ {
+ int x =
+ point.x + position.x;
+
+ int y =
+ point.y + position.y;
+
+ if (x < 1 ||
+ x > m_Width - 2)
+ return true;
+
+ if (y < 1 ||
+ y > m_Height - 2)
+ return true;
+
+ if (
+ m_Field[y - 1][x - 1]
+ != 0x0387
+ )
+ return true;
+ }
+
+ return false;
+}
+
+void GameField::merge(const Figure& figure)
+{
+ Point position = figure.get_position();
+
+ for (const Point& point : figure.get_body())
+ {
+ int x = point.x + position.x;
+ int y = point.y + position.y;
+
+ if (x < 1 || x > m_Width - 2 ||
+ y < 1 || y > m_Height - 2)
+ continue;
+
+ m_Field[y - 1][x - 1] = L'#';
+ }
+}
+
+int GameField::clear_lines()
+{
+ int cleared = 0;
+
+ for (int y = (int)m_Field.size() - 1; y >= 0; y--)
+ {
+ bool full = true;
+
+ for (size_t x = 0; x < m_Field[y].size(); x++)
+ {
+ if (m_Field[y][x] == L' ')
+ {
+ full = false;
+ break;
+ }
+ }
+
+ if (full)
+ {
+ cleared++;
+
+ for (int j = y; j > 0; j--)
+ {
+ m_Field[j] = m_Field[j - 1];
+ }
+
+ m_Field[0] = std::vector(
+ m_Width - 2,
+ L' '
+ );
+
+ y++; // 🔥 ВАЖНО: перескан строки после сдвига
+ }
+ }
+
+ return cleared;
+}
\ No newline at end of file
diff --git a/core/GameField.h b/core/GameField.h
new file mode 100644
index 0000000..e7ae133
--- /dev/null
+++ b/core/GameField.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include
+
+#include "Figure.h"
+
+class GameField
+{
+private:
+ size_t m_Width = 0;
+ size_t m_Height = 0;
+
+ std::vector> m_Field;
+
+public:
+ void resize(
+ size_t width,
+ size_t height
+ );
+
+ void render(
+ PaintDevice& paintDevice
+ );
+
+ bool has_collision(
+ const Figure& figure
+ );
+
+ void merge(
+ const Figure& figure
+ );
+ int clear_lines();
+};
\ No newline at end of file
diff --git a/core/IBlock.cpp b/core/IBlock.cpp
new file mode 100644
index 0000000..3ffb73c
--- /dev/null
+++ b/core/IBlock.cpp
@@ -0,0 +1,37 @@
+#include "IBlock.h"
+
+IBlock::IBlock(Point position)
+ :
+ Figure(position)
+{
+ m_Body =
+ {
+ {
+ Point(1,0),
+ Point(1,1),
+ Point(1,2),
+ Point(1,3)
+ },
+
+ {
+ Point(0,2),
+ Point(1,2),
+ Point(2,2),
+ Point(3,2)
+ },
+
+ {
+ Point(2,0),
+ Point(2,1),
+ Point(2,2),
+ Point(2,3)
+ },
+
+ {
+ Point(0,1),
+ Point(1,1),
+ Point(2,1),
+ Point(3,1)
+ }
+ };
+}
\ No newline at end of file
diff --git a/core/IBlock.h b/core/IBlock.h
new file mode 100644
index 0000000..d3e27e3
--- /dev/null
+++ b/core/IBlock.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "Figure.h"
+
+class IBlock : public Figure
+{
+public:
+ explicit IBlock(Point position);
+};
\ No newline at end of file
diff --git a/core/PaintDevice.cpp b/core/PaintDevice.cpp
new file mode 100644
index 0000000..76a2ddb
--- /dev/null
+++ b/core/PaintDevice.cpp
@@ -0,0 +1,107 @@
+#include "PaintDevice.h"
+
+PaintDevice::PaintDevice()
+ : m_Size(40, 40)
+{
+ initscr();
+
+ noecho();
+
+ curs_set(0);
+
+ keypad(stdscr, TRUE);
+
+ nodelay(stdscr, TRUE);
+
+ resize(m_Size);
+
+ m_Ready = true;
+}
+
+PaintDevice::~PaintDevice()
+{
+ endwin();
+}
+
+bool PaintDevice::ready() const
+{
+ return m_Ready;
+}
+
+void PaintDevice::resize(const Size& size)
+{
+ m_Size = size;
+
+ m_Buffer =
+ std::vector>
+ (
+ m_Size.height(),
+ std::vector(
+ m_Size.width(),
+ L' '
+ )
+ );
+}
+
+void PaintDevice::clear()
+{
+ for (int y = 0; y < m_Size.height(); y++)
+ {
+ for (int x = 0; x < m_Size.width(); x++)
+ {
+ m_Buffer[y][x] = L' ';
+ }
+ }
+}
+
+void PaintDevice::set_char(
+ const Vector2& position,
+ wchar_t c
+)
+{
+ if (
+ position.x() >= 0 &&
+ position.x() < m_Size.width() &&
+ position.y() >= 0 &&
+ position.y() < m_Size.height()
+ )
+ {
+ m_Buffer[position.y()][position.x()] = c;
+ }
+}
+
+wchar_t PaintDevice::get_char(
+ const Vector2& position
+)
+{
+ if (
+ position.x() >= 0 &&
+ position.x() < m_Size.width() &&
+ position.y() >= 0 &&
+ position.y() < m_Size.height()
+ )
+ {
+ return m_Buffer[position.y()][position.x()];
+ }
+
+ return L'\0';
+}
+
+void PaintDevice::render()
+{
+ ::clear();
+
+ for (int y = 0; y < m_Size.height(); y++)
+ {
+ for (int x = 0; x < m_Size.width(); x++)
+ {
+ mvaddch(
+ y,
+ x,
+ m_Buffer[y][x]
+ );
+ }
+ }
+
+ refresh();
+}
\ No newline at end of file
diff --git a/core/PaintDevice.h b/core/PaintDevice.h
new file mode 100644
index 0000000..b963907
--- /dev/null
+++ b/core/PaintDevice.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include
+#include
+
+#include "Size.h"
+#include "Vector2.h"
+
+class PaintDevice
+{
+public:
+ PaintDevice();
+ ~PaintDevice();
+
+ bool ready() const;
+
+ void resize(const Size& size);
+
+ void clear();
+
+ void set_char(
+ const Vector2& position,
+ wchar_t c
+ );
+
+ wchar_t get_char(
+ const Vector2& position
+ );
+
+ void render();
+
+private:
+ std::vector> m_Buffer;
+
+ Size m_Size;
+
+ bool m_Ready = false;
+};
\ No newline at end of file
diff --git a/core/Point.h b/core/Point.h
new file mode 100644
index 0000000..cd14a42
--- /dev/null
+++ b/core/Point.h
@@ -0,0 +1,17 @@
+#pragma once
+
+struct Point
+{
+ int x;
+ int y;
+
+ Point(
+ int x = 0,
+ int y = 0
+ )
+ :
+ x(x),
+ y(y)
+ {
+ }
+};
\ No newline at end of file
diff --git a/core/Size.cpp b/core/Size.cpp
new file mode 100644
index 0000000..cd9470d
--- /dev/null
+++ b/core/Size.cpp
@@ -0,0 +1,35 @@
+#include "Size.h"
+
+Size::Size(
+ PointType width,
+ PointType height
+)
+{
+ m_Width = width;
+ m_Height = height;
+}
+
+Size::PointType Size::width() const
+{
+ return m_Width;
+}
+
+Size::PointType Size::height() const
+{
+ return m_Height;
+}
+
+Size::PointType& Size::width()
+{
+ return m_Width;
+}
+
+Size::PointType& Size::height()
+{
+ return m_Height;
+}
+
+Size::PointType Size::area() const
+{
+ return m_Width * m_Height;
+}
\ No newline at end of file
diff --git a/core/Size.h b/core/Size.h
new file mode 100644
index 0000000..bd1fab8
--- /dev/null
+++ b/core/Size.h
@@ -0,0 +1,24 @@
+#pragma once
+
+class Size
+{
+ using PointType = long long;
+
+public:
+ Size(
+ PointType width = 0,
+ PointType height = 0
+ );
+
+ PointType width() const;
+ PointType height() const;
+
+ PointType& width();
+ PointType& height();
+
+ PointType area() const;
+
+private:
+ PointType m_Width;
+ PointType m_Height;
+};
\ No newline at end of file
diff --git a/core/Square.cpp b/core/Square.cpp
new file mode 100644
index 0000000..34dda78
--- /dev/null
+++ b/core/Square.cpp
@@ -0,0 +1,52 @@
+#include "Square.h"
+
+Square::Square(
+ const Vector2& position,
+ const Size& size
+)
+ : m_Position(position),
+ m_Size(size)
+{
+
+}
+
+bool Square::hit(
+ const Vector2& point
+) const
+{
+ return
+ point.x() >= top_left().x() &&
+ point.x() <= bottom_right().x() &&
+ point.y() >= top_left().y() &&
+ point.y() <= bottom_right().y();
+}
+
+Vector2 Square::top_left() const
+{
+ return m_Position;
+}
+
+Vector2 Square::bottom_right() const
+{
+ return Vector2(
+ m_Position.x() + m_Size.width() - 1,
+ m_Position.y() + m_Size.height() - 1
+ );
+}
+
+bool Square::collide(
+ const Square& other
+) const
+{
+ bool xProjectionCollide =
+ top_left().x() <= other.bottom_right().x() &&
+ bottom_right().x() >= other.top_left().x();
+
+ bool yProjectionCollide =
+ top_left().y() <= other.bottom_right().y() &&
+ bottom_right().y() >= other.top_left().y();
+
+ return
+ xProjectionCollide &&
+ yProjectionCollide;
+}
\ No newline at end of file
diff --git a/core/Square.h b/core/Square.h
new file mode 100644
index 0000000..5cdbc09
--- /dev/null
+++ b/core/Square.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "Vector2.h"
+#include "Size.h"
+
+class Square
+{
+public:
+ Square(
+ const Vector2& position,
+ const Size& size
+ );
+
+ bool hit(
+ const Vector2& point
+ ) const;
+
+ bool collide(
+ const Square& other
+ ) const;
+
+ Vector2 top_left() const;
+
+ Vector2 bottom_right() const;
+
+private:
+ Vector2 m_Position;
+
+ Size m_Size;
+};
\ No newline at end of file
diff --git a/core/Tetris.cpp b/core/Tetris.cpp
new file mode 100644
index 0000000..6e13141
--- /dev/null
+++ b/core/Tetris.cpp
@@ -0,0 +1,120 @@
+#include "Tetris.h"
+
+Tetris::Tetris()
+{
+ paint_device().resize(
+ Size(
+ m_Width + 6,
+ m_Height
+ )
+ );
+
+ m_GameField.resize(
+ m_Width,
+ m_Height
+ );
+
+ m_Figure =
+ new IBlock(
+ Point(5,1)
+ );
+
+ track_key(KEY_LEFT);
+ track_key(KEY_RIGHT);
+ track_key(KEY_DOWN);
+ track_key(' ');
+}
+Tetris::~Tetris()
+{
+ delete m_Figure;
+}
+bool Tetris::end() const
+{
+ return m_End;
+}
+void Tetris::render(
+ PaintDevice& paintDevice
+)
+{
+ m_GameField.render(
+ paintDevice
+ );
+
+ if (m_Figure)
+ {
+ m_Figure->render(
+ paintDevice
+ );
+ }
+
+ Vector2 scorePos(m_Width + 1, 2);
+
+ std::wstring text = L"SCORE: " + std::to_wstring(m_Score);
+
+ for (size_t i = 0; i < text.size(); i++)
+ {
+ paintDevice.set_char(
+ Vector2(scorePos.x() + i, scorePos.y()),
+ text[i]
+ );
+ }
+}
+void Tetris::update(int dt)
+{
+ m_Figure->backup();
+
+ m_Figure->update(dt);
+
+ if (m_GameField.has_collision(*m_Figure))
+ {
+ m_Figure->restore();
+
+ m_GameField.merge(*m_Figure);
+
+ int lines = m_GameField.clear_lines();
+
+ if (lines > 0)
+ {
+ m_Score += lines * 100;
+ }
+
+ delete m_Figure;
+
+ m_Figure =
+ new IBlock(Point(5,1));
+ }
+}
+void Tetris::on_button_press(
+ int button
+)
+{
+ m_Figure->backup();
+
+ switch (button)
+ {
+ case KEY_LEFT:
+ m_Figure->move_left();
+ break;
+
+ case KEY_RIGHT:
+ m_Figure->move_right();
+ break;
+
+ case KEY_DOWN:
+ m_Figure->boost();
+ break;
+
+ case ' ':
+ m_Figure->rotate();
+ break;
+ }
+
+ if (
+ m_GameField.has_collision(
+ *m_Figure
+ )
+ )
+ {
+ m_Figure->restore();
+ }
+}
\ No newline at end of file
diff --git a/core/Tetris.h b/core/Tetris.h
new file mode 100644
index 0000000..bba6de4
--- /dev/null
+++ b/core/Tetris.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "Engine.h"
+#include "GameField.h"
+#include "IBlock.h"
+
+class Tetris : public Engine
+{
+private:
+ GameField m_GameField;
+
+ Figure* m_Figure = nullptr;
+
+ bool m_End = false;
+
+ const size_t m_Width = 14;
+ const size_t m_Height = 26;
+
+ int m_Score = 0;
+
+public:
+ Tetris();
+
+ ~Tetris() override;
+
+protected:
+ bool end() const override;
+
+ void on_button_press(int button) override;
+
+ void update(int dt) override;
+
+ void render(PaintDevice& paintDevice) override;
+};
\ No newline at end of file
diff --git a/core/Vector2.cpp b/core/Vector2.cpp
new file mode 100644
index 0000000..b8ab43e
--- /dev/null
+++ b/core/Vector2.cpp
@@ -0,0 +1,121 @@
+#include "Vector2.h"
+
+Vector2::Vector2()
+ : m_X(0),
+ m_Y(0)
+{
+
+}
+
+Vector2::Vector2(
+ PointType x,
+ PointType y
+)
+ : m_X(x),
+ m_Y(y)
+{
+
+}
+
+Vector2::PointType Vector2::x() const
+{
+ return m_X;
+}
+
+Vector2::PointType Vector2::y() const
+{
+ return m_Y;
+}
+
+Vector2::PointType& Vector2::x()
+{
+ return m_X;
+}
+
+Vector2::PointType& Vector2::y()
+{
+ return m_Y;
+}
+
+Vector2 Vector2::operator*(
+ const PointType& rhs
+) const
+{
+ return Vector2(
+ m_X * rhs,
+ m_Y * rhs
+ );
+}
+
+Vector2& Vector2::operator+=(
+ const Vector2& rhs
+)
+{
+ m_X += rhs.m_X;
+ m_Y += rhs.m_Y;
+
+ return *this;
+}
+
+Vector2& Vector2::operator-=(
+ const Vector2& rhs
+)
+{
+ m_X -= rhs.m_X;
+ m_Y -= rhs.m_Y;
+
+ return *this;
+}
+
+bool operator==(
+ const Vector2& lhs,
+ const Vector2& rhs
+)
+{
+ return
+ lhs.m_X == rhs.m_X &&
+ lhs.m_Y == rhs.m_Y;
+}
+
+bool operator!=(
+ const Vector2& lhs,
+ const Vector2& rhs
+)
+{
+ return !(lhs == rhs);
+}
+
+Vector2 operator+(
+ Vector2 lhs,
+ const Vector2& rhs
+)
+{
+ lhs += rhs;
+
+ return lhs;
+}
+
+Vector2 operator-(
+ Vector2 lhs,
+ const Vector2& rhs
+)
+{
+ lhs -= rhs;
+
+ return lhs;
+}
+
+std::ostream& operator<<(
+ std::ostream& os,
+ const Vector2& obj
+)
+{
+ os
+ << "{"
+ << obj.x()
+ << ", "
+ << obj.y()
+ << "}";
+
+ return os;
+}
\ No newline at end of file
diff --git a/core/Vector2.h b/core/Vector2.h
new file mode 100644
index 0000000..7d2502f
--- /dev/null
+++ b/core/Vector2.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include
+
+class Vector2
+{
+ using PointType = long long;
+
+ friend bool operator==(
+ const Vector2& lhs,
+ const Vector2& rhs
+ );
+
+ friend Vector2 operator+(
+ Vector2 lhs,
+ const Vector2& rhs
+ );
+
+ friend Vector2 operator-(
+ Vector2 lhs,
+ const Vector2& rhs
+ );
+
+public:
+ Vector2();
+
+ Vector2(
+ PointType x,
+ PointType y
+ );
+
+ PointType x() const;
+ PointType y() const;
+
+ PointType& x();
+ PointType& y();
+
+ Vector2 operator*(
+ const PointType& rhs
+ ) const;
+
+ Vector2& operator+=(
+ const Vector2& rhs
+ );
+
+ Vector2& operator-=(
+ const Vector2& rhs
+ );
+
+private:
+ PointType m_X;
+ PointType m_Y;
+};
+
+bool operator==(
+ const Vector2& lhs,
+ const Vector2& rhs
+);
+
+bool operator!=(
+ const Vector2& lhs,
+ const Vector2& rhs
+);
+
+Vector2 operator+(
+ Vector2 lhs,
+ const Vector2& rhs
+);
+
+Vector2 operator-(
+ Vector2 lhs,
+ const Vector2& rhs
+);
+
+std::ostream& operator<<(
+ std::ostream& os,
+ const Vector2& obj
+);
\ No newline at end of file
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..4f4a7ed
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,14 @@
+#include "core/Tetris.h"
+#include
+
+
+int main()
+{
+ setlocale(LC_ALL, "");
+
+ Tetris game;
+
+ game.run();
+
+ return 0;
+}
\ No newline at end of file