Question
C# I need a smarter AI player. I could use some help creating a smarter AI Player. The code below is the full program broken
C# I need a smarter AI player. I could use some help creating a smarter AI Player. The code below is the full program broken out, but the focus is using something similar to the 'Random Player' class, but building on it so that it formulates better guesses based on previous AI guesses by the 'Dumb Player' and the 'Random Player'. Thanks for the help!
internal class MultiPlayerBattleShip { const int GridSize = 10; //Your player should work when GridSize >=7
private static readonly Random Random = new Random();
private readonly List _players;
private List _playerGrids; private List _playerShips; private List currentPlayers;
public MultiPlayerBattleShip(List players) { this._players = players; }
internal void Play(PlayMode playMode) { currentPlayers = new List(); var availablePlayers = new List(_players); _playerGrids = new List(); _playerShips = new List();
//Add each player in a random order for (int i = 0; i < _players.Count; i++) { var player = availablePlayers[Random.Next(availablePlayers.Count)]; availablePlayers.Remove(player); currentPlayers.Add(player); }
//Tell each player the game is about to start for (int i=0; i { var ships = new Ships(); ships.Add(new AircraftCarrier()); ships.Add(new Submarine()); ships.Add(new Destroyer()); ships.Add(new Destroyer()); ships.Add(new PatrolBoat()); ships.Add(new PatrolBoat()); ships.Add(new PatrolBoat()); ships.Add(new Battleship());
var count = ships._ships.Count(); int totalLength = ships._ships.Sum(ship => ship.Length); currentPlayers[i].StartNewGame(i, GridSize, ships);
//Make sure player didn't change ships if (count != ships._ships.Count() || totalLength != ships._ships.Sum(ship => ship.Length)) { throw new Exception("Ship collection has ships added or removed"); }
var grid = new Grid(GridSize); grid.Add(ships); _playerGrids.Add(grid); _playerShips.Add(ships); }
int currentPlayerIndex = 0; while (currentPlayers.Count > 1) { var currentPlayer = currentPlayers[currentPlayerIndex];
//Ask the current player for their move Position pos = currentPlayer.GetAttackPosition();
//Work out if anything was hit var results = CheckAttack(pos);
//Notify each player of the results foreach (var player in currentPlayers) { player.SetAttackResults(results); }
DrawGrids();
Console.WriteLine(" Player " + currentPlayer.Index + "[" + currentPlayer.Name + "] turn."); Console.WriteLine(" Attack: " + pos.X + "," + pos.Y); Console.WriteLine(" Results:"); foreach (var result in results) { Console.Write(" Player " + result.PlayerIndex + " " + result.ResultType); if (result.ResultType == AttackResultType.Sank) { Console.Write(" - " + result.SunkShip); } Console.WriteLine(); }
//Remove any ships with sunken battleships //Iterate backwards so that we don't mess with the indexes for (int i = currentPlayers.Count - 1; i >= 0; --i) { var player = currentPlayers[i]; if (_playerShips[player.Index].SunkMyBattleShip) { currentPlayers.Remove(player); //We never want to remvoe all the players... if (currentPlayers.Count == 1) { break; } } }
//Move to next player wrapping around the end of the collection currentPlayerIndex = (currentPlayerIndex + 1)%currentPlayers.Count;
if (playMode == PlayMode.Pause) { Console.WriteLine(" Press a key to continue"); Console.ReadKey(true); } else { Thread.Sleep(2000); } }
Console.WriteLine(); Console.WriteLine("Winner is '" + currentPlayers[0].Name + "'"); Console.ReadKey(true);
}
private List CheckAttack(Position pos) { var results = new List();
foreach (var player in currentPlayers) { var result = _playerShips[player.Index].Attack(pos);
//Mark attacks on the grid foreach (var grid in _playerGrids) { grid.Attack(pos); }
result.PlayerIndex = player.Index; results.Add(result); } return results; }
private void DrawGrids() { Console.Clear(); int drawX = 0; int drawY = 0;
for (int i=0; i < currentPlayers.Count; i++) { var player = currentPlayers[i]; var playerIndex = player.Index;
var grid = _playerGrids[playerIndex]; Console.SetCursorPosition(drawX, drawY); Console.ForegroundColor = ConsoleColor.Black; Console.BackgroundColor = ConsoleColor.White;
Console.Write(player.Name); grid.Draw(drawX, drawY+1);
drawX += GridSize + 4; if (drawX + GridSize > Console.BufferWidth) { drawY += GridSize + 5; drawX = 0; } } }
}
public class Grid { private readonly GridEntry[,] _grid; private readonly int _gridSize;
public Grid(int gridSize) { _gridSize = gridSize; _grid = new GridEntry[gridSize,gridSize]; //Fill the grid with empty entries marked as not hit for (int x = 0; x < gridSize; x++) { for (int y = 0; y < gridSize; y++) { _grid[x,y] = new GridEntry(); } } }
public void Add(Ships ships) { foreach (var ship in ships._ships) { if (ship.Positions == null) { throw new ArgumentException("A player has not set the ships positions"); }
foreach (var pos in ship.Positions) { if (pos.X< 0 || pos.X >_gridSize || pos.Y <0 || pos.y>= _gridSize) { throw new ArgumentException("One of the ships is outside the grid"); }
if (pos.Hit) { throw new ArgumentException("One of the players is adding a hit ship to the game"); }
if (_grid[pos.X, pos.Y].Ship != null) { throw new ArgumentException("One of the players has an overlapping ship"); }
_grid[pos.X, pos.Y].Ship = ship; } } }
public void Draw(int drawX, int drawY) { for (int x = 0; x < _gridSize; x++) { for (int y = 0; y < _gridSize; y++) { Console.SetCursorPosition(drawX + x, drawY + y); Console.ForegroundColor = (_grid[x, y].Ship == null) ? ConsoleColor.Gray : _grid[x, y].Ship.Color; //Find if this segment of the ship is hit Console.BackgroundColor = (_grid[x,y].Hit)? ConsoleColor.Red : ConsoleColor.Black; if (_grid[x, y].Ship == null) { Console.Write("."); } else { Console.Write(_grid[x, y].Ship.Character); } } }
//Reset colors Console.BackgroundColor = ConsoleColor.Black; Console.ForegroundColor = ConsoleColor.White; }
public void Attack(Position pos) { _grid[pos.X, pos.Y].Hit = true; } }
public class GridEntry { public bool Hit; public Ship Ship; }
interface IPlayer { ///
/// Initializes the players are the start of a game and returns the positions of the ships /// Note: This method should be used to reset any AI state. It will be called once per game and each session might be multiple games /// You may also use this to generate new data structures for this game. The Test harness will handle checking for hits based on your /// returned value so it is up to you if and how you want to store the representation of your own grid ///
///What is the index of this player for this game - may change each game ///Size of the square grid - may change each game ///A list of Ships to provide positions for - may change each game. You should populate this collection with positions void StartNewGame(int playerIndex, int gridSize, Ships ships);
///
/// The name of this player - displayed in the UI ///
String Name { get; }
///
/// The index of this player - it should return the index passed into the StartNewGame ///
int Index { get; }
///
/// This is where you put the AI that chooses which square to target ///
/// A position with an x, y coordinate Position GetAttackPosition();
///
/// The game will notify you of the results of each attack. ///
///A collection for each player still in the game /// You will get the index, the attack position and the result of the attack void SetAttackResults(List results); }
public enum PlayMode { Delay, Pause, }
internal class DumbPlayer : IPlayer { private static int _nextGuess = 0; //Note static - we all share the same guess
private int _index; private int _gridSize;
public DumbPlayer(string name) { Name = name; } public void StartNewGame(int playerIndex, int gridSize, Ships ships) { _gridSize = gridSize; _index = playerIndex;
//DumbPlayer just puts the ships in the grid one on each row int y = 0; foreach (var ship in ships._ships) { ship.Place(new Position(0, y++), Direction.Horizontal); } }
public Position GetAttackPosition() { //A *very* naive guessing algorithm that simply starts at 0, 0 and guess each square in order //All 'DumbPlayers' share the counter so they won't guess the same one //But we don't check to make sure the square has not been guessed before var pos = new Position(_nextGuess % _gridSize, (_nextGuess /_gridSize)); _nextGuess++; return pos; }
public void SetAttackResults(List results) { //DumbPlayer does nothing with these results - its going to keep making dumb guesses }
public string Name { get; }
public int Index => _index; }
internal class RandomPlayer : IPlayer { private static readonly List Guesses = new List(); private int _index; private static readonly Random Random = new Random(); private int _gridSize;
public RandomPlayer(string name) { Name = name; }
public void StartNewGame(int playerIndex, int gridSize, Ships ships) { _gridSize = gridSize; _index = playerIndex;
GenerateGuesses();
//Random player just puts the ships in the grid in Random columns //Note it cannot deal with the case where there's not enough columns //for 1 per ship var availableColumns = new List(); for (int i = 0; i < gridSize; i++) { availableColumns.Add(i); }
foreach (var ship in ships._ships) { //Choose an X from the set of remaining columns var x = availableColumns[Random.Next(availableColumns.Count)]; availableColumns.Remove(x); //Make sure we can't pick it again
//Choose a Y based o nthe ship length and grid size so it always fits var y = Random.Next(gridSize - ship.Length); ship.Place(new Position(x, y), Direction.Vertical); } }
private void GenerateGuesses() { //We want all instances of RandomPlayer to share the same pool of guesses //So they don't repeat each other.
//We need to populate the guesses list, but not for every instance - so we only do it if the set is missing some guesses if (Guesses.Count < _gridSize*_gridSize) { Guesses.Clear(); for (int x = 0; x < _gridSize; x++) { for (int y = 0; y < _gridSize; y++) { Guesses.Add(new Position(x,y)); } } } }
public string Name { get; } public int Index => _index;
public Position GetAttackPosition() { //RandomPlayer just guesses random squares. Its smart in that it never repeats a move from any other random //player since they share the same set of guesses //But it doesn't take into account any other players guesses var guess = Guesses[Random.Next(Guesses.Count)]; Guesses.Remove(guess); //Don't use this one again return guess; }
public void SetAttackResults(List results) { //Random player does nothing useful with these results, just keeps on making random guesses } }
public abstract class Ship {
private Position[] _positions; public readonly int Length; public readonly ConsoleColor Color; public readonly ShipTypes ShipType; private static readonly char[] Characters = {'?', 'P', 'S', 'D', 'A', 'B'};
public virtual bool IsBattleShip => false;
protected Ship(int length, ConsoleColor color, ShipTypes shipType) { Length = length; Color = color; ShipType = shipType; }
public void Reset() { _positions = null; }
public char Character => Characters[(int) ShipType];
public Position[] Positions { get { if (_positions == null) { return null; } //Return a copy since this is a readonly field var retval = new Position[_positions.Length]; Array.Copy(_positions, retval, _positions.Length); return retval; } }
public void Place(Position start, Direction direction) { _positions = new Position[Length]; for (int i = 0; i < Length; i++) { _positions[i] = new Position(start.X, start.Y); if (direction == Direction.Horizontal) start.X++; if (direction == Direction.Vertical) start.Y++; } }
public AttackResult Attack(Position pos) { foreach (var position in _positions) { if (position.X == pos.X && position.Y == pos.Y) { position.Hit = true; if (Sunk) { return new AttackResult(0, pos, AttackResultType.Sank, ShipType); } return new AttackResult(0, pos, AttackResultType.Hit); } }
return new AttackResult(0, pos); //Miss }
public bool Sunk { get { return _positions.All(position => position.Hit); } } }
public class Ships { public readonly List _ships = new List();
public void Clear() { _ships.Clear(); }
public bool SunkMyBattleShip { get { //Find the battleship and see if its sunk foreach (var ship in _ships) { var battleShip = ship as Battleship; if (battleShip != null) { return battleShip.Sunk; }
}
throw new Exception("Cannot find a battleship"); } }
public void Add(Ship ship) { _ships.Add(ship); }
public AttackResult Attack(Position pos) { //Search the positions for a hit foreach (var ship in _ships) { AttackResult attackResult = ship.Attack(pos); if (attackResult.ResultType != AttackResultType.Miss) { return attackResult; //Once we hit then no point looking any more } }
//No hits means a miss! return new AttackResult(0, pos); }
}
public class Position
{ public int X; public int Y; public bool Hit; public Position(int x, int y) { X = x; Y = y; Hit = false; } }
public enum Direction { Horizontal, Vertical, }
public enum ShipTypes { None, PatrolBoat, Submarine, Destroyer, AircraftCarrier, Battleship, }
class AircraftCarrier : Ship { public AircraftCarrier() : base(5, ConsoleColor.Blue, ShipTypes.AircraftCarrier) {
}
}
class Battleship : Ship { public Battleship() : base(4, ConsoleColor.DarkGreen, ShipTypes.Battleship) {
}
public override bool IsBattleShip => true; }
class Destroyer : Ship { public Destroyer() : base(3, ConsoleColor.DarkRed, ShipTypes.Destroyer) {
}
}
class PatrolBoat : Ship { public PatrolBoat() : base(2, ConsoleColor.White, ShipTypes.PatrolBoat) {
}
}
class Submarine : Ship { public Submarine() : base(3, ConsoleColor.Cyan, ShipTypes.Submarine) {
}
}
public enum AttackResultType { Miss, Hit, Sank, }
public struct AttackResult { public int PlayerIndex; public Position Position; public AttackResultType ResultType; public ShipTypes SunkShip; //Filled in if ResultType is Sunk
public AttackResult(int playerIndex, Position position, AttackResultType attackResultType= AttackResultType.Miss, ShipTypes sunkShip = ShipTypes.None) { PlayerIndex = playerIndex; Position = position; ResultType = attackResultType; SunkShip = sunkShip; } }
class Program { static void Main(string[] args) { List players = new List(); players.Add(new DumbPlayer("Dumb 1")); players.Add(new RandomPlayer("Random 1"));
//Your code here //players.Add(new GroupNPlayer());
MultiPlayerBattleShip game = new MultiPlayerBattleShip(players); game.Play(PlayMode.Pause); } }
Step by Step Solution
There are 3 Steps involved in it
Step: 1
Get Instant Access to Expert-Tailored Solutions
See step-by-step solutions with expert insights and AI powered tools for academic success
Step: 2
Step: 3
Ace Your Homework with AI
Get the answers you need in no time with our AI-driven, step-by-step assistance
Get Started