From 8b7d1cc7a016cc746ede5e79deec9b28ab28b0ad Mon Sep 17 00:00:00 2001 From: josh Date: Fri, 28 Feb 2025 21:04:11 -0500 Subject: [PATCH] Initial Commit --- CMakeLists.txt | 49 ++++++ cmake/CPM.cmake | 24 +++ main.cpp | 421 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 494 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/CPM.cmake create mode 100644 main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2fc1e33 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.18..3.30) +project(Retris + VERSION 1.0 + LANGUAGES CXX) + +if (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) + message(FATAL_ERROR, "In-source builds are not allowed") +endif() + +set(CMAKE_CXX_STANDARD 20) + +include(cmake/CPM.cmake) + +CPMAddPackage( + NAME mcolor + URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-6.3.zip +) + +CPMAddPackage( + NAME J3ML + URL https://git.redacted.cc/josh/j3ml/archive/3.4.5.zip +) + +CPMAddPackage( + NAME ReWindow + URL https://git.redacted.cc/Redacted/ReWindow/archive/Prerelease-32.zip +) + +CPMAddPackage( + NAME JGL + URL https://git.redacted.cc/josh/JGL/archive/Prerelease-52.zip +) + +CPMAddPackage( + NAME JUI + URL https://git.redacted.cc/josh/ReJUI/archive/Prerelease-5.14.zip +) + + + +add_executable(Retris main.cpp) + +target_include_directories(Retris PUBLIC + ${J3ML_SOURCE_DIR}/include + ${ReWindow_SOURCE_DIR}/include + ${JGL_SOURCE_DIR}/include + ${JUI_SOURCE_DIR}/include) + +target_link_libraries(Retris PUBLIC J3ML ReWindow JGL JUI) diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 0000000..d866ad7 --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +# +# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors + +set(CPM_DOWNLOAD_VERSION 0.38.7) +set(CPM_HASH_SUM "83e5eb71b2bbb8b1f2ad38f1950287a057624e385c238f6087f94cdfc44af9c5") + +if(CPM_SOURCE_CACHE) + set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +elseif(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +else() + set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +endif() + +# Expand relative path. This is important if the provided path contains a tilde (~) +get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) + +file(DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} +) + +include(${CPM_DOWNLOAD_LOCATION}) \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..a5f699a --- /dev/null +++ b/main.cpp @@ -0,0 +1,421 @@ +#include +#include +#include "jlog/Logger.hpp" +#include "ReWindow/Logger.h" +#include "JGL/JGL.h" + +using namespace ReWindow; + +#define GL_MAJOR 2 +#define GL_MINOR 1 + +int fieldWidth = 24; +int fieldHeight = 27; +float scale = 32; + +int window_width = fieldWidth * scale; +int window_height = (fieldHeight * scale)+20; + +Color4 tetcolors[10] = { + Colors::Red, + Colors::Purples::Magenta, + Colors::Yellow, + Colors::Green, + Colors::Cyans::Cyan, + Colors::Blue, + Colors::Oranges::Gold, + Colors::Reds::DarkRed, + Colors::Gray, + Colors::White, +}; + +std::wstring tetramino[7]; + +void define_tetraminos() +{ + tetramino[0].append(L" X "); + tetramino[0].append(L" X "); + tetramino[0].append(L" X "); + tetramino[0].append(L" X "); + + tetramino[1].append(L" X "); + tetramino[1].append(L" XX "); + tetramino[1].append(L" X "); + tetramino[1].append(L" "); + + tetramino[2].append(L" X "); + tetramino[2].append(L" XX "); + tetramino[2].append(L" X "); + tetramino[2].append(L" "); + + tetramino[3].append(L" "); + tetramino[3].append(L" XX "); + tetramino[3].append(L" XX "); + tetramino[3].append(L" "); + + tetramino[4].append(L" X "); + tetramino[4].append(L" XX "); + tetramino[4].append(L" X "); + tetramino[4].append(L" "); + + tetramino[5].append(L" "); + tetramino[5].append(L" XX "); + tetramino[5].append(L" X "); + tetramino[5].append(L" X "); + + tetramino[6].append(L" "); + tetramino[6].append(L" XX "); + tetramino[6].append(L" X "); + tetramino[6].append(L" X "); +} + + +int rotate(int px, int py, int r) +{ + switch(r % 4) + { + case 0 : return py * 4 + px; // 0 degrees + case 1 : return 12 + py - (px*4); // 90 degrees + case 2 : return 15 - (py * 4) - px; // 180 degrees + case 3 : return 3 - py + (px * 4); // 270 degrees; + } + return 0; +} + + + +unsigned char *pField = nullptr; + +void setup_playing_field() +{ + pField = new unsigned char [fieldWidth* fieldHeight]; + for (int x = 0; x < fieldWidth; x++) { + for (int y = 0; y < fieldHeight; y++) { + pField[y*fieldWidth + x] = (x == 0 || x == fieldWidth - 1 || y == fieldHeight -1) ? 9 : 0; + } + } +} + +bool does_piece_fit(int nTetramino, int rotation, int posX, int posY) +{ + for (int px = 0; px < 4; px++) { + for (int py = 0; py < 4; py++) { + int pi = rotate(px, py, rotation); + int fi = (posY + py) * fieldWidth + (posX + px); + + if (posX + px >= 0 && posX + px < fieldWidth) { + if (posY + py >= 0 && posY + py < fieldHeight) { + if (tetramino[nTetramino][pi] == L'X' && pField[fi] != 0) + return false; + } + } + } + } + return true; +} + + +class RetrisWindow : public OpenGLWindow { +public: + int score = 0; + float gameTime = 0; + int currentPiece = 0; + int currentRotation = 0; + int currentX = fieldWidth / 2; + int currentY = 0; + + float gamespeed = 1; // Increment to speed up gameticks. + + bool gameOver = false; + + std::vector vLines; + + + void initGL() { + + if (!JGL::Init({ GetSize().x, GetSize().y}, 75, 100)) + Logger::Fatal("Initialization failed."); + + + glClearColor(0.f, 0.f, 0.f, 0.f); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDepthMask(GL_TRUE); + } + + RetrisWindow() : OpenGLWindow("Retris - A Tetris Clone by redacted.cc", window_width, window_height, GL_MAJOR, GL_MINOR) + { + score = 0; + ResetGameState(); + } + + void ResetGameState() + { + score = 0; + gameTime = 0; + currentPiece = 0; + currentRotation = 0; + currentX = fieldWidth / 2; + currentY = 0; + gamespeed = 1; + } + + + + + void DrawBoard() + { + for (int x = 0; x < fieldWidth; x++) + { + for (int y = 0; y < fieldHeight; y++) + { + auto cell = pField[y * fieldWidth + x]; + if (cell > 0) + { + Color4 color = tetcolors[cell]; + Color4 outline = color.Lerp(Colors::White, 0.5f); + + JGL::J2D::FillChamferRect(outline, Vector2{x*scale, y*scale}+Vector2{8, 8}, {scale-16, scale-16}, 10); + JGL::J2D::FillChamferRect(color, {x*scale, y*scale}, {scale, scale}, 5); + } + } + } + } + + void DrawTetramino(int pieceID, int curX, int curY, int rot) + { + for (int px = 0; px < 4; px++) { + for (int py = 0; py < 4; py++) { + if (tetramino[pieceID][rotate(px, py, rot)]==L'X') { + Color4 color = tetcolors[pieceID+1]; + Color4 outline = color.Lerp(Colors::White, 0.5f); + JGL::J2D::FillChamferRect(outline, Vector2((curX+px)*scale , (curY+py)*scale) + Vector2{8, 8}, {scale-16, scale-16}, 10); + JGL::J2D::FillChamferRect(color, {(curX+px)*scale, (curY+py)*scale}, {scale, scale}, 5); + } + } + } + } + + void DrawCurrentPiece() { + DrawTetramino(currentPiece, currentX, currentY, currentRotation); + } + + void DrawInfo() { + if (gameOver) + { + J2D::DrawString(Colors::White, "You lose, fuckface!!", 0, 0, 1, 12); + } else { + J2D::DrawString(Colors::White, "Score:"+std::to_string(score), 0, 0, 1, 12); + J2D::DrawString(Colors::White, "Next:", 0, 14, 1, 12); + } + } + + void Input() + { + /* + if (IsKeyDown(Keys::LeftArrow) && + does_piece_fit(currentPiece, currentRotation, currentX-1, currentY)) + currentX--; + + if (IsKeyDown(Keys::RightArrow) && + does_piece_fit(currentPiece, currentRotation, currentX+1, currentY)) + currentX++; + + if (IsKeyDown(Keys::DownArrow) && + does_piece_fit(currentPiece, currentRotation, currentX+1, currentY)) + currentX++; + + if (IsKeyDown(Keys::RightControl) && + does_piece_fit(currentPiece, currentRotation, currentX+1, currentY)) + currentRotation++; + + if (IsKeyDown(Keys::RightShift) && + does_piece_fit(currentPiece, currentRotation, currentX+1, currentY)) + currentRotation--; + */ + } + + + void OnKeyDown(const ReWindow::KeyDownEvent &ev) override + { + auto key = ev.key; + + if (key == Keys::LeftArrow && + does_piece_fit(currentPiece, currentRotation, currentX-1, currentY)) + currentX--; + + if (key == Keys::RightArrow && + does_piece_fit(currentPiece, currentRotation, currentX+1, currentY)) + currentX++; + + if (key == Keys::DownArrow && + does_piece_fit(currentPiece, currentRotation, currentX, currentY+1)) + currentY++; + + if (key == Keys::UpArrow && + does_piece_fit(currentPiece, currentRotation+1, currentX, currentY)) + currentRotation++; + + /*if (key == Keys::RightShift && + does_piece_fit(currentPiece, currentRotation-1, currentX, currentY)) + currentRotation--; + */ + + if (key == Keys::Escape) + ResetGameState(); + } + + + void Render() + { + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + + JGL::J2D::Begin(); + DrawBoard(); + DrawCurrentPiece(); + DrawInfo(); + JGL::J2D::End(); + } + + // Checks for instances of "Tetris" (a full line) on the board + void CheckForTetris() + { + // Check for horiz lines + for (int py = 0; py < 4; py++) + if (currentY + py < fieldHeight - 1) + { + bool bLine = true; + for (int px = 1; px < fieldWidth-1; px++) + { + bLine &= (pField[(currentY+py)*fieldWidth+px]) != 0; + } + + if (bLine) + { + for (int px = 1; px < fieldWidth-1; px++) + { + pField[(currentY+py) * fieldWidth + px] = 8; + + } + + vLines.push_back(currentY+py); + } + } + } + + void ClearLines() + { + int prescore = 0; + for (auto &v: vLines) + { + prescore++; + for (int px = 1; px < fieldWidth-1; px++) + { + for (int py = v; py > 0; py--) + { + pField[py*fieldWidth+px] = pField[(py-1)*fieldWidth+px]; + + } + pField[px] = 0; + } + } + + + score += 50*prescore; + vLines.clear(); + + } + + void NextPiece() + { + // Choose Next Piece + currentX = fieldWidth/2; + currentY = 0; + currentRotation = 0; + currentPiece = rand() % 7; + } + void DropCurrentPiece() { + // lock current piece into the field + for (int px = 0; px < 4; px++) + { + for (int py = 0; py < 4; py++) + { + if (tetramino[currentPiece][rotate(px, py, currentRotation)]==L'X') + pField[(currentY+py)*fieldWidth + (currentX+px)] = currentPiece+1; + } + } + } + void GameTick() + { + + // if piece does not fit + gameOver = !does_piece_fit(currentPiece, currentRotation, currentX, currentY); + if (gameOver) + { + //ResetGameState(); + return; + } + + // Can the piece move? + if (does_piece_fit(currentPiece, currentRotation, currentX, currentY+1)) { + currentY++; + } else { + DropCurrentPiece(); + CheckForTetris(); + NextPiece(); + gamespeed++; + + if (!vLines.empty()) + { + //DrawBoard(); // ASS HACK + //using namespace std::chrono_literals; + //std::this_thread::sleep_for(400ms); + ClearLines(); + } + } + } + + void OnRefresh(float elapsed) override + { + OpenGLWindow::SwapBuffers(); + + gameTime += elapsed; + + if (gameTime > 1.f / gamespeed) { + gameTime = 0; + GameTick(); + } + + Input(); + Render(); + OpenGLWindow::OnRefresh(elapsed); + } + +}; + + +int main() { + /// Suppress logging from RWindow library. + ReWindow::Logger::Debug.EnableConsole(false); + + + define_tetraminos(); + setup_playing_field(); + + auto* window = new RetrisWindow(); + + if (!window->Open()) + std::cerr << "Unable to open Retris Window" << std::endl; + + window->initGL(); + + while (!window->IsClosing()) + window->ManagedRefresh(); + + delete window; + return 0; +}