//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+ //| 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; } } } } } } //+------------------------------------------------------------------+