Files
Retris/main.cpp
2025-02-28 21:04:11 -05:00

422 lines
11 KiB
C++

#include <iostream>
#include <ReWindow/types/Window.h>
#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<int> 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;
}