Unit Players of BattleshipGames

by Josh Blake: L6 Age ~17

unit Players;
{
    Copyright (c) 2014 Josh Blake

    Licensed under the Apache License, Version 2.0 (the "License"); you may not
    use this file except in compliance with the License, as described at
    http://www.apache.org/licenses/ and http://www.pp4s.co.uk/licenses/
}
interface
uses
  Game,
  Grid,
  SysUtils,
  Settings,
  UI;

type
  TPlayer = class
  protected
    Game: TGame; //the game we are playing
    UI: TBaseUI; //the UI being used
    function SelectGuess: TCoord; virtual; abstract;
  public
    function TakeTurn: Integer;
    constructor Create(aGame: TGame; aUI: TBaseUI);
  end;

  AIEasy = class(TPlayer)
  private
    FoundShip: TCoord; //position where ship was found
    CurrentDirection,  //represents direction in which we've guessed ship lies
    TurnsSinceDestroy: //number of guesses on current mode
    Integer;
    Remaining: array of TCoord;
    function DestroyShip: TCoord;
    function RandomGuess: TCoord;
    procedure Setup(const GridSize: Integer);
  protected
    function SelectGuess: TCoord; override;
  end;

  User = class(TPlayer)
  protected
    function SelectGuess: TCoord; override;
  end;

implementation
constructor TPlayer.Create(aGame: TGame; aUI: TBaseUI);
begin
  Game := aGame;
  UI := aUI;
end;

function TPlayer.TakeTurn: Integer;
var
  GuessResult: TGuessResult;
  Guess: TCoord;
begin
  repeat
    Guess := SelectGuess;
    GuessResult := Game.ProcessGuess(Guess);
  until GuessResult <> Invalid;   //a turn has to have a valid guess
  if (GuessResult = Won) then Result := Game.GuessCount
  else begin
    Result := -1;
  end;
end;

procedure AIEasy.Setup(const GridSize: Integer);
var
  X, Y, I: Integer;
begin
  //Mark all grid squares as remaining
  SetLength(Remaining, GridSize * GridSize);
  I := 0;
  for X := 0 to GridSize - 1 do begin
    for Y := 0 to GridSize - 1 do begin
      Remaining[I].X := X;
      Remaining[I].Y := Y;
      Inc(I);
    end;
  end;
  //Mark that no ship has been found
  FoundShip.X := -1;
end;

function AIEasy.DestroyShip: TCoord;
var
  BaseGuess: TCoord; //guess to base next move off
begin
  if (CurrentDirection = -1) or (Game.LastGuessResult <> Hit) then begin
    CurrentDirection := Random(4);  //Need a new direction
  end;
  if Game.LastGuessResult = Hit then BaseGuess := Game.LastGuess //correct direction, continue searching
  else BaseGuess := FoundShip;

  case CurrentDirection of
    0: Dec(BaseGuess.Y); //Guess up
    1: Inc(BaseGuess.X); //Guess right
    2: Inc(BaseGuess.Y); //Guess down
    3: Dec(BaseGuess.X); //Guess left
  end;
  Result := BaseGuess;
end;

function AIEasy.RandomGuess: TCoord;
var
  Selection, I: Integer;
begin
  Selection := Random(Length(Remaining)); //Select a random square
  Result := Remaining[Selection]; //return the selection
  //Remove from remaining
  for I := Selection to Length(Remaining) - 2 do begin
    Remaining[I] := Remaining[I + 1];
  end;
  SetLength(Remaining, Length(Remaining) - 1);
end;

function AIEasy.SelectGuess: TCoord;
begin
  Log('Select Guess');
  if Game.LastGuessResult = None then Setup(Length(Game.Grid)); //setup for new game
  if (Game.LastGuessResult = Hit) and (FoundShip.X = -1) then begin
    TurnsSinceDestroy := 0;
    FoundShip := Game.LastGuess;    //look for rest of ship
    CurrentDirection := -1;
  end;
  
  if Game.LastGuessResult = Sunk then begin
    FoundShip.X := -1;   //ship sunk, don't keep searching
    TurnsSinceDestroy := 0;
  end;

  if FoundShip.X <> -1 then begin
    Inc(TurnsSinceDestroy);
    if TurnsSinceDestroy > 31 then begin
      FoundShip.X := -1; //stop infinite loop as AI is stupid for some situations
    end;
    Result := DestroyShip;
  end else begin
    Inc(TurnsSinceDestroy);  //track turns since last state change
    Result := RandomGuess;
  end;
end;

function User.SelectGuess: TCoord;
begin
  Result := UI.GetUserGuess(Game);
end;

end.
Programming - a skill for life!

by Josh Blake: L6 Age ~17