Question
C# Project I'm struggling with getting this new class to work. I need to build a class called GroupNPlayer that implements the IPlayer interface and
C# Project
I'm struggling with getting this new class to work. I need to build a class called GroupNPlayer that implements the IPlayer interface and takes user input for the 'GetAttackPosition' method so that the user can input grid coordinates such as A5 compare for a hit and then the AI will cycle through their turns and so forth, until game is complete. Below is the Game code which must be used, I just need to figure out how to build an actual user controlled, player object that will take in grid coordinates and function with the rest of the game:
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