program project1;
{$mode objfpc}{$H+}
uses
{$IFDEF LINUX} cthreads,{$ENDIF}
Classes, SysUtils, CustApp, raylib, Math;
type
{ TRayApplication }
TRayApplication = class(TCustomApplication)
protected
procedure DoRun; override;
public
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
end;
TGameState = (gsPlaying, gsAnimating, gsGameOver);
TCellState = (csEmpty, csFalling, csNormal);
TCellType = (ctRed, ctGreen, ctBlue, ctYellow, ctPurple, ctOrange);
TCell = record
CellType: TCellType;
State: TCellState;
Position: TVector2;
TargetY: Single;
end;
const
AppTitle = 'raylib - Match-3 Game';
GRID_SIZE = 8;
CELL_SIZE = 60;
GRID_OFFSET_X = 50;
GRID_OFFSET_Y = 50;
FALL_SPEED = 5.0;
var
GameGrid: array[0..GRID_SIZE-1, 0..GRID_SIZE-1] of TCell;
GameState: TGameState;
SelectedCell: TVector2;
HasSelected: Boolean;
Score: Integer;
{ TRayApplication }
function CheckMatches: Boolean;
var
i, j, k: Integer;
MatchesFound: Boolean;
begin
MatchesFound := False;
// Check horizontal matches
for j := 0 to GRID_SIZE - 1 do
begin
i := 0;
while i < GRID_SIZE - 2 do
begin
if (GameGrid[i, j].State = csNormal) and
(GameGrid[i+1, j].State = csNormal) and
(GameGrid[i+2, j].State = csNormal) and
(GameGrid[i, j].CellType = GameGrid[i+1, j].CellType) and
(GameGrid[i, j].CellType = GameGrid[i+2, j].CellType) then
begin
MatchesFound := True;
k := i;
while (k < GRID_SIZE) and (GameGrid[k, j].CellType = GameGrid[i, j].CellType) do
begin
GameGrid[k, j].State := csEmpty;
Inc(k);
end;
i := k;
end
else
Inc(i);
end;
end;
// Check vertical matches
for i := 0 to GRID_SIZE - 1 do
begin
j := 0;
while j < GRID_SIZE - 2 do
begin
if (GameGrid[i, j].State = csNormal) and
(GameGrid[i, j+1].State = csNormal) and
(GameGrid[i, j+2].State = csNormal) and
(GameGrid[i, j].CellType = GameGrid[i, j+1].CellType) and
(GameGrid[i, j].CellType = GameGrid[i, j+2].CellType) then
begin
MatchesFound := True;
k := j;
while (k < GRID_SIZE) and (GameGrid[i, k].CellType = GameGrid[i, j].CellType) do
begin
GameGrid[i, k].State := csEmpty;
Inc(k);
end;
j := k;
end
else
Inc(j);
end;
end;
Result := MatchesFound;
end;
procedure RemoveMatches;
var
i, j, k: Integer;
begin
// Move cells down to fill empty spaces
for i := 0 to GRID_SIZE - 1 do
begin
for j := GRID_SIZE - 1 downto 0 do
begin
if GameGrid[i, j].State = csEmpty then
begin
// Find the next non-empty cell above
k := j - 1;
while (k >= 0) and (GameGrid[i, k].State = csEmpty) do
Dec(k);
if k >= 0 then
begin
// Move cell down
GameGrid[i, j] := GameGrid[i, k];
GameGrid[i, j].State := csFalling;
GameGrid[i, j].TargetY := GRID_OFFSET_Y + j * CELL_SIZE;
GameGrid[i, k].State := csEmpty;
end
else
begin
// Create new cell at top
GameGrid[i, j].CellType := TCellType(Random(6));
GameGrid[i, j].State := csFalling;
GameGrid[i, j].Position.y := GRID_OFFSET_Y - CELL_SIZE;
GameGrid[i, j].TargetY := GRID_OFFSET_Y + j * CELL_SIZE;
end;
end;
end;
end;
GameState := gsAnimating;
end;
procedure InitializeGame;
var
i, j: Integer;
begin
Randomize;
GameState := gsPlaying;
HasSelected := False;
Score := 0;
// Initialize grid with random cells
for i := 0 to GRID_SIZE - 1 do
begin
for j := 0 to GRID_SIZE - 1 do
begin
GameGrid[i, j].CellType := TCellType(Random(6));
GameGrid[i, j].State := csNormal;
GameGrid[i, j].Position.x := GRID_OFFSET_X + i * CELL_SIZE;
GameGrid[i, j].Position.y := GRID_OFFSET_Y + j * CELL_SIZE;
GameGrid[i, j].TargetY := GameGrid[i, j].Position.y;
end;
end;
// Remove initial matches
while CheckMatches do
RemoveMatches;
end;
constructor TRayApplication.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
InitWindow((GRID_OFFSET_X*2) + (GRID_SIZE*CELL_SIZE), (GRID_OFFSET_Y*2) + (GRID_SIZE*CELL_SIZE), AppTitle);
SetTargetFPS(60);
InitializeGame;
end;
function GetCellColor(CellType: TCellType): TColor;
begin
case CellType of
ctRed: Result := RED;
ctGreen: Result := GREEN;
ctBlue: Result := BLUE;
ctYellow: Result := YELLOW;
ctPurple: Result := PURPLE;
ctOrange: Result := ORANGE;
else
Result := WHITE;
end;
end;
procedure UpdateFallingCells;
var
i, j: Integer;
AllStopped: Boolean;
begin
AllStopped := True;
for i := 0 to GRID_SIZE - 1 do
begin
for j := 0 to GRID_SIZE - 1 do
begin
if GameGrid[i, j].State = csFalling then
begin
if GameGrid[i, j].Position.y < GameGrid[i, j].TargetY then
begin
GameGrid[i, j].Position.y := GameGrid[i, j].Position.y + FALL_SPEED;
AllStopped := False;
end
else
begin
GameGrid[i, j].Position.y := GameGrid[i, j].TargetY;
GameGrid[i, j].State := csNormal;
end;
end;
end;
end;
if AllStopped then
begin
GameState := gsPlaying;
if CheckMatches then
begin
Score := Score + 100;
RemoveMatches;
end;
end;
end;
procedure SwapCells(x1, y1, x2, y2: Integer);
var
TempCell: TCell;
begin
TempCell := GameGrid[x1, y1];
GameGrid[x1, y1] := GameGrid[x2, y2];
GameGrid[x2, y2] := TempCell;
// Update positions
GameGrid[x1, y1].Position.x := GRID_OFFSET_X + x1 * CELL_SIZE;
GameGrid[x1, y1].Position.y := GRID_OFFSET_Y + y1 * CELL_SIZE;
GameGrid[x1, y1].TargetY := GameGrid[x1, y1].Position.y;
GameGrid[x2, y2].Position.x := GRID_OFFSET_X + x2 * CELL_SIZE;
GameGrid[x2, y2].Position.y := GRID_OFFSET_Y + y2 * CELL_SIZE;
GameGrid[x2, y2].TargetY := GameGrid[x2, y2].Position.y;
end;
function IsValidMove(x1, y1, x2, y2: Integer): Boolean;
var
TempGrid: array[0..GRID_SIZE-1, 0..GRID_SIZE-1] of TCell;
i, j: Integer;
begin
Result := False;
// Only allow adjacent swaps
if (Abs(x1 - x2) + Abs(y1 - y2)) <> 1 then
Exit;
// Create temporary grid for simulation
for i := 0 to GRID_SIZE - 1 do
for j := 0 to GRID_SIZE - 1 do
TempGrid[i, j] := GameGrid[i, j];
// Simulate swap
SwapCells(x1, y1, x2, y2);
// Check if swap creates matches
Result := CheckMatches;
// Restore original grid
for i := 0 to GRID_SIZE - 1 do
for j := 0 to GRID_SIZE - 1 do
GameGrid[i, j] := TempGrid[i, j];
end;
procedure TRayApplication.DoRun;
var
MousePos: TVector2;
GridX, GridY: Integer;
begin
while (not WindowShouldClose) do
begin
// Update
if GameState = gsAnimating then
begin
UpdateFallingCells;
end
else if GameState = gsPlaying then
begin
MousePos := GetMousePosition;
if IsMouseButtonPressed(MOUSE_LEFT_BUTTON) then
begin
GridX := Trunc((MousePos.x - GRID_OFFSET_X) / CELL_SIZE);
GridY := Trunc((MousePos.y - GRID_OFFSET_Y) / CELL_SIZE);
if (GridX >= 0) and (GridX < GRID_SIZE) and
(GridY >= 0) and (GridY < GRID_SIZE) then
begin
if not HasSelected then
begin
SelectedCell.x := GridX;
SelectedCell.y := GridY;
HasSelected := True;
end
else
begin
if IsValidMove(Trunc(SelectedCell.x), Trunc(SelectedCell.y), GridX, GridY) then
begin
SwapCells(Trunc(SelectedCell.x), Trunc(SelectedCell.y), GridX, GridY);
RemoveMatches;
end;
HasSelected := False;
end;
end
else
begin
HasSelected := False;
end;
end;
end;
// Draw
BeginDrawing();
ClearBackground(RAYWHITE);
// Draw score
DrawText(PChar('Score: ' + IntToStr(Score)), GRID_OFFSET_X, 10, 10, DARKGRAY);
// Draw grid background
DrawRectangle(GRID_OFFSET_X - 5, GRID_OFFSET_Y - 5,
GRID_SIZE * CELL_SIZE + 10, GRID_SIZE * CELL_SIZE + 10, LIGHTGRAY);
// Draw cells
for GridX := 0 to GRID_SIZE - 1 do
begin
for GridY := 0 to GRID_SIZE - 1 do
begin
if GameGrid[GridX, GridY].State <> csEmpty then
begin
// Draw cell
DrawRectangle(Round(GameGrid[GridX, GridY].Position.x),
Round(GameGrid[GridX, GridY].Position.y),
CELL_SIZE - 2, CELL_SIZE - 2,
GetCellColor(GameGrid[GridX, GridY].CellType));
// Draw selection
if HasSelected and (GridX = Trunc(SelectedCell.x)) and (GridY = Trunc(SelectedCell.y)) then
begin
DrawRectangleLines(Round(GameGrid[GridX, GridY].Position.x),
Round(GameGrid[GridX, GridY].Position.y),
CELL_SIZE - 2, CELL_SIZE - 2, BLACK);
end;
end;
end;
end;
// Draw instructions
DrawText('Click two adjacent cells to swap them', GRID_OFFSET_X, 20, 10, DARKGRAY);
DrawText('Match 3 or more of the same color to score points', GRID_OFFSET_X, 30, 10, DARKGRAY);
EndDrawing();
end;
Terminate;
end;
destructor TRayApplication.Destroy;
begin
CloseWindow();
TraceLog(LOG_INFO, 'Match-3 game closed');
inherited Destroy;
end;
var
Application: TRayApplication;
begin
Application := TRayApplication.Create(nil);
Application.Title := AppTitle;
Application.Run;
Application.Free;
end.