Recent

Author Topic: SameGame  (Read 269 times)

Guva

  • Full Member
  • ***
  • Posts: 201
  • 🌈 ZX-Spectrum !!!
SameGame
« on: November 16, 2025, 06:19:18 pm »
A small example of an old classic game.

Code: Pascal  [Select][+][-]
  1. program samegame;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6. {$IFDEF LINUX}
  7.   cthreads,
  8. {$ENDIF}
  9.   Classes, SysUtils, CustApp, raylib, math;
  10.  
  11. type
  12.   { TRayApplication }
  13.   TRayApplication = class(TCustomApplication)
  14.   protected
  15.     procedure DoRun; override;
  16.   public
  17.     constructor Create(TheOwner: TComponent); override;
  18.     destructor Destroy; override;
  19.   end;
  20.  
  21. const
  22.   AppTitle = 'SameGame';
  23.   BOARD_WIDTH = 15;
  24.   BOARD_HEIGHT = 12;
  25.   CELL_SIZE = 40;
  26.   MARGIN_X = 50;
  27.   MARGIN_Y = 80;
  28.  
  29. type
  30.   TCellState = (csEmpty, csRed, csGreen, csBlue, csYellow, csPurple);
  31.   TGameState = (gsPlaying, gsGameOver, gsVictory);
  32.  
  33. var
  34.   Board: array[0..BOARD_WIDTH-1, 0..BOARD_HEIGHT-1] of TCellState;
  35.   Score: Integer;
  36.   GameState: TGameState;
  37.  
  38. { TRayApplication }
  39. procedure InitializeGame;
  40. var
  41.   x, y: Integer;
  42. begin
  43.   Randomize;
  44.   Score := 0;
  45.   GameState := gsPlaying;
  46.  
  47.   // Заполняем поле случайными цветами
  48.   for x := 0 to BOARD_WIDTH - 1 do
  49.     for y := 0 to BOARD_HEIGHT - 1 do
  50.       Board[x, y] := TCellState(Random(5) + 1); // 1-5 цвета, 0 - пусто
  51. end;
  52.  
  53. constructor TRayApplication.Create(TheOwner: TComponent);
  54. begin
  55.   inherited Create(TheOwner);
  56.     InitWindow( (BOARD_WIDTH * CELL_SIZE) + (MARGIN_X * 2), (BOARD_HEIGHT * CELL_SIZE)+ (MARGIN_Y * 2) , AppTitle);
  57.   SetTargetFPS(60);
  58.   InitializeGame;
  59. end;
  60.  
  61. function GetCellColor(State: TCellState): TColor;
  62. begin
  63.   case State of
  64.     csEmpty: Result := RAYWHITE;
  65.     csRed: Result := MAROON;
  66.     csGreen: Result := DARKGREEN;
  67.     csBlue: Result := DARKBLUE;
  68.     csYellow: Result := GOLD;
  69.     csPurple: Result := PURPLE;
  70.   else
  71.     Result := LIGHTGRAY;
  72.   end;
  73. end;
  74.  
  75. function HasValidMoves: Boolean;
  76. var
  77.   x, y: Integer;
  78.   currentColor: TCellState;
  79. begin
  80.   Result := False;
  81.  
  82.   for x := 0 to BOARD_WIDTH - 1 do
  83.   begin
  84.     for y := 0 to BOARD_HEIGHT - 1 do
  85.     begin
  86.       currentColor := Board[x, y];
  87.       if currentColor = csEmpty then
  88.         Continue;
  89.  
  90.       // Проверяем соседей справа и снизу
  91.       if (x < BOARD_WIDTH - 1) and (Board[x + 1, y] = currentColor) then
  92.       begin
  93.         Result := True;
  94.         Exit;
  95.       end;
  96.  
  97.       if (y < BOARD_HEIGHT - 1) and (Board[x, y + 1] = currentColor) then
  98.       begin
  99.         Result := True;
  100.         Exit;
  101.       end;
  102.     end;
  103.   end;
  104. end;
  105.  
  106. procedure FindConnected(x, y: Integer; Color: TCellState;
  107.   var Visited: array of Boolean; var Connected: array of Boolean);
  108. var
  109.   directions: array[0..3, 0..1] of Integer;
  110.   i, nx, ny: Integer;
  111. begin
  112.   if (x < 0) or (x >= BOARD_WIDTH) or (y < 0) or (y >= BOARD_HEIGHT) then
  113.     Exit;
  114.  
  115.   if Visited[y * BOARD_WIDTH + x] or (Board[x, y] <> Color) then
  116.     Exit;
  117.  
  118.   Visited[y * BOARD_WIDTH + x] := True;
  119.   Connected[y * BOARD_WIDTH + x] := True;
  120.  
  121.   directions[0, 0] := 1;  directions[0, 1] := 0;   // right
  122.   directions[1, 0] := -1; directions[1, 1] := 0;   // left
  123.   directions[2, 0] := 0;  directions[2, 1] := 1;   // down
  124.   directions[3, 0] := 0;  directions[3, 1] := -1;  // up
  125.  
  126.   for i := 0 to 3 do
  127.   begin
  128.     nx := x + directions[i, 0];
  129.     ny := y + directions[i, 1];
  130.     FindConnected(nx, ny, Color, Visited, Connected);
  131.   end;
  132. end;
  133.  
  134. function GetConnectedCount(x, y: Integer; Color: TCellState): Integer;
  135. var
  136.   Visited: array of Boolean;
  137.   Connected: array of Boolean;
  138.   i: Integer;
  139. begin
  140.   Result := 0;
  141.   if Color = csEmpty then Exit;
  142.  
  143.   SetLength(Visited, BOARD_WIDTH * BOARD_HEIGHT);
  144.   SetLength(Connected, BOARD_WIDTH * BOARD_HEIGHT);
  145.  
  146.   for i := 0 to Length(Visited) - 1 do
  147.   begin
  148.     Visited[i] := False;
  149.     Connected[i] := False;
  150.   end;
  151.  
  152.   FindConnected(x, y, Color, Visited, Connected);
  153.  
  154.   for i := 0 to Length(Connected) - 1 do
  155.     if Connected[i] then
  156.       Inc(Result);
  157. end;
  158.  
  159. procedure RemoveConnectedGroup(x, y: Integer; Color: TCellState);
  160. var
  161.   Visited: array of Boolean;
  162.   Connected: array of Boolean;
  163.   i, cx, cy: Integer;
  164. begin
  165.   if Color = csEmpty then Exit;
  166.  
  167.   SetLength(Visited, BOARD_WIDTH * BOARD_HEIGHT);
  168.   SetLength(Connected, BOARD_WIDTH * BOARD_HEIGHT);
  169.  
  170.   for i := 0 to Length(Visited) - 1 do
  171.   begin
  172.     Visited[i] := False;
  173.     Connected[i] := False;
  174.   end;
  175.  
  176.   FindConnected(x, y, Color, Visited, Connected);
  177.  
  178.   // Удаляем все подключенные блоки
  179.   for i := 0 to Length(Connected) - 1 do
  180.   begin
  181.     if Connected[i] then
  182.     begin
  183.       cx := i mod BOARD_WIDTH;
  184.       cy := i div BOARD_WIDTH;
  185.       Board[cx, cy] := csEmpty;
  186.     end;
  187.   end;
  188. end;
  189.  
  190. procedure ApplyGravity;
  191. var
  192.   x, y, emptyCount: Integer;
  193. begin
  194.   // Гравитация в столбцах - блоки падают вниз
  195.   for x := 0 to BOARD_WIDTH - 1 do
  196.   begin
  197.     emptyCount := 0;
  198.     // Проходим столбец снизу вверх
  199.     for y := BOARD_HEIGHT - 1 downto 0 do
  200.     begin
  201.       if Board[x, y] = csEmpty then
  202.         Inc(emptyCount)
  203.       else if emptyCount > 0 then
  204.       begin
  205.         // Перемещаем блок вниз
  206.         Board[x, y + emptyCount] := Board[x, y];
  207.         Board[x, y] := csEmpty;
  208.       end;
  209.     end;
  210.   end;
  211. end;
  212.  
  213. procedure RemoveEmptyColumns;
  214. var
  215.   x, shift, y: Integer;
  216.   columnEmpty: Boolean;
  217.   tempBoard: array[0..BOARD_WIDTH-1, 0..BOARD_HEIGHT-1] of TCellState;
  218. begin
  219.   // Создаем временную копию доски
  220.   for x := 0 to BOARD_WIDTH - 1 do
  221.     for y := 0 to BOARD_HEIGHT - 1 do
  222.       tempBoard[x, y] := Board[x, y];
  223.  
  224.   shift := 0;
  225.  
  226.   // Переносим непустые столбцы влево
  227.   for x := 0 to BOARD_WIDTH - 1 do
  228.   begin
  229.     columnEmpty := True;
  230.     for y := 0 to BOARD_HEIGHT - 1 do
  231.     begin
  232.       if tempBoard[x, y] <> csEmpty then
  233.       begin
  234.         columnEmpty := False;
  235.         Break;
  236.       end;
  237.     end;
  238.  
  239.     if not columnEmpty then
  240.     begin
  241.       // Копируем непустой столбец на новую позицию
  242.       for y := 0 to BOARD_HEIGHT - 1 do
  243.         Board[shift, y] := tempBoard[x, y];
  244.       Inc(shift);
  245.     end;
  246.   end;
  247.  
  248.   // Заполняем оставшиеся столбцы пустыми клетками
  249.   for x := shift to BOARD_WIDTH - 1 do
  250.     for y := 0 to BOARD_HEIGHT - 1 do
  251.       Board[x, y] := csEmpty;
  252. end;
  253.  
  254. function IsBoardEmpty: Boolean;
  255. var
  256.   x, y: Integer;
  257. begin
  258.   Result := True;
  259.   for x := 0 to BOARD_WIDTH - 1 do
  260.     for y := 0 to BOARD_HEIGHT - 1 do
  261.       if Board[x, y] <> csEmpty then
  262.       begin
  263.         Result := False;
  264.         Exit;
  265.       end;
  266. end;
  267.  
  268. procedure HandleMouseClick;
  269. var
  270.   MousePos: TVector2;
  271.   x, y: Integer;
  272.   groupSize: Integer;
  273. begin
  274.   if GameState <> gsPlaying then
  275.     Exit;
  276.  
  277.   MousePos := GetMousePosition();
  278.  
  279.   // Преобразуем координаты мыши в координаты доски
  280.   x := Trunc((MousePos.x - MARGIN_X) / CELL_SIZE);
  281.   y := Trunc((MousePos.y - MARGIN_Y) / CELL_SIZE);
  282.  
  283.   if (x < 0) or (x >= BOARD_WIDTH) or (y < 0) or (y >= BOARD_HEIGHT) then
  284.     Exit;
  285.  
  286.   if Board[x, y] = csEmpty then
  287.     Exit;
  288.  
  289.   // Проверяем размер группы
  290.   groupSize := GetConnectedCount(x, y, Board[x, y]);
  291.  
  292.   if groupSize < 2 then
  293.     Exit;
  294.  
  295.   // Удаляем группу и начисляем очки
  296.   RemoveConnectedGroup(x, y, Board[x, y]);
  297.   Score := Score + (groupSize - 1) * (groupSize - 1);
  298.  
  299.   // Применяем гравитацию и удаляем пустые столбцы
  300.   ApplyGravity;
  301.   RemoveEmptyColumns;
  302.  
  303.   // Проверяем условия окончания игры
  304.   if IsBoardEmpty then
  305.     GameState := gsVictory
  306.   else if not HasValidMoves then
  307.     GameState := gsGameOver;
  308. end;
  309.  
  310. procedure TRayApplication.DoRun;
  311. var
  312.   x, y: Integer;
  313.   cellColor: TColor;
  314.   rect: TRectangle;
  315.   groupSize, TextWidth: Integer;
  316.   MousePos: TVector2;
  317.   InstructionText, GameTitle, GameReset: string;
  318.   popupRect: TRectangle;
  319. begin
  320.   while (not WindowShouldClose) do
  321.   begin
  322.     // Обработка ввода
  323.     if IsMouseButtonPressed(MOUSE_BUTTON_LEFT) then
  324.       HandleMouseClick;
  325.  
  326.     if IsKeyPressed(KEY_R) then
  327.       InitializeGame;
  328.  
  329.     // Отрисовка
  330.     BeginDrawing();
  331.       ClearBackground(RAYWHITE);
  332.  
  333.       // Отрисовка заголовка по центру
  334.       GameTitle := 'SameGame';
  335.       TextWidth := MeasureText(PChar(GameTitle), 30);
  336.       DrawText(PChar(GameTitle), (GetScreenWidth() - TextWidth) div 2, 20, 30, GRAY);
  337.  
  338.       // Отрисовка счета и кнопки новой игры
  339.       DrawText(PChar('Score: ' + IntToStr(Score)), MARGIN_X, 20, 20, GRAY);
  340.  
  341.       GameReset := 'R - GameReset';
  342.       TextWidth := MeasureText(PChar(GameReset), 20);
  343.       DrawText(PChar(GameReset), GetScreenWidth() - TextWidth - MARGIN_X, 20, 20, GRAY);
  344.  
  345.       // Отрисовка игрового поля
  346.       for x := 0 to BOARD_WIDTH - 1 do
  347.       begin
  348.         for y := 0 to BOARD_HEIGHT - 1 do
  349.         begin
  350.           cellColor := GetCellColor(Board[x, y]);
  351.  
  352.           rect.x := MARGIN_X + x * CELL_SIZE;
  353.           rect.y := MARGIN_Y + y * CELL_SIZE;
  354.           rect.width := CELL_SIZE - 2;
  355.           rect.height := CELL_SIZE - 2;
  356.  
  357.           DrawRectangleRec(rect, cellColor);
  358.           DrawRectangleLinesEx(rect, 1, LIGHTGRAY);
  359.  
  360.           // Показываем размер группы при наведении
  361.           if Board[x, y] <> csEmpty then
  362.           begin
  363.             MousePos := GetMousePosition();
  364.             if (MousePos.x >= rect.x) and (MousePos.x <= rect.x + rect.width) and
  365.                (MousePos.y >= rect.y) and (MousePos.y <= rect.y + rect.height) then
  366.             begin
  367.               groupSize := GetConnectedCount(x, y, Board[x, y]);
  368.               if groupSize >= 2 then
  369.               begin
  370.                 DrawRectangleLinesEx(rect, 2 , BLACK);
  371.                 DrawText(PChar(IntToStr(groupSize)),
  372.                   Round(rect.x + rect.width / 2 - 4),
  373.                   Round(rect.y + rect.height / 2 - 5),
  374.                   10, BLACK);
  375.               end;
  376.             end;
  377.           end;
  378.         end;
  379.       end;
  380.  
  381.       // Отрисовка состояния игры по центру
  382.       case GameState of
  383.         gsGameOver:
  384.           begin
  385.             // Центрируем прямоугольник
  386.             popupRect.width := 400;
  387.             popupRect.height := 150;
  388.             popupRect.x := (GetScreenWidth() - popupRect.width) /2 ;
  389.             popupRect.y := 250;
  390.  
  391.             DrawRectangleRec(popupRect, Fade(DARKGRAY, 0.8));
  392.  
  393.             TextWidth := MeasureText('GAME OVER!', 30);
  394.             DrawText('GAME OVER!', (GetScreenWidth() - TextWidth) div 2, 270, 30, MAROON);
  395.  
  396.             TextWidth := MeasureText('No valid moves available', 20);
  397.             DrawText('No valid moves available', (GetScreenWidth() - TextWidth) div 2, 310, 20, RAYWHITE);
  398.  
  399.             TextWidth := MeasureText('Press R - for new game', 20);
  400.             DrawText('Press R - for new game', (GetScreenWidth() - TextWidth) div 2, 340, 20, RAYWHITE);
  401.           end;
  402.  
  403.         gsVictory:
  404.           begin
  405.             // Центрируем прямоугольник
  406.             popupRect.width := 400;
  407.             popupRect.height := 150;
  408.             popupRect.x := (GetScreenWidth() - popupRect.width) / 2;
  409.             popupRect.y := 250;
  410.  
  411.             DrawRectangleRec(popupRect, Fade(DARKGRAY, 0.8));
  412.  
  413.             TextWidth := MeasureText('Victory!', 30);
  414.             DrawText('Victory!', (GetScreenWidth() - TextWidth) div 2, 270, 30, DARKGREEN);
  415.  
  416.             TextWidth := MeasureText(PChar('Score: ' + IntToStr(Score)), 20);
  417.             DrawText(PChar('Score: ' + IntToStr(Score)), (GetScreenWidth() - TextWidth) div 2, 310, 20, RAYWHITE);
  418.  
  419.             TextWidth := MeasureText('Press R - for new game', 20);
  420.             DrawText('Press R - for new game', (GetScreenWidth() - TextWidth) div 2, 340, 20, RAYWHITE);
  421.           end;
  422.       end;
  423.  
  424.       // Инструкция по центру
  425.       InstructionText := 'Click groups of 2 or more same-colored blocks';
  426.       TextWidth := MeasureText(PChar(InstructionText), 20);
  427.       DrawText(PChar(InstructionText),
  428.         (GetScreenWidth() - TextWidth) div 2,
  429.         MARGIN_Y + BOARD_HEIGHT * CELL_SIZE + 20,
  430.         20, GRAY);
  431.  
  432.       EndDrawing();
  433.   end;
  434.   Terminate;
  435. end;
  436.  
  437. destructor TRayApplication.Destroy;
  438. begin
  439.   CloseWindow();
  440.   inherited Destroy;
  441. end;
  442.  
  443. var
  444.   Application: TRayApplication;
  445. begin
  446.   Application := TRayApplication.Create(nil);
  447.   Application.Title := AppTitle;
  448.   Application.Run;
  449.   Application.Free;
  450. end.    
  451.  

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12571
  • FPC developer.
Re: SameGame
« Reply #1 on: November 16, 2025, 09:55:57 pm »
(A console or graph unit version of this game comes with FPC)

 

TinyPortal © 2005-2018