program Mine;
uses
  SysRandom,
  Termio;

type
  TCell = (Empty, Bomb);
  TState = (Closed, Open, Flagged);
  TField = record
    Cells: array of TCell;
    States: array of TState;
    Rows: Integer;
    Cols: Integer;
    CursorRow: Integer;
    CursorCol: Integer;
  end;

function FieldGet(Field: TField; Row, Col: Integer): TCell;
begin
  FieldGet := Field.Cells[Row * Field.Cols + Col];
end;

function FieldGetState(Field: TField; Row, Col: Integer): TState;
begin
  FieldGetState := Field.States[Row * Field.Cols + Col];
end;

function FieldOpenAtCursor(var Field: TField): TCell;
var
  Index: Integer;
begin
  Index := Field.CursorRow * Field.Cols + Field.CursorCol;
  Field.States[Index] := Open;
  FieldOpenAtCursor := Field.Cells[Index];
end;

procedure FieldFlagAtCursor(var Field: TField);
var
  Index: Integer;
begin
  Index := Field.CursorRow * Field.Cols + Field.CursorCol;
  case Field.States[Index] of
    Closed: Field.States[Index] := Flagged;
    Flagged: Field.States[Index] := Closed;
  end;
end;

procedure FieldOpenBombs(var Field: TField);
var
  Index: Integer;
begin
  for Index := 0 to Field.Rows * Field.Cols do
    if Field.Cells[Index] = Bomb then
      Field.States[Index] := Open;
end;

function FieldGetChecked(Field: TField; Row, Col: Integer; var Cell: TCell): Boolean;
begin
  FieldGetChecked := (0 <= Row) and (Row < Field.Rows) and (0 <= Col) and (Col < Field.Cols);
  if FieldGetChecked then Cell := FieldGet(Field, Row, Col);
end;

procedure FieldSet(var Field: TField; Row, Col: Integer; Cell: TCell);
begin
  Field.Cells[Row * Field.Cols + Col] := Cell;
end;

procedure FieldResize(var Field: TField; Rows, Cols: Integer);
var
  Index: Integer;
begin
  SetLength(Field.Cells, Rows * Cols);
  SetLength(Field.States, Rows * Cols);
  Field.Rows := Rows;
  Field.Cols := Cols;
  Field.CursorRow := 0;
  Field.CursorCol := 0;
  for Index := 0 to Rows * Cols do Field.States[Index] := Closed;
end;

function FieldRandomCell(Field: TField; var Row, Col: Integer): TCell;
begin
  Row := Random(Field.Rows);
  Col := Random(Field.Cols);
  FieldRandomCell := FieldGet(Field, Row, Col);
end;

function FieldAtCursor(Field: TField; Row, Col: Integer): Boolean;
begin
  FieldAtCursor := (Field.CursorRow = Row) and (Field.CursorCol = Col);
end;

function FieldAroundCursor(Field: TField; Row, Col: Integer): Boolean;
var
  DRow, DCol: Integer;
begin
  for DRow := -1 to 1 do
    for DCol := -1 to 1 do
      if (Field.CursorRow + DRow = Row) and (Field.CursorCol + DCol = Col) then
        Exit(True);
  FieldAroundCursor := False;
end;

procedure FieldRandomize(var Field: TField; BombsPercentage: Integer);
var
  Index, BombsCount: Integer;
  Row, Col: Integer;
begin
  for Index := 0 to Field.Rows * Field.Cols do Field.Cells[Index] := Empty;
  if BombsPercentage > 100 then BombsPercentage := 100;
  BombsCount := (Field.Rows * Field.Cols * BombsPercentage + 99) div 100;
  for Index := 1 to BombsCount do
  begin
    {TODO: prevent it from going forever}
    while (FieldRandomCell(Field, Row, Col) = Bomb) or FieldAroundCursor(Field, Row, Col) do ;
    FieldSet(Field, Row, Col, Bomb);
  end;
end;

function FieldCountNeighbors(Field: TField; Row, Col: Integer): Integer;
var
  DRow, DCol: Integer;
  Cell: TCell;
begin
  FieldCountNeighbors := 0;
  for DRow := -1 to 1 do
    for DCol := -1 to 1 do
      if (DRow <> 0) or(DCol <> 0) then
      begin
        if FieldGetChecked(Field, Row + DRow, Col + DCol, Cell) then
          if Cell = Bomb then
            Inc(FieldCountNeighbors);
      end;
end;

procedure FieldWrite(Field: TField);
var
  Row, Col, Neighbors: Integer;
begin
  for Row := 0 to Field.Rows - 1 do
  begin
    for Col := 0 to Field.Cols - 1 do
    begin
      if FieldAtCursor(Field, Row, Col) then Write('[') else Write(' ');
      case FieldGetState(Field, Row, Col) of
        Open: case FieldGet(Field, Row, Col) of
          Bomb: Write('@');
          Empty: begin
            Neighbors := FieldCountNeighbors(Field, Row, Col);
            if Neighbors > 0 then Write(Neighbors) else Write(' ');
          end;
        end;
        Closed: Write('.');
        Flagged: Write('?');
      end;
      if FieldAtCursor(Field, Row, Col) then Write(']') else Write(' ');
    end;
    WriteLn;
  end;
end;

const
  STDIN_FILENO = 0;

var
  MainField: TField;
  Quit: Boolean = False;
  First: Boolean = True;

  TAttr, SavedTAttr: Termios;
  Cmd: Char;

begin
  Randomize;
  FieldResize(MainField, 10, 10);

  if IsATTY(STDIN_FILENO) = 0 then
  begin
    WriteLn('Error: This is not a terminal!');
    Exit;
  end;
  TCGetAttr(STDIN_FILENO, TAttr);
  TCGetAttr(STDIN_FILENO, SavedTAttr);
  TAttr.c_lflag := TAttr.c_lflag and not (ICANON or ECHO);
  TAttr.c_cc[VMIN] := 1;
  TAttr.c_cc[VTIME] := 0;
  TCSetAttr(STDIN_FILENO, TCSAFLUSH, TAttr);
  while not Quit do
  begin
    FieldWrite(MainField);
    Read(Cmd);
    case Cmd of
      'w': if MainField.CursorRow > 0                  then Dec(MainField.CursorRow);
      's': if MainField.CursorRow < MainField.Rows - 1 then Inc(MainField.CursorRow);
      'a': if MainField.CursorCol > 0                  then Dec(MainField.CursorCol);
      'd': if MainField.CursorCol < MainField.Cols - 1 then Inc(MainField.CursorCol);
      'f': FieldFlagAtCursor(MainField);
      ' ': begin
        if First then
        begin
          FieldRandomize(MainField, 20);
          First := False;
        end;
        if FieldOpenAtCursor(MainField) = Bomb then
        begin
          FieldOpenBombs(MainField);
          Write(Chr(27), '[', MainField.Rows, 'A');
          Write(Chr(27), '[', MainField.Cols * 3, 'D');
          FieldWrite(MainField);
          WriteLn('oops');
          break;
        end;
      end;
    end;
    Write(Chr(27), '[', MainField.Rows, 'A');
    Write(Chr(27), '[', MainField.Cols * 3, 'D');
  end;
  TCSetAttr(STDIN_FILENO, TCSAFLUSH, SavedTAttr);
end.