unit Minesweeper;

interface

uses
  Classes, SysUtils, Game, Scene, Graphics, Generics.Collections, Controls;

type

  { TSceneMain }

  TSceneMain = class(TScene)
    Game: TGame;
    procedure Draw; override;
  end;

  { TCell }

  TCell = record
    Mined: Boolean;
    Hidden: Boolean;
    Flagged: Boolean;
    procedure Clear;
  end;

  { TBoard }

  TBoard = class
  private
    FSize: TPoint;
    procedure SetSize(AValue: TPoint);
  public
    Cells: array of array of TCell;
    procedure Clear;
    procedure Init;
    procedure ShowAll;
    property Size: TPoint read FSize write SetSize;
  end;

  { TMinesweeper }

  TMinesweeper = class(TGame)
  private
  public
    Winner: string;
    Board: TBoard;
    Score: Integer;
    BoardRect: TRect;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    function GetSurroundingMinesCount(Pos: TPoint): Integer;
    procedure UncoverEmpty(Pos: TPoint; Uncover: Boolean);
    procedure Run; override;
    constructor Create; override;
    destructor Destroy; override;
  end;


implementation

resourcestring
  SMinesweeper = 'Minesweeper';

{ TSceneMain }

procedure TSceneMain.Draw;
var
  ParentGame: TMinesweeper;
  X, Y: Integer;
  CellSize: TPoint;
  CellColor: TColor;
  Symbol: string;
  BoardRect: TRect;
  Surrounding: Integer;
begin
  Clear;
  ParentGame := TMinesweeper(Game);
  with Canvas do begin
    BoardRect := GetBoardRect(ParentGame.Board.Size);
    ParentGame.BoardRect := BoardRect;
    CellSize := Point(BoardRect.Width div ParentGame.Board.Size.X,
      BoardRect.Height div ParentGame.Board.Size.Y);

    Pen.Color := clWhite;
    Brush.Color := clBlack;
    FillRect(BoardRect);

    Brush.Style := bsSolid;

    for Y := 0 to ParentGame.Board.Size.Y - 1 do
      for X := 0 to ParentGame.Board.Size.X - 1 do begin
        CellColor := clWhite;
        if ParentGame.Board.Cells[Y, X].Hidden then begin
          Brush.Color := clGray;
          if ParentGame.Board.Cells[Y, X].Flagged then Symbol := 'F'
          else begin
            Symbol := '';
          end;
        end else begin
          Brush.Color := clBlack;
          if ParentGame.Board.Cells[Y, X].Mined then begin
            Symbol := 'M';
            CellColor := clRed;
          end else begin
            Surrounding := ParentGame.GetSurroundingMinesCount(Point(X, Y));
            if Surrounding > 0 then Symbol := IntToStr(Surrounding)
              else Symbol := '';
          end;
        end;


        Font.Color := CellColor;
        FillRect(BoardRect.Left + X * CellSize.X,
          BoardRect.Top + Y * CellSize.Y,
          BoardRect.Left + (X + 1) * CellSize.X - 1,
          BoardRect.Top + (Y + 1) * CellSize.Y - 1);
        TextOut(BoardRect.Left + X * CellSize.X + CellSize.X div 2 - TextWidth(Symbol) div 2,
          BoardRect.Top + Y * CellSize.Y + CellSize.Y div 2 - TextHeight(Symbol) div 2,
          Symbol);
      end;

    Brush.Style := bsClear;
    TextOut(10, 10, IntToStr(ParentGame.Score));
  end;
end;

{ TCell }

procedure TCell.Clear;
begin
  Mined := False;
  Hidden := True;
  Flagged := False;
end;

{ TBoard }

procedure TBoard.SetSize(AValue: TPoint);
begin
  if FSize = AValue then Exit;
  FSize := AValue;
  SetLength(Cells, AValue.Y, AValue.X);
end;

procedure TBoard.Clear;
var
  X, Y: Integer;
begin
  for Y := 0 to FSize.Y - 1 do
    for X := 0 to FSize.X - 1 do
      Cells[Y, X].Clear;
end;

procedure TBoard.Init;
const
  MinesRatio = 0.1;
var
  MinesCount: Integer;
  I: Integer;
begin
  Clear;
  MinesCount := Round(Size.X * Size.Y * MinesRatio);
  for I := 0 to MinesCount - 1 do
    Cells[Random(Size.Y), Random(Size.X)].Mined := True;
end;

procedure TBoard.ShowAll;
var
  X, Y: Integer;
begin
  for Y := 0 to FSize.Y - 1 do
    for X := 0 to FSize.X - 1 do
      Cells[Y, X].Hidden := False;
end;

{ TMinesweeper }

procedure TMinesweeper.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
var
  CellSize: TPoint;
  Pos: TPoint;
  Surrounding: Integer;
begin
  CellSize := Point(BoardRect.Width div Board.Size.X,
    BoardRect.Height div Board.Size.Y);

  if BoardRect.Contains(Point(X, Y)) then begin
    Pos := Point((X - BoardRect.Left) div CellSize.X,
      (Y - BoardRect.Top) div CellSize.Y);

    if Button = mbLeft then begin
      if Board.Cells[Pos.Y, Pos.X].Hidden and not Board.Cells[Pos.Y, Pos.X].Flagged then begin
        Board.Cells[Pos.Y, Pos.X].Hidden := False;
        if Board.Cells[Pos.Y, Pos.X].Mined then begin
          State := gsStopped;
          Board.ShowAll;
        end else begin
          Inc(Score);
          Surrounding := GetSurroundingMinesCount(Pos);
          if Surrounding = 0 then begin
            Board.Cells[Pos.Y, Pos.X].Hidden := True;
            UncoverEmpty(Pos, True);
          end;
        end;
        Scene.Redraw;
      end;
    end else
    if Button = mbRight then begin
      if Board.Cells[Pos.Y, Pos.X].Hidden then begin
        Board.Cells[Pos.Y, Pos.X].Flagged := not Board.Cells[Pos.Y, Pos.X].Flagged;
        Scene.Redraw;
      end;
    end;
  end;
end;

function TMinesweeper.GetSurroundingMinesCount(Pos: TPoint): Integer;
var
  X, Y: Integer;
begin
  X := Pos.X;
  Y := Pos.Y;
  Result := 0;
  if (Y - 1) >= 0 then begin
    if (X - 1) >= 0 then
      if Board.Cells[Y - 1, X - 1].Mined then Inc(Result);
    if Board.Cells[Y - 1, X].Mined then Inc(Result);
    if (X + 1) < Board.Size.X then
      if Board.Cells[Y - 1, X + 1].Mined then Inc(Result);
  end;
  if (X - 1) >= 0 then
    if Board.Cells[Y, X - 1].Mined then Inc(Result);
  if (X + 1) < Board.Size.X then
    if Board.Cells[Y, X + 1].Mined then Inc(Result);
  if (Y + 1) < Board.Size.Y then begin
    if (X - 1) >= 0 then
      if Board.Cells[Y + 1, X - 1].Mined then Inc(Result);
    if Board.Cells[Y + 1, X].Mined then Inc(Result);
    if (X + 1) < Board.Size.X then
      if Board.Cells[Y + 1, X + 1].Mined then Inc(Result);
  end;
end;

procedure TMinesweeper.UncoverEmpty(Pos: TPoint; Uncover: Boolean);
var
  Surroundings: Integer;
begin
  if (Pos.X < 0) or (Pos.Y < 0) or (Pos.X >= Board.Size.X) or
    (Pos.Y >= Board.Size.Y) then Exit;

  if (not Board.Cells[Pos.Y, Pos.X].Mined) then begin
    Surroundings := GetSurroundingMinesCount(Pos);
    if (Surroundings = 0) and Board.Cells[Pos.Y, Pos.X].Hidden then begin
      Board.Cells[Pos.Y, Pos.X].Hidden := False;

      UncoverEmpty(Point(Pos.X - 1, Pos.Y - 1), Surroundings = 0);
      UncoverEmpty(Point(Pos.X, Pos.Y - 1), Surroundings = 0);
      UncoverEmpty(Point(Pos.X + 1, Pos.Y - 1), Surroundings = 0);
      UncoverEmpty(Point(Pos.X - 1, Pos.Y), Surroundings = 0);
      UncoverEmpty(Point(Pos.X + 1, Pos.Y), Surroundings = 0);
      UncoverEmpty(Point(Pos.X - 1, Pos.Y + 1), Surroundings = 0);
      UncoverEmpty(Point(Pos.X, Pos.Y + 1), Surroundings = 0);
      UncoverEmpty(Point(Pos.X + 1, Pos.Y + 1), Surroundings = 0);
    end;
  end;
  if Uncover then Board.Cells[Pos.Y, Pos.X].Hidden := False;
end;

procedure TMinesweeper.Run;
begin
  Winner := '';
  Board.Init;
  Score := 0;
  inherited;
end;

constructor TMinesweeper.Create;
begin
  Name := SMinesweeper;
  Scene := TSceneMain.Create;
  TSceneMain(Scene).Game := Self;
  Board := TBoard.Create;
  Board.Size := Point(20, 10);
end;

destructor TMinesweeper.Destroy;
begin
  FreeAndNil(Board);
  FreeAndNil(Scene);
  inherited;
end;

end.

