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