GameTestLib/Tetris/Main.mqh
Nique_372 64289fa43e
2025-11-14 20:55:53 -05:00

622 lines
38 KiB
MQL5

//+------------------------------------------------------------------+
//| Main.mqh |
//| Copyright 2025, Niquel Mendoza. |
//| https://www.mql5.com/es/users/nique_372/news |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Niquel Mendoza."
#property link "https://www.mql5.com/es/users/nique_372/news"
#property strict
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
#include "Espacio\\Main.mqh"
#include "..\\..\\GrapichsByLeo\\General\\ARGBGenerator.mqh"
#include "..\\..\\GrapichsByLeo\\General\\TextoCanvas.mqh"
#include "..\\..\\MQLArticles\\Utils\\RandomSimple.mqh"
#include <Controls\\Dialog.mqh>
#include <Controls\\Picture.mqh>
enum ENUM_TETRIS_CURR_STATE
{
TETRIS_STATE_STOP = 0, // Fin ya no hacemos nada
TETRIS_STATE_PROCESS_FIGURE = 1,
TETRIS_STATE_ESPERAR_NUEVA_FIGURA = 2
};
const string g_tetris_str_status[3]
{
"Detenido",
"Procesando figura",
"Esperando nueva figura"
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CTetrisGame : public CAppDialog
{
private:
// Pestaña 1
CPicture m_image; // Imagen del canvas
//--- Del juego
ENUM_TETRIS_CURR_STATE m_state;
ENUM_TETRIS_CURR_STATE m_prev_state;
CColorGeneratorArgb m_color_gen;
//CRandomSimple m_random;
CCanvasCustom* m_canvas;
CEspacioUtilizable* m_espacio;
CTetrisFigura* m_curr_figura;
int m_factor_pixel;
uint m_back_clr;
uint m_clr_tetris;
//
int m_section_sizepx;
int m_section_px_step;
int m_x_inicial;
//
CTextCanvas* m_titulo;
//
CTextCanvas m_status_test;
CTextCanvas m_the_status_text;
//
CTextCanvas* m_score_text;
CTextCanvas* m_the_score_text;
int m_score;
//
CTextCanvas* m_copyright;
//
bool m_stop_game;
//
CTetrisFigura* RandomFigure();
// Procesamiento de figura
bool VerifyFigure(); // true: colisiona | false: no colisiona
bool ProcessFigure(); // true: FIn | false: continua
__forceinline void DeleteCurrFigure();
// Evento de tecla
ushort m_tecla_abajo;
ushort m_tecla_izquierda;
ushort m_tecla_derecha;
ushort m_tecla_pausar;
void OnTeclaAbajo();
void OnTeclaIzquierda();
void OnTeclaDerecha();
// Estado
void StateChange(ENUM_TETRIS_CURR_STATE state);
void StateResetLatValue();
//void DrawFigureInStatus();
void ResetGame();
void OnGameOver();
void OnEspaciosEliminados(int s);
void SetScoreValue(int value);
void UpdateImage();
//---
void Minimize() override;
void Maximize() override;
public:
CTetrisGame(void);
~CTetrisGame(void);
//---
void Init(int width, int height, int xsize_seccion, uint back_clr, uint tetris_clr, int scale);
void OnTimerEvent();
bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
void OnDeinitEvent(const int reason);
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CTetrisGame::CTetrisGame(void)
{
m_canvas = new CCanvasCustom();
m_score = 0;
m_espacio = new CEspacioUtilizable();
m_curr_figura = NULL;
m_titulo = new CTextCanvas();
m_score_text = new CTextCanvas();
m_copyright = new CTextCanvas();
m_the_score_text = new CTextCanvas();
m_tecla_abajo = 's';
m_tecla_derecha = 'd';
m_tecla_izquierda = 'a';
m_tecla_pausar = 'p';
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CTetrisGame::~CTetrisGame()
{
delete m_canvas;
delete m_espacio;
delete m_titulo;
delete m_score_text;
delete m_copyright;
delete m_the_score_text;
DeleteCurrFigure();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::ResetGame(void)
{
m_espacio.Resetear();
m_the_score_text.Text("0");
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::StateChange(ENUM_TETRIS_CURR_STATE state)
{
m_the_status_text.Text(g_tetris_str_status[state]);
UpdateImage();
m_prev_state = m_state;
m_state = state;
}
//+------------------------------------------------------------------+
void CTetrisGame::StateResetLatValue(void)
{
m_state = m_prev_state;
m_the_status_text.Text(g_tetris_str_status[m_state]);
UpdateImage();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::Minimize(void)
{
// m_stop_game = true; // Paramos el juego
StateChange(TETRIS_STATE_STOP);
CAppDialog::Minimize();
}
//+------------------------------------------------------------------+
void CTetrisGame::Maximize()
{
//m_stop_game = false;
StateResetLatValue();
CAppDialog::Maximize();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::Init(int width, int height, int xsize_seccion, uint back_clr, uint tetris_clr, int scale)
{
//--- Creacion del juego
// Bitamp
m_canvas.Create("restetrisgame", width, height, COLOR_FORMAT_ARGB_RAW);
m_canvas.Erase(back_clr);
m_back_clr = back_clr;
m_clr_tetris = tetris_clr;
m_factor_pixel = scale;
// Fontset
m_canvas.FontSet("Arial", 14);
// Creacion del titulo
m_titulo.Create(m_canvas, (width >> 1), 20, "Tetris", ::ColorToARGB(clrBlack), TA_CENTER | TA_VCENTER);
// Creacion del lienzo
int x1_l = 10;
int x2_l = width - xsize_seccion - 10;
int y1_l = 40;
int y2_l = height - 15;
int w2 = x2_l - x1_l;
m_section_sizepx = w2 / scale;
int s = m_section_sizepx >> 1;
if(s * 2 != m_section_sizepx) // No es par
{
m_section_sizepx = s * 2;
}
x2_l = (m_section_sizepx * scale);
m_section_px_step = m_section_sizepx >> 1;
m_x_inicial = x1_l;
PrintFormat("X Inicial = %d | Section size = %d | Section step size = %d", m_x_inicial, m_section_sizepx, m_section_px_step);
//---
m_canvas.Rectangle(x1_l - 1, y1_l - 1, x2_l + 1, y2_l + 1, 0xFFFFFFFF); // Borde
m_espacio.Init(m_canvas, y1_l, y2_l, x1_l, x2_l, m_clr_tetris); // Aqui pinta el fondo
// Creacion del score
int xt = (width - xsize_seccion) + (xsize_seccion >> 1);
const uint clr_blacl = ColorToARGB(clrBlack);
int curr_y = 45;
m_score_text.Create(m_canvas, xt, curr_y, "Score", clr_blacl, TA_CENTER | TA_VCENTER, 15, "Arial");
curr_y += 15;
m_the_score_text.Create(m_canvas, xt, curr_y, "0", clr_blacl, TA_CENTER | TA_VCENTER, 15, "Arial");
m_the_score_text.CleanColor(back_clr);
curr_y += 30;
m_status_test.Create(m_canvas, xt, curr_y, "Estado", clr_blacl, TA_CENTER | TA_VCENTER, 15, "Arial");
curr_y += 15;
m_the_status_text.Create(m_canvas, xt, curr_y, g_tetris_str_status[TETRIS_STATE_ESPERAR_NUEVA_FIGURA], clr_blacl, TA_CENTER | TA_VCENTER, 15, "Arial");
m_the_status_text.CleanColor(back_clr);
//--- Creacion del panel
// Panel
CAppDialog::Create(0, "Tetris By Leo", 0, 1, 1, width + 40, height + 100);
// Imagen
m_image.Create(0, "ImageTetris", 0, 20, 30, Width() - 30, Height() - 30);
m_image.BmpName(m_canvas.ResourceName());
Add(m_image);
// Finish
UpdateImage();
::Sleep(1000);
m_state = TETRIS_STATE_ESPERAR_NUEVA_FIGURA;
::EventSetMillisecondTimer(1000);
}
//+------------------------------------------------------------------+
//| Procesamiento de una figura |
//+------------------------------------------------------------------+
#define TESTRIS_PROCESS_FIGURA_OUT_RANGE 1
#define TESTRIS_PROCESS_FIGURA_FINALIZA 2
//+------------------------------------------------------------------+
bool CTetrisGame::VerifyFigure()
{
//---
int out_px[];
//--- Verificacion inicial
m_curr_figura.GetOcupatePixels(TETRIS_DIR_ABAJO, out_px);
if(m_espacio.YColision(out_px, m_curr_figura.Y2())) // La nueva figuira ya colisionca, se perdio
{
m_score = 0;
::Print("Perdiste XD");
DeleteCurrFigure(); // Eliminamos la figura
OnGameOver(); // Procesamos y paramos por ahora
return true; // Finalizaiocn, pero con fuera de rango
}
return false;
}
//+------------------------------------------------------------------+
bool CTetrisGame::ProcessFigure(void)
{
//--- Declracion de array de pixles
int out_px[];
//--- Paso la veriirfacion inicial
int curr_y = m_curr_figura.Y1(); // Y1 incial de la figura
curr_y += m_section_px_step + 1; // Aumentamos el y
Print("Nuevo y1: ", curr_y, " X1 Actual: ", m_curr_figura.X1());
m_curr_figura.GetOcupatePixels(curr_y, TETRIS_DIR_Y_ABAJO, out_px); // Obtenemos los pixceles ocuapdos por debajo
//--- Verificacion de colision
if(m_espacio.YColision(out_px, m_curr_figura.Y2())) // Verificamos si los proximos puixxles colisionan con el muro img
{
::PrintFormat("Colision | Curr y = %d | Curr y2 = %d| MaxY: %d", curr_y, m_curr_figura.Y2(), m_espacio.YEnd());
// El siguiente y colisiona
// Requerimos un resize
// Calcularemos el error que hay entre las cordenas "proximas" de abajo con el muro
int max_err = m_espacio.GetMaxErrorY(out_px, m_curr_figura.X1());
//::Print("Max Error: ", max_err);
const int next_y2 = curr_y + m_curr_figura.DistY();
const int new_y2 = next_y2 - (max_err);
const int new_y1 = new_y2 - m_curr_figura.DistY();
m_curr_figura.ChangeY(new_y1); // Corrgeimos el y dado que es menor o igual al y que tenem,os
const int e = m_espacio.AddFigure(m_curr_figura);
OnEspaciosEliminados(e);
UpdateImage();
return true; // Colisiona
}
else
{
// No hay colision, dibujamos
m_curr_figura.ChangeY(curr_y);
UpdateImage();
return false;
}
}
//+------------------------------------------------------------------+
void CTetrisGame::DeleteCurrFigure(void)
{
if(::CheckPointer(m_curr_figura) == POINTER_DYNAMIC)
delete m_curr_figura;
m_curr_figura = NULL;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::OnTimerEvent(void)
{
//::PrintFormat("Estado actual: %s", EnumToString(m_state));
switch(m_state)
{
case TETRIS_STATE_STOP:
break;
case TETRIS_STATE_ESPERAR_NUEVA_FIGURA:
{
m_curr_figura = RandomFigure();
if(!VerifyFigure())
{
// No hay errores continuamos
StateChange(TETRIS_STATE_PROCESS_FIGURE);
}
break; // Se sale
}
case TETRIS_STATE_PROCESS_FIGURE:
{
if(ProcessFigure()) // FIn de la figura actual
{
DeleteCurrFigure();
StateChange(TETRIS_STATE_ESPERAR_NUEVA_FIGURA);
}
break;
}
default:
break;
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CTetrisGame::OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
//Print("Hola");
#define ret return(CAppDialog::OnEvent(id, lparam, dparam, sparam))
if(id == CHARTEVENT_KEYDOWN)
{
const int k = (int)lparam;
const ushort key = TranslateKey(k);
//Print(ShortToString(key));
if(key == m_tecla_abajo)
{
OnTeclaAbajo();
}
else
if(key == m_tecla_derecha)
{
OnTeclaDerecha();
}
else
if(key == m_tecla_izquierda)
{
OnTeclaIzquierda();
}
ret;
}
if(id == CHARTEVENT_KEYUP)
{
const int k = (int)lparam;
const ushort key = TranslateKey(k);
if(key == m_tecla_pausar)
{
if(m_state == TETRIS_STATE_STOP)
{
StateResetLatValue();
}
else
{
StateChange(TETRIS_STATE_STOP);
}
}
ret;
}
//---
ret;
#undef ret
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::OnEspaciosEliminados(int s)
{
if(s <= 0)
return;
SetScoreValue((m_score + s * 100));
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::SetScoreValue(int value)
{
m_the_score_text.Text(::IntegerToString(value));
UpdateImage();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::OnDeinitEvent(const int reason)
{
Destroy(reason);
::EventKillTimer();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::UpdateImage(void)
{
m_canvas.Update(true); // Solo actulizo canvas el recurso
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::OnGameOver(void)
{
//---
StateChange(TETRIS_STATE_STOP);
//---
const int res = ::MessageBox("Ha perdido el juego, desea continuar ?", "Game over", MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON1);
if(res == IDYES)
{
ResetGame();
StateChange(TETRIS_STATE_ESPERAR_NUEVA_FIGURA);
}
else
{
::ExpertRemove();
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::OnTeclaAbajo(void)
{
if(m_curr_figura == NULL)
return;
if(ProcessFigure()) // FIn de la figura actual
{
DeleteCurrFigure();
StateChange(TETRIS_STATE_ESPERAR_NUEVA_FIGURA);
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::OnTeclaDerecha(void)
{
if(m_curr_figura == NULL || m_curr_figura.IsActiveColisionEnd())
return;
//---
const int change = (m_section_px_step);
//---
const int x2 = m_curr_figura.X2();
int new_x = m_curr_figura.X1() + change;
int out_px[];
m_curr_figura.GetOcupatePixels(new_x, TETRIS_DIR_X_DERECHA, out_px);
Print("Nuevo x1: ", new_x);
if(m_espacio.XColisionAdelante(out_px, x2))
{
Print("Colision");
int max_err = m_espacio.GetMaxErrorXAdelente(out_px, x2);
Print("Maximo erorr = ", max_err);
int next_x2 = new_x + m_curr_figura.DistX();
Print("X2 nex: ", next_x2);
int new_x2 = next_x2 - (max_err);
Print("Nuevo x2: ", new_x2);
new_x = new_x2 - m_curr_figura.DistX();
Print("Nuevo x1: ", new_x);
m_curr_figura.ChangeX(new_x);
m_curr_figura.FlagColisionEndAdd(); // Colisiona con add
m_curr_figura.FlagColisionInitRemove();
}
else
{
m_curr_figura.ChangeX(new_x);
m_curr_figura.FlagColisionInitRemove();
}
UpdateImage();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CTetrisGame::OnTeclaIzquierda(void)
{
if(m_curr_figura == NULL || m_curr_figura.IsActiveColisionInit())
return;
//---
const int change = (m_section_px_step);
//---
const int x1 = m_curr_figura.X1();
int new_x = x1 - change;
int out_px[];
m_curr_figura.GetOcupatePixels(new_x, TETRIS_DIR_X_IZQUIERDA, out_px);
// Print("Nuevo x1: ", new_x);
if(m_espacio.XColisionAtras(out_px, x1))
{
// Print("Colision");
int max_err = m_espacio.GetMaxErrorXAtras(out_px, x1);
new_x = new_x + max_err;
m_curr_figura.ChangeX(new_x);
m_curr_figura.FlagColisionInitAdd(); // Colisiona con add
m_curr_figura.FlagColisionEndRemove();
}
else
{
m_curr_figura.ChangeX(new_x);
m_curr_figura.FlagColisionEndRemove();
}
UpdateImage();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CTetrisFigura* CTetrisGame::RandomFigure(void)
{
//Print("Nueva figura");
CTetrisFigura* fig = new CTetrisFiguraCuadrado();
//---
const int x1 = m_x_inicial;
const int x2 = m_x_inicial + m_section_sizepx;
const int y1 = m_espacio.YInit();
const int y2 = y1 + m_section_sizepx;
fig.Init(x1, y1, x2, y2, m_canvas, m_clr_tetris, m_color_gen.Next());
UpdateImage();
return fig;
}
//+------------------------------------------------------------------+