Article-22351-Crazy-Scalper.../Crazy_Scalper.mq5

446 lines
18 KiB
MQL5
Raw Permalink Normal View History

2026-06-25 19:12:10 +00:00
//+------------------------------------------------------------------+
//| Crazy Scalper |
//| Copyright 2020, Crazy Scalper |
//| https://www.mql5.com/es/users/dayanacubillas |
//+------------------------------------------------------------------+
#property copyright "Crazy Scalper"
#property link "https://www.mql5.com/es/users/dayanacubillas"
#property version "1.00"
//--- Include the standard MQL5 library for rendering graphics on the screen
#include <Canvas\Canvas.mqh>
//+------------------------------------------------------------------+
//| Global Variables for the Graphical Engine |
//+------------------------------------------------------------------+
CCanvas canvas; // The main object that will act as our drawing canvas
int g_chartWidth; // Will store the actual chart window width in pixels
int g_chartHeight; // Will store the actual chart window height in pixels
bool g_isInitialized = false; // Security flag to prevent timer execution before initialization
//+------------------------------------------------------------------+
//| Game State Machine |
//+------------------------------------------------------------------+
enum ENUM_GAME_STATE
{
START_SCREEN, // Initial screen waiting for user input
PLAYING, // Active gameplay state
GAME_OVER // Liquidation screen (Margin Call)
};
ENUM_GAME_STATE g_gameState = START_SCREEN;
//+------------------------------------------------------------------+
//| Physics and Movement Variables |
//+------------------------------------------------------------------+
double g_playerY; // Current vertical position of the rocket
double g_playerX; // Current horizontal position of the rocket
double g_velocityY; // Current falling speed
const double GRAVITY = 0.6; // Constant downward force applied every frame
const double JUMP_STRENGTH = -9.5; // Upward force (negative Y) applied on key press
int g_score = 0; // Current game score (Profit)
int g_bestScore = 0; // All-Time High score
//+------------------------------------------------------------------+
//| Obstacles (Japanese Candles) Structure and Variables |
//+------------------------------------------------------------------+
struct CandlePipe
{
double x; // Horizontal position of the candle obstacle
double gap_y; // Vertical center of the safe gap for the rocket
bool passed; // Flag to avoid counting the same obstacle twice
};
CandlePipe g_pipes[3]; // Array to hold a maximum of 3 obstacles on screen
double g_pipeSpeed; // Current moving speed of the obstacles
int g_pipeGap; // Vertical space between the upper and lower candles
int g_pipeSpacing; // Horizontal distance between one obstacle and the next
//--- Base difficulty constants
const double BASE_PIPE_SPEED = 6.0; // Starting speed
const int BASE_PIPE_GAP = 220; // Starting vertical gap
const int BASE_PIPE_SPACING = 450; // Starting horizontal distance
const int PIPE_WIDTH = 45; // Fixed width of the candlestick obstacles
//--- Variable to animate the background grid (Parallax effect)
double g_gridOffsetX = 0.0;
//+------------------------------------------------------------------+
//| Cyberpunk Color Palette using Alpha Channel (ARGB) |
//+------------------------------------------------------------------+
uint CLR_BG = ColorToARGB(C'15,17,20', 255); // Dark solid background (Alpha 255)
uint CLR_GRID = ColorToARGB(C'25,28,32', 255); // Grid lines color
uint CLR_ACCENT = ColorToARGB(C'0,240,240', 255); // Neon Cyan for details
uint CLR_BULL = ColorToARGB(C'40,167,69', 255); // Institutional Green (Bull candle)
uint CLR_BEAR = ColorToARGB(C'242,54,69', 255); // Institutional Red (Bear candle)
uint CLR_ROCKET = ColorToARGB(C'255,160,0', 255); // Orange/Gold for the rocket body
uint CLR_FIRECORE = ColorToARGB(C'255,255,100', 255); // Yellow for the engine fire
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- Initialize the random number generator using the system tick count
MathSrand(GetTickCount());
//--- Hide the standard MT5 candlestick chart to provide a clean visual space
ChartSetInteger(0, CHART_SHOW, false);
//--- Get the actual screen dimensions in pixels from the user's terminal
g_chartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
g_chartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
//--- Create the canvas using COLOR_FORMAT_ARGB_NORMALIZE to support true Alpha transparency
if(!canvas.CreateBitmapLabel(0, 0, "FlappyScreen", 0, 0, g_chartWidth, g_chartHeight, COLOR_FORMAT_ARGB_NORMALIZE))
{
return(INIT_FAILED);
}
//--- Setup initial game variables
ResetGame();
//--- Set a high-frequency timer to 12 milliseconds (approx. 83 Frames Per Second)
EventSetMillisecondTimer(12);
g_isInitialized = true;
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- Stop the game loop
EventKillTimer();
//--- Destroy the canvas to free up RAM
canvas.Destroy();
//--- Restore the standard MT5 chart view
ChartSetInteger(0, CHART_SHOW, true);
}
//+------------------------------------------------------------------+
//| Reset the game to initial state |
//+------------------------------------------------------------------+
void ResetGame()
{
//--- Position the rocket vertically in the center and horizontally to the left
g_playerX = g_chartWidth * 0.2;
g_playerY = g_chartHeight / 2.0;
g_velocityY = 0.0;
g_score = 0;
//--- Restore base difficulty parameters
g_pipeSpeed = BASE_PIPE_SPEED;
g_pipeGap = BASE_PIPE_GAP;
g_pipeSpacing = BASE_PIPE_SPACING;
//--- Generate the first 3 obstacles outside the right edge of the screen
for(int i = 0; i < 3; i++)
{
g_pipes[i].x = g_chartWidth + (i * g_pipeSpacing);
g_pipes[i].gap_y = (double)(MathRand() % (g_chartHeight - 300) + 150);
g_pipes[i].passed = false;
}
}
//+------------------------------------------------------------------+
//| Timer function (Main Game Loop) |
//+------------------------------------------------------------------+
void OnTimer()
{
//--- Prevent execution if initialization failed
if(!g_isInitialized)
{
return;
}
//--- Check if the user resized the MT5 window
int curr_w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
int curr_h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
if(curr_w != g_chartWidth || curr_h != g_chartHeight)
{
g_chartWidth = curr_w;
g_chartHeight = curr_h;
canvas.Resize(g_chartWidth, g_chartHeight);
}
//--- STEP 1: CLEAR THE SCREEN (Erase the previous frame to avoid infinite trails)
canvas.Erase(CLR_BG);
//--- Calculate the Parallax background offset
if(g_gameState == PLAYING)
{
g_gridOffsetX -= (g_pipeSpeed * 0.5); // Move background slower than obstacles
if(g_gridOffsetX <= -40.0)
{
g_gridOffsetX += 40.0; // Infinite loop reset
}
}
//--- Draw the dynamic background grid
for(int x = (int)g_gridOffsetX; x < g_chartWidth + 40; x += 40)
{
canvas.LineVertical(x, 0, g_chartHeight, CLR_GRID);
}
for(int y = 0; y < g_chartHeight; y += 40)
{
canvas.LineHorizontal(0, g_chartWidth, y, CLR_GRID);
}
//--- STEP 2: CALCULATE AND DRAW BASED ON CURRENT STATE
if(g_gameState == START_SCREEN)
{
DrawStartScreen();
}
else
{
if(g_gameState == PLAYING)
{
UpdatePhysics();
DrawPipes();
DrawRocket();
DrawScore();
}
else
{
if(g_gameState == GAME_OVER)
{
DrawPipes();
DrawRocket();
DrawGameOver();
}
}
}
//--- STEP 3: UPDATE DISPLAY (Send the rendered graphics to the monitor)
canvas.Update();
}
//+------------------------------------------------------------------+
//| Physics and Collision Updates |
//+------------------------------------------------------------------+
void UpdatePhysics()
{
//--- Apply gravity to the falling speed
g_velocityY += GRAVITY;
//--- Move the rocket along the Y axis
g_playerY += g_velocityY;
//--- Detect collision with the ceiling or the floor
if(g_playerY < 0.0 || g_playerY > g_chartHeight)
{
TriggerGameOver();
return;
}
//--- Update obstacles and check for collisions
for(int i = 0; i < 3; i++)
{
g_pipes[i].x -= g_pipeSpeed;
//--- If the obstacle leaves the screen, recycle it to the right
if(g_pipes[i].x < -PIPE_WIDTH)
{
g_pipes[i].x += g_pipeSpacing * 3.0;
g_pipes[i].gap_y = (double)(MathRand() % (g_chartHeight - 300) + 150); // New random gap height
g_pipes[i].passed = false;
}
//--- AABB Collision Detection (Hitbox evaluation)
// Horizontal check
if(g_playerX + 15.0 > g_pipes[i].x && g_playerX - 15.0 < g_pipes[i].x + PIPE_WIDTH)
{
//--- Vertical check (Did it hit the upper bear candle or lower bull candle?)
if(g_playerY - 12.0 < g_pipes[i].gap_y - g_pipeGap / 2.0 || g_playerY + 12.0 > g_pipes[i].gap_y + g_pipeGap / 2.0)
{
TriggerGameOver();
return;
}
}
//--- Score counter (Executed only once when passing an obstacle)
if(!g_pipes[i].passed && g_playerX > g_pipes[i].x + PIPE_WIDTH)
{
g_score++;
g_pipes[i].passed = true;
PlaySound("ok.wav");
//--- DYNAMIC DIFFICULTY RAMP-UP (Triggered every 10 points)
if(g_score % 10 == 0)
{
if(g_pipeSpeed < 12.0)
{
g_pipeSpeed += 1.5; // Increase market volatility (speed)
}
if(g_pipeGap > 110)
{
g_pipeGap -= 20; // Decrease safety gap
}
if(g_pipeSpacing > 250)
{
g_pipeSpacing -= 30; // Decrease distance between candles
}
}
}
}
}
//+------------------------------------------------------------------+
//| Trigger Game Over State |
//+------------------------------------------------------------------+
void TriggerGameOver()
{
g_gameState = GAME_OVER;
//--- Save the All-Time High score in memory
if(g_score > g_bestScore)
{
g_bestScore = g_score;
}
//--- Play the classic MT5 disconnection sound as a Margin Call alert
PlaySound("disconnect.wav");
}
//+------------------------------------------------------------------+
//| Vector Engine: Draw the Rocket |
//+------------------------------------------------------------------+
void DrawRocket()
{
int px = (int)g_playerX;
int py = (int)g_playerY;
//--- 1. Draw dynamic engine fire if the rocket is jumping (negative velocity)
if(g_velocityY < 0.0)
{
int fire_len = (MathRand() % 15) + 15;
uchar flicker_alpha = (uchar)((MathRand() % 100) + 100);
uint clrFireOuter = ColorToARGB(C'255,80,30', flicker_alpha);
//--- Transparent outer flame (Aura)
canvas.FillTriangle(px - 10, py - 8, px - 10, py + 8, px - 10 - fire_len - 5, py, clrFireOuter);
//--- Solid inner core flame
canvas.FillTriangle(px - 10, py - 4, px - 10, py + 4, px - 10 - fire_len, py, CLR_FIRECORE);
}
//--- 2. Draw the main fuselage using basic geometry
canvas.FillRectangle(px - 10, py - 6, px + 8, py + 6, CLR_ROCKET);
canvas.FillTriangle(px + 8, py - 6, px + 8, py + 6, px + 18, py, CLR_ACCENT);
//--- 3. Draw upper and lower aerodynamic fins
canvas.FillTriangle(px - 10, py - 6, px - 2, py - 6, px - 10, py - 12, CLR_ACCENT);
canvas.FillTriangle(px - 10, py + 6, px - 2, py + 6, px - 10, py + 12, CLR_ACCENT);
//--- 4. Draw the cockpit window
canvas.FillCircle(px, py, 3, CLR_BG);
}
//+------------------------------------------------------------------+
//| Vector Engine: Draw the Candles (Obstacles) |
//+------------------------------------------------------------------+
void DrawPipes()
{
for(int i = 0; i < 3; i++)
{
int px = (int)g_pipes[i].x;
int gap_top = (int)(g_pipes[i].gap_y - g_pipeGap / 2.0);
int gap_bot = (int)(g_pipes[i].gap_y + g_pipeGap / 2.0);
//--- BEAR CANDLE (Dropping from the ceiling)
canvas.FillRectangle(px, 0, px + PIPE_WIDTH, gap_top, CLR_BEAR);
canvas.LineVertical(px + PIPE_WIDTH / 2, gap_top, gap_top + 20, ColorToARGB(C'150,150,150', 255));
canvas.Rectangle(px, 0, px + PIPE_WIDTH, gap_top, ColorToARGB(clrBlack, 255));
//--- BULL CANDLE (Rising from the floor)
canvas.FillRectangle(px, gap_bot, px + PIPE_WIDTH, g_chartHeight, CLR_BULL);
canvas.LineVertical(px + PIPE_WIDTH / 2, gap_bot - 20, gap_bot, ColorToARGB(C'150,150,150', 255));
canvas.Rectangle(px, gap_bot, px + PIPE_WIDTH, g_chartHeight, ColorToARGB(clrBlack, 255));
}
}
//+------------------------------------------------------------------+
//| Draw the Score Text (Profit) |
//+------------------------------------------------------------------+
void DrawScore()
{
canvas.FontSet("Arial", 40, FW_BOLD);
string profit_text = "Profit: +$" + IntegerToString(g_score);
//--- Render text with Alpha 220 to create a slight transparency effect
canvas.TextOut(g_chartWidth / 2, 50, profit_text, ColorToARGB(C'40,167,69', 220), TA_CENTER|TA_VCENTER);
}
//+------------------------------------------------------------------+
//| Draw the Start Screen UI |
//+------------------------------------------------------------------+
void DrawStartScreen()
{
canvas.FontSet("Trebuchet MS", 60, FW_BOLD);
canvas.TextOut(g_chartWidth / 2, g_chartHeight / 2 - 80, "CRAZY SCALPER", CLR_ACCENT, TA_CENTER|TA_VCENTER);
//--- Use a Sine wave function based on system ticks to create a pulsing animation
uchar pulse_alpha = (uchar)(155.0 + 100.0 * MathSin(GetTickCount() * 0.005));
canvas.FontSet("Arial", 20, FW_NORMAL);
canvas.TextOut(g_chartWidth / 2, g_chartHeight / 2 + 20, "Press SPACE or UP ARROW to trade", ColorToARGB(clrWhite, pulse_alpha), TA_CENTER|TA_VCENTER);
}
//+------------------------------------------------------------------+
//| Draw the Game Over Screen UI |
//+------------------------------------------------------------------+
void DrawGameOver()
{
//--- Draw a semi-transparent black rectangle over the entire screen for dramatic effect
canvas.FillRectangle(0, 0, g_chartWidth, g_chartHeight, ColorToARGB(C'0,0,0', 180));
canvas.FontSet("Arial", 60, FW_BOLD);
canvas.TextOut(g_chartWidth / 2, g_chartHeight / 2 - 100, "MARGIN CALL", CLR_BEAR, TA_CENTER|TA_VCENTER);
canvas.FontSet("Arial", 25, FW_NORMAL);
string stats = "Final Profit: +$" + IntegerToString(g_score) + " | ATH: +$" + IntegerToString(g_bestScore);
canvas.TextOut(g_chartWidth / 2, g_chartHeight / 2 - 20, stats, CLR_ROCKET, TA_CENTER|TA_VCENTER);
canvas.FontSet("Arial", 16, FW_NORMAL);
canvas.TextOut(g_chartWidth / 2, g_chartHeight / 2 + 80, "(Press SPACE to deposit new funds... I mean, try again)", ColorToARGB(C'150,150,150', 255), TA_CENTER|TA_VCENTER);
}
//+------------------------------------------------------------------+
//| Chart Event Handler (Keyboard Inputs) |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
//--- Check if the event is a keyboard key press
if(id == CHARTEVENT_KEYDOWN)
{
//--- Check if the pressed key is SPACE (32) or UP ARROW (38)
if(lparam == 32 || lparam == 38)
{
if(g_gameState == START_SCREEN)
{
ResetGame();
g_gameState = PLAYING;
}
else
{
if(g_gameState == PLAYING)
{
//--- Apply upward thrust fighting gravity
g_velocityY = JUMP_STRENGTH;
}
else
{
if(g_gameState == GAME_OVER)
{
g_gameState = START_SCREEN;
}
}
}
}
}
}
//+------------------------------------------------------------------+