Recent

Author Topic: Match 3 game  (Read 189 times)

Guva

  • Full Member
  • ***
  • Posts: 201
  • 🌈 ZX-Spectrum !!!
Match 3 game
« on: November 17, 2025, 02:47:54 pm »
Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6. {$IFDEF LINUX} cthreads,{$ENDIF}
  7.   Classes, SysUtils, CustApp, raylib, Math;
  8.  
  9. type
  10.   { TRayApplication }
  11.   TRayApplication = class(TCustomApplication)
  12.   protected
  13.     procedure DoRun; override;
  14.   public
  15.     constructor Create(TheOwner: TComponent); override;
  16.     destructor Destroy; override;
  17.   end;
  18.  
  19.   TGameState = (gsPlaying, gsAnimating, gsGameOver);
  20.   TCellState = (csEmpty, csFalling, csNormal);
  21.   TCellType = (ctRed, ctGreen, ctBlue, ctYellow, ctPurple, ctOrange);
  22.  
  23.   TCell = record
  24.     CellType: TCellType;
  25.     State: TCellState;
  26.     Position: TVector2;
  27.     TargetY: Single;
  28.   end;
  29.  
  30. const
  31.   AppTitle = 'raylib - Match-3 Game';
  32.   GRID_SIZE = 8;
  33.   CELL_SIZE = 60;
  34.   GRID_OFFSET_X = 50;
  35.   GRID_OFFSET_Y = 50;
  36.   FALL_SPEED = 5.0;
  37.  
  38. var
  39.   GameGrid: array[0..GRID_SIZE-1, 0..GRID_SIZE-1] of TCell;
  40.   GameState: TGameState;
  41.   SelectedCell: TVector2;
  42.   HasSelected: Boolean;
  43.   Score: Integer;
  44.  
  45. { TRayApplication }
  46. function CheckMatches: Boolean;
  47. var
  48.   i, j, k: Integer;
  49.   MatchesFound: Boolean;
  50. begin
  51.   MatchesFound := False;
  52.  
  53.   // Check horizontal matches
  54.   for j := 0 to GRID_SIZE - 1 do
  55.   begin
  56.     i := 0;
  57.     while i < GRID_SIZE - 2 do
  58.     begin
  59.       if (GameGrid[i, j].State = csNormal) and
  60.          (GameGrid[i+1, j].State = csNormal) and
  61.          (GameGrid[i+2, j].State = csNormal) and
  62.          (GameGrid[i, j].CellType = GameGrid[i+1, j].CellType) and
  63.          (GameGrid[i, j].CellType = GameGrid[i+2, j].CellType) then
  64.       begin
  65.         MatchesFound := True;
  66.         k := i;
  67.         while (k < GRID_SIZE) and (GameGrid[k, j].CellType = GameGrid[i, j].CellType) do
  68.         begin
  69.           GameGrid[k, j].State := csEmpty;
  70.           Inc(k);
  71.         end;
  72.         i := k;
  73.       end
  74.       else
  75.         Inc(i);
  76.     end;
  77.   end;
  78.  
  79.   // Check vertical matches
  80.   for i := 0 to GRID_SIZE - 1 do
  81.   begin
  82.     j := 0;
  83.     while j < GRID_SIZE - 2 do
  84.     begin
  85.       if (GameGrid[i, j].State = csNormal) and
  86.          (GameGrid[i, j+1].State = csNormal) and
  87.          (GameGrid[i, j+2].State = csNormal) and
  88.          (GameGrid[i, j].CellType = GameGrid[i, j+1].CellType) and
  89.          (GameGrid[i, j].CellType = GameGrid[i, j+2].CellType) then
  90.       begin
  91.         MatchesFound := True;
  92.         k := j;
  93.         while (k < GRID_SIZE) and (GameGrid[i, k].CellType = GameGrid[i, j].CellType) do
  94.         begin
  95.           GameGrid[i, k].State := csEmpty;
  96.           Inc(k);
  97.         end;
  98.         j := k;
  99.       end
  100.       else
  101.         Inc(j);
  102.     end;
  103.   end;
  104.  
  105.   Result := MatchesFound;
  106. end;
  107.  
  108. procedure RemoveMatches;
  109. var
  110.   i, j, k: Integer;
  111. begin
  112.   // Move cells down to fill empty spaces
  113.   for i := 0 to GRID_SIZE - 1 do
  114.   begin
  115.     for j := GRID_SIZE - 1 downto 0 do
  116.     begin
  117.       if GameGrid[i, j].State = csEmpty then
  118.       begin
  119.         // Find the next non-empty cell above
  120.         k := j - 1;
  121.         while (k >= 0) and (GameGrid[i, k].State = csEmpty) do
  122.           Dec(k);
  123.  
  124.         if k >= 0 then
  125.         begin
  126.           // Move cell down
  127.           GameGrid[i, j] := GameGrid[i, k];
  128.           GameGrid[i, j].State := csFalling;
  129.           GameGrid[i, j].TargetY := GRID_OFFSET_Y + j * CELL_SIZE;
  130.           GameGrid[i, k].State := csEmpty;
  131.         end
  132.         else
  133.         begin
  134.           // Create new cell at top
  135.           GameGrid[i, j].CellType := TCellType(Random(6));
  136.           GameGrid[i, j].State := csFalling;
  137.           GameGrid[i, j].Position.y := GRID_OFFSET_Y - CELL_SIZE;
  138.           GameGrid[i, j].TargetY := GRID_OFFSET_Y + j * CELL_SIZE;
  139.         end;
  140.       end;
  141.     end;
  142.   end;
  143.  
  144.   GameState := gsAnimating;
  145. end;
  146. procedure InitializeGame;
  147. var
  148.   i, j: Integer;
  149. begin
  150.   Randomize;
  151.   GameState := gsPlaying;
  152.   HasSelected := False;
  153.   Score := 0;
  154.  
  155.   // Initialize grid with random cells
  156.   for i := 0 to GRID_SIZE - 1 do
  157.   begin
  158.     for j := 0 to GRID_SIZE - 1 do
  159.     begin
  160.       GameGrid[i, j].CellType := TCellType(Random(6));
  161.       GameGrid[i, j].State := csNormal;
  162.       GameGrid[i, j].Position.x := GRID_OFFSET_X + i * CELL_SIZE;
  163.       GameGrid[i, j].Position.y := GRID_OFFSET_Y + j * CELL_SIZE;
  164.       GameGrid[i, j].TargetY := GameGrid[i, j].Position.y;
  165.     end;
  166.   end;
  167.  
  168.   // Remove initial matches
  169.   while CheckMatches do
  170.     RemoveMatches;
  171. end;
  172.  
  173. constructor TRayApplication.Create(TheOwner: TComponent);
  174. begin
  175.   inherited Create(TheOwner);
  176.  
  177.   InitWindow((GRID_OFFSET_X*2) + (GRID_SIZE*CELL_SIZE), (GRID_OFFSET_Y*2) + (GRID_SIZE*CELL_SIZE), AppTitle);
  178.   SetTargetFPS(60);
  179.   InitializeGame;
  180. end;
  181.  
  182.  
  183.  
  184. function GetCellColor(CellType: TCellType): TColor;
  185. begin
  186.   case CellType of
  187.     ctRed: Result := RED;
  188.     ctGreen: Result := GREEN;
  189.     ctBlue: Result := BLUE;
  190.     ctYellow: Result := YELLOW;
  191.     ctPurple: Result := PURPLE;
  192.     ctOrange: Result := ORANGE;
  193.   else
  194.     Result := WHITE;
  195.   end;
  196. end;
  197.  
  198.  
  199.  
  200. procedure UpdateFallingCells;
  201. var
  202.   i, j: Integer;
  203.   AllStopped: Boolean;
  204. begin
  205.   AllStopped := True;
  206.  
  207.   for i := 0 to GRID_SIZE - 1 do
  208.   begin
  209.     for j := 0 to GRID_SIZE - 1 do
  210.     begin
  211.       if GameGrid[i, j].State = csFalling then
  212.       begin
  213.         if GameGrid[i, j].Position.y < GameGrid[i, j].TargetY then
  214.         begin
  215.           GameGrid[i, j].Position.y := GameGrid[i, j].Position.y + FALL_SPEED;
  216.           AllStopped := False;
  217.         end
  218.         else
  219.         begin
  220.           GameGrid[i, j].Position.y := GameGrid[i, j].TargetY;
  221.           GameGrid[i, j].State := csNormal;
  222.         end;
  223.       end;
  224.     end;
  225.   end;
  226.  
  227.   if AllStopped then
  228.   begin
  229.     GameState := gsPlaying;
  230.     if CheckMatches then
  231.     begin
  232.       Score := Score + 100;
  233.       RemoveMatches;
  234.     end;
  235.   end;
  236. end;
  237.  
  238. procedure SwapCells(x1, y1, x2, y2: Integer);
  239. var
  240.   TempCell: TCell;
  241. begin
  242.   TempCell := GameGrid[x1, y1];
  243.   GameGrid[x1, y1] := GameGrid[x2, y2];
  244.   GameGrid[x2, y2] := TempCell;
  245.  
  246.   // Update positions
  247.   GameGrid[x1, y1].Position.x := GRID_OFFSET_X + x1 * CELL_SIZE;
  248.   GameGrid[x1, y1].Position.y := GRID_OFFSET_Y + y1 * CELL_SIZE;
  249.   GameGrid[x1, y1].TargetY := GameGrid[x1, y1].Position.y;
  250.  
  251.   GameGrid[x2, y2].Position.x := GRID_OFFSET_X + x2 * CELL_SIZE;
  252.   GameGrid[x2, y2].Position.y := GRID_OFFSET_Y + y2 * CELL_SIZE;
  253.   GameGrid[x2, y2].TargetY := GameGrid[x2, y2].Position.y;
  254. end;
  255.  
  256. function IsValidMove(x1, y1, x2, y2: Integer): Boolean;
  257. var
  258.   TempGrid: array[0..GRID_SIZE-1, 0..GRID_SIZE-1] of TCell;
  259.   i, j: Integer;
  260. begin
  261.   Result := False;
  262.  
  263.   // Only allow adjacent swaps
  264.   if (Abs(x1 - x2) + Abs(y1 - y2)) <> 1 then
  265.     Exit;
  266.  
  267.   // Create temporary grid for simulation
  268.   for i := 0 to GRID_SIZE - 1 do
  269.     for j := 0 to GRID_SIZE - 1 do
  270.       TempGrid[i, j] := GameGrid[i, j];
  271.  
  272.   // Simulate swap
  273.   SwapCells(x1, y1, x2, y2);
  274.  
  275.   // Check if swap creates matches
  276.   Result := CheckMatches;
  277.  
  278.   // Restore original grid
  279.   for i := 0 to GRID_SIZE - 1 do
  280.     for j := 0 to GRID_SIZE - 1 do
  281.       GameGrid[i, j] := TempGrid[i, j];
  282. end;
  283.  
  284. procedure TRayApplication.DoRun;
  285. var
  286.   MousePos: TVector2;
  287.   GridX, GridY: Integer;
  288. begin
  289.   while (not WindowShouldClose) do
  290.   begin
  291.     // Update
  292.     if GameState = gsAnimating then
  293.     begin
  294.       UpdateFallingCells;
  295.     end
  296.     else if GameState = gsPlaying then
  297.     begin
  298.       MousePos := GetMousePosition;
  299.  
  300.       if IsMouseButtonPressed(MOUSE_LEFT_BUTTON) then
  301.       begin
  302.         GridX := Trunc((MousePos.x - GRID_OFFSET_X) / CELL_SIZE);
  303.         GridY := Trunc((MousePos.y - GRID_OFFSET_Y) / CELL_SIZE);
  304.  
  305.         if (GridX >= 0) and (GridX < GRID_SIZE) and
  306.            (GridY >= 0) and (GridY < GRID_SIZE) then
  307.         begin
  308.           if not HasSelected then
  309.           begin
  310.             SelectedCell.x := GridX;
  311.             SelectedCell.y := GridY;
  312.             HasSelected := True;
  313.           end
  314.           else
  315.           begin
  316.             if IsValidMove(Trunc(SelectedCell.x), Trunc(SelectedCell.y), GridX, GridY) then
  317.             begin
  318.               SwapCells(Trunc(SelectedCell.x), Trunc(SelectedCell.y), GridX, GridY);
  319.               RemoveMatches;
  320.             end;
  321.             HasSelected := False;
  322.           end;
  323.         end
  324.         else
  325.         begin
  326.           HasSelected := False;
  327.         end;
  328.       end;
  329.     end;
  330.  
  331.     // Draw
  332.     BeginDrawing();
  333.       ClearBackground(RAYWHITE);
  334.  
  335.       // Draw score
  336.       DrawText(PChar('Score: ' + IntToStr(Score)), GRID_OFFSET_X, 10, 10, DARKGRAY);
  337.  
  338.       // Draw grid background
  339.       DrawRectangle(GRID_OFFSET_X - 5, GRID_OFFSET_Y - 5,
  340.                    GRID_SIZE * CELL_SIZE + 10, GRID_SIZE * CELL_SIZE + 10, LIGHTGRAY);
  341.  
  342.       // Draw cells
  343.       for GridX := 0 to GRID_SIZE - 1 do
  344.       begin
  345.         for GridY := 0 to GRID_SIZE - 1 do
  346.         begin
  347.           if GameGrid[GridX, GridY].State <> csEmpty then
  348.           begin
  349.             // Draw cell
  350.             DrawRectangle(Round(GameGrid[GridX, GridY].Position.x),
  351.                          Round(GameGrid[GridX, GridY].Position.y),
  352.                          CELL_SIZE - 2, CELL_SIZE - 2,
  353.                          GetCellColor(GameGrid[GridX, GridY].CellType));
  354.  
  355.             // Draw selection
  356.             if HasSelected and (GridX = Trunc(SelectedCell.x)) and (GridY = Trunc(SelectedCell.y)) then
  357.             begin
  358.               DrawRectangleLines(Round(GameGrid[GridX, GridY].Position.x),
  359.                                Round(GameGrid[GridX, GridY].Position.y),
  360.                                CELL_SIZE - 2, CELL_SIZE - 2, BLACK);
  361.             end;
  362.           end;
  363.         end;
  364.       end;
  365.  
  366.       // Draw instructions
  367.       DrawText('Click two adjacent cells to swap them', GRID_OFFSET_X, 20, 10, DARKGRAY);
  368.       DrawText('Match 3 or more of the same color to score points', GRID_OFFSET_X, 30, 10, DARKGRAY);
  369.  
  370.     EndDrawing();
  371.   end;
  372.  
  373.   Terminate;
  374. end;
  375.  
  376. destructor TRayApplication.Destroy;
  377. begin
  378.   CloseWindow();
  379.   TraceLog(LOG_INFO, 'Match-3 game closed');
  380.   inherited Destroy;
  381. end;
  382.  
  383. var
  384.   Application: TRayApplication;
  385. begin
  386.   Application := TRayApplication.Create(nil);
  387.   Application.Title := AppTitle;
  388.   Application.Run;
  389.   Application.Free;
  390. end.
  391.  
« Last Edit: November 17, 2025, 02:50:21 pm by Guva »

 

TinyPortal © 2005-2018