///
/// Created by Maxim Britvin
/// Зачем нужно это упражнение:
/// 1. Показывает как можно вызвать событие в родительской форме из дочернего объекта - CheckMine в классе FieldTile
/// 2. Показывает как можно реализовать динамическое создание элементов на форме
/// 3. Показывает как можно использовать наследование от классов Форсайта
/// 4. Если не вызывать функцию по закрытию формы - становится примером демонстрирующим утечку памяти в Форсайте
///
Const MINE = "💣";
Const ROWS = 10;
Const COLUMNS = 10;
Const MINES_COUNT = 30;
Const BUTTON_WIDTH = 40;
Const BUTTON_HEIGHT = 40;
Const BUTTON_MARGIN = 2;
///
/// Класс-помощник для обработки координат
///
Class ButtonCoords: Object
_row: Integer;
_col: Integer;
Public Constructor Create(row: Integer; col: Integer);
Begin
_row := row;
_col := col;
End Constructor Create;
Public Function Up: ButtonCoords;
Begin
Return New ButtonCoords.Create(Self._row - 1, Self._col);
End Function Up;
Public Function Down: ButtonCoords;
Begin
Return New ButtonCoords.Create(Self._row + 1, Self._col);
End Function Down;
Public Function Left: ButtonCoords;
Begin
Return New ButtonCoords.Create(Self._row, Self._col - 1);
End Function Left;
Public Function Right: ButtonCoords;
Begin
Return New ButtonCoords.Create(Self._row, Self._col + 1);
End Function Right;
Public Property Row: Integer
Get
Begin
Return _row
End Get
End Property Row;
Public Property Column: Integer
Get
Begin
Return _col
End Get
End Property Column;
End Class ButtonCoords;
///
/// Одна ячейка на поле
///
Class FieldTile: PPButton
_is_mine: Boolean;
_but_index: Integer;
_coords: ButtonCoords;
Public Constructor Create(but_index: Integer; row: Integer; column: Integer);
Begin
Inherited Create;
_but_index := but_index;
_coords := New ButtonCoords.Create(row, column);
Self.Width := BUTTON_WIDTH;
Self.Height := BUTTON_HEIGHT;
Self.Left := 5 + (BUTTON_WIDTH + BUTTON_MARGIN) * column;
self.Top := 5 + (BUTTON_HEIGHT + BUTTON_MARGIN) * row + 40;
self.OnClick := CheckMine;
self.Text := " ";
self.Align
End Constructor Create;
Public Property IsMine: Boolean
Get
Begin
Return _is_mine
End Get
End Property IsMine;
Public Sub PlantMine;
Begin
Self._is_mine := True;
End Sub PlantMine;
Public Sub ShowMine;
Begin
If _is_mine Then
self.Text := MINE;
End If;
self.Enabled := False;
End Sub ShowMine;
Private Sub CheckMine(Sender: Object; Args: IMouseEventArgs);
Var
resultCheck: Integer;
Begin
If self._is_mine Then
(Self.Parent As IFormControl).SendCommand("FindBomb");
Else
resultCheck := (Self.Parent As IFormControl).SendCommand("CheckNeighbours", self._coords) As Integer;
Self.Text := resultCheck = 0 ? "" : resultCheck.ToString;
self.Enabled := False;
End If;
End Sub CheckMine;
End Class FieldTile;
// Игровое поле
Class DEV_MINESWEEPERForm: Form
MB: IMetabase;
logonSession: ILogonSession;
game_button: FieldTile;
PlayGround: IArrayList;
MinesTiles: IList;
NewGame: PPButton;
Sub DEV_MINESWEEPERFormOnCreate(Sender: Object; Args: IEventArgs);
Var
mines_positions: IList;
Begin
self.Width := (COLUMNS * BUTTON_WIDTH) + (BUTTON_MARGIN * COLUMNS * 2) + 15;
self.Height := (ROWS * BUTTON_HEIGHT) + (BUTTON_MARGIN * ROWS * 2) + 100;
mines_positions := New ArrayList.Create;
Self.PlayGround := New ArrayList.Create;
If MINES_COUNT >= ROWS * COLUMNS Then
Raise New Exception.Create("Can't place " + MINES_COUNT.ToString + "mines to field " + ROWS.ToString + " x " + COLUMNS.ToString);
End If;
Debug.WriteLine("Start");
NewGame := New PPButton.Create;
NewGame.Parent := Self;
NewGame.Text := "Новая игра";
NewGame.Top := 15;
NewGame.Width := 100;
NewGame.Left := (self.Width Div 2) - 60;
NewGame.OnClick := NewGameButton_OnClick;
mines_positions := Self.GenerateMines;
Self.PlayGround := Self.CreatePlayground(mines_positions);
End Sub DEV_MINESWEEPERFormOnCreate;
Private Function CreatePlayground(minesList: IList): IArrayList;
Var
butt_index_rows: Integer;
butt_index_cols: Integer;
button_index: Integer;
game_button: FieldTile;
playground: IArrayList;
field_row: IArrayList;
Begin
// Создание поля для игры
playground := New ArrayList.Create;
For butt_index_rows := 0 To ROWS - 1 Do
field_row := New ArrayList.Create;
For butt_index_cols := 0 To COLUMNS - 1 Do
button_index := butt_index_rows * ROWS + butt_index_cols + 1;
game_button := New FieldTile.Create(button_index, butt_index_rows, butt_index_cols);
game_button.Parent := Self;
If minesList.Contains(button_index) Then
game_button.PlantMine;
End If;
field_row.Add(game_button);
End For;
playground.Add(field_row);
End For;
Return playground;
End Function CreatePlayground;
Private Function GenerateMines: IList;
Var
minesList: IList;
mine_position: Integer;
Begin
// Генерация мин
minesList := New ArrayList.Create;
While minesList.Count < MINES_COUNT Do
mine_position := Double.CeilingInt(Math.RandBetween(0, ROWS * COLUMNS));
If Not minesList.Contains(mine_position) Then
minesList.Add(mine_position)
End If;
End While;
Return minesList
End Function GenerateMines;
Public Sub NewGameButton_OnClick(Sender: Object; Args: IMouseEventArgs);
Begin
CreateNewGame;
End Sub NewGameButton_OnClick;
Private Sub CreateNewGame;
Var
mines_positions: IList;
Begin
self.PlayGround.Clear;
mines_positions := New ArrayList.Create;
mines_positions := Self.GenerateMines;
Self.PlayGround := Self.CreatePlayground(mines_positions);
End Sub CreateNewGame;
Private Sub ShowAllBombs;
Var
row: IArrayList;
tile: FieldTile;
Begin
For Each row In Self.PlayGround Do
For Each tile In row Do
tile.ShowMine;
End For;
End For;
End Sub ShowAllBombs;
Private Function IsValidCoord(coord: ButtonCoords): Boolean;
Begin
Return Not ((coord.Row < 0) Or (coord.Column < 0) Or (coord.Row >= ROWS) Or (coord.Column >= COLUMNS));
End Function IsValidCoord;
Private Function IsBombHere(coord: ButtonCoords): Integer;
///
/// Возвращаем 1, если есть бомба и 0 если нет
///
Var
checked_row: IArrayList;
checked_tile: FieldTile;
Begin
If IsValidCoord(coord) Then
checked_row := Self.PlayGround.Item(coord.Row);
checked_tile := checked_row.Item(coord.Column);
If checked_tile.IsMine Then
Return 1;
End If;
End If;
Return 0;
End Function IsBombHere;
Public Function CheckNeighbours(coord: ButtonCoords): Integer;
///
/// Строим соседей и проверяем их. Возвращаем количество соседних бомб
///
Var
click_row: Integer;
click_col: Integer;
result: Integer;
Begin
click_row := coord.Row;
click_col := coord.Column;
result := 0;
result := IsBombHere(coord.Up) + IsBombHere(coord.Down) + IsBombHere(coord.Left) + IsBombHere(coord.Right);
result := result + IsBombHere(coord.Up.Left) + IsBombHere(coord.Up.Right);
result := result + IsBombHere(coord.Down.Left) + IsBombHere(coord.Down.Right);
Return result;
End Function CheckNeighbours;
Sub DEV_MINESWEEPERFormOnCommand(Sender: Object; Args: ICommandEventArgs);
Begin
If Args.Command = "CheckNeighbours" Then
Args.Result := CheckNeighbours(Args.Argument);
Elseif Args.Command = "FindBomb" Then
ShowAllBombs;
End If;
End Sub DEV_MINESWEEPERFormOnCommand;
Sub DEV_MINESWEEPERFormOnClose(Sender: Object; Args: IEventArgs);
Begin
self.PlayGround.Clear;
Dispose Self.PlayGround;
End Sub DEV_MINESWEEPERFormOnClose;
End Class DEV_MINESWEEPERForm;