622 lines
38 KiB
MQL5
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;
|
|
}
|
|
//+------------------------------------------------------------------+
|