422 lines
11 KiB
C++
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;
|
|
}
|