Answered step by step
Verified Expert Solution
Link Copied!

Question

1 Approved Answer

C# Question: Battleship AI~ NOTE ==== I have provided our attempt to solve the problem at the very bottom of this post with the addition

C# Question:

Battleship AI~

NOTE ==== I have provided our attempt to solve the problem at the very bottom of this post with the addition of both a new cs file as well as a code snippet that follows that is being considered to solve the issue of what happens after a ship is detected by the AI.

The AI should have three modes, 1) Hunt 2) Agression 3)Sink

Hunt should be a method to select cells within the array to attck obviously

Agression should be how upon detection of an enemy ship the AI should focus on attacking allr emaining cells systematically until that ship has been sunk

Sink should be the after the AI sinks a ship, it declairs what ship was sunk and return to hunt mode for the following turn.

Implement a multiplayer battleship AI.

The rules are the same as last week.

The game is played on an NxN grid

Each player will place a specified set of ships

The ships will vary in length from 2 to 5.

There can be any number or any size ship including 0

EXCEPT the battleship which there will always be 1 and only 1

Player order will be random but fixed at the start of the game

Each round consists of each player making a grid selection in turn

Each grid selection will be played into all players grid. Including the current players grid

Each player will respond with HIT, MISS or SANK {ship name}

If a players battleship is sunk that player is removed from the game

Repeat from #4 until 1 player remains

That player is the winner

Design update

Examine the test framework from blackboard.

Review the IPlayer interface this is the entry point to your code. You will need to implement each of these functions. Does this interface provide you with enough information to implement? If not speak to your instructor maybe accommodations can be made

Review the DumbPlayer and RandomPlayer implementations to see an example of how simple AIs can be built.

Run the code and watch the game play out between the example players

Complete the AI

Your code should be able to play against the provided AIs and also will be challenged by the AIs from other teams as well as one from the instructor

If your code takes an unreasonable amount of time or crashes then your player will be removed from the game.

Since you have the test harness you should be able to run many test passes yourself against the sample AIs

Note any attempts to use reflection or any other similar techniques to determine other players positions will be considered cheating and result in 0 for the assignment.

Grid.cs --

using System; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates;

namespace Week6 { 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; } } }

GridEntry.cs --

namespace Week6 { public class GridEntry { public bool Hit; public Ship Ship; } }

IPlayer.cs --

using System; using System.Collections.Generic;

namespace Week6 { 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); } }

MultiPlayerBattleShip.cs --

using System; using System.CodeDom; using System.CodeDom.Compiler; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Threading; using Gsd311.Week6.Group1;

namespace Week6 { 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 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; } } }

} }

PlayerMode.cs --

namespace Week6 { public enum PlayMode { Delay, Pause, } }

Program.cs --

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;

namespace Week6 { class Program { static void Main(string[] args) {

List players = new List(); players.Add(new DumbPlayer("Dumb 1")); // players.Add(new DumbPlayer("Dumb 2")); // players.Add(new DumbPlayer("Dumb 3")); players.Add(new RandomPlayer("Random 1")); // players.Add(new RandomPlayer("Random 2")); // players.Add(new RandomPlayer("Random 3")); // players.Add(new RandomPlayer("Random 4")); // players.Add(new RandomPlayer("Random 5"));

//Your code here players.Add(new Gsd311.Week6.Group1.Group1Player.Group1PLayer("Group1PLayer"));

MultiPlayerBattleShip game = new MultiPlayerBattleShip(players); game.Play(PlayMode.Pause); } } }

============================================================

============================================================

So far our custom code to try and solve the question is as follows

We are trying to get the AttackMode working, CheckGuessStack function, and Ship Placement.

Code Attempt 2

Group1Player.cs --

using System; using System.CodeDom.Compiler; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Runtime.InteropServices; using System.Security.Policy; using System.Text; using System.Threading.Tasks; using Week6;

namespace Gsd311.Week6.Group1 { class Group1Player { private enum GridStatusType { Miss, Hit, Open }

internal class Group1PLayer : IPlayer { private int BattleshipSize = 4; private static readonly Stack Guesses = new Stack(); private int _index; private static readonly Random Random = new Random(); private int _gridSize; private Queue AttackQueue = new Queue(); private List GuessedPositions = new List(); private Ships OurShips;

private int playerCounter = 0; private bool lastHit = false; //last shot we fired hit. private hits lastHitLocation; //last hit location private int hitSpacer = 3; //used to iterate hit locations based on battleship size.

public struct hits { public int x; public int y;

public hits(int hitx, int hity) { x = hitx; y = hity; } }

public Group1PLayer(string name) { Name = name; }

public void StartNewGame(int playerIndex, int gridSize, Ships ships) { _gridSize = gridSize; _index = playerIndex;

//place ships 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); } //end ship placement OurShips = ships; GenerateGuesses(); }

private void GenerateGuesses() { List GuessBuffer = new List(); for (int i = 0; i < _gridSize; i++) { for (int j = 0; j < _gridSize; j++) { if ((j - i) % BattleshipSize == 0) { GuessBuffer.Add(new Position(j, i)); } } } // First make sure the space on our own battleship is picked last for(int i = 0; i < GuessBuffer.Count; i++) { if(CheckOwnBattleshipPostion(GuessBuffer[i])) { Guesses.Push(GuessBuffer[i]); GuessBuffer.RemoveAt(i); break; } }

// Randomize our guesses, this probably isn't important vs other AI but would be if vs another player while(GuessBuffer.Count != 0) { int index = Random.Next(0, GuessBuffer.Count - 1); Guesses.Push(GuessBuffer[index]); GuessBuffer.RemoveAt(index); } }

public string Name { get; } public int Index => _index;

public Position GetAttackPosition() { // Check if we're done sinking our current target if(AttackQueue.Count > 0) { if (!CanFitBattleship(AttackQueue.Peek(), Direction.Horizontal) && !CanFitBattleship(AttackQueue.Peek(), Direction.Horizontal)) { AttackQueue.Dequeue(); } }

if (AttackQueue.Count == 0) { return GetHuntModePosition(); } else { return GetAttackModePosition(AttackQueue.Peek()); }

}

/* * Gets the next guess if we are in hunt mode */ private Position GetHuntModePosition() { bool validGuess = false; Position guess = new Position(0, 0); while(!validGuess) { if (Guesses.Count == 0) { // If programmed correctly we should never get here, however we do need mitigation until we get there // if we run out of guesses just switch to random mode int x = Random.Next(0, _gridSize); int y = Random.Next(0, _gridSize); guess = new Position(x, y); } else { guess = Guesses.Pop(); } validGuess = CheckGridLocationOpen(guess); } return guess; }

private Position GetAttackModePosition(Position origin) { var shoot = RunHorizontal(origin); if (shoot == origin) { shoot = RunVertical(origin); } return shoot; }

private Position RunHorizontal(Position horizontal) { int i = 1; if (CanFitBattleship(horizontal, Direction.Horizontal)) { do { var hShoot = new Position(horizontal.X + i, horizontal.Y); if (CheckGridLocationOpen(hShoot)) { return hShoot;

} else { i++; } } while (i < 5); return horizontal; } return horizontal; }

private Position RunVertical(Position vertical) { int i = 1; if (CanFitBattleship(vertical, Direction.Vertical)) { do { var vShoot = new Position(vertical.X, vertical.Y + 1); if (CheckGridLocationOpen(vShoot)) { return vShoot;

} else { i++; } } while (i < 5); return vertical; } return vertical; }

/* * Checks if the passed grid position has already been guessed by us or another * player. Returns true if the grid location is open (has not been guessed yet) */ private bool CheckGridLocationOpen(Position pos) { GridStatusType cellStatus = GetGridStatus(pos); return cellStatus == GridStatusType.Open; }

public void SetAttackResults(List results) { // Since we don't have direct access to the grid, we need to store all guessed squares Position guessedPosition = new Position(results[0].Position.X, results[0].Position.Y);

foreach(AttackResult res in results) { if(res.ResultType == AttackResultType.Hit) { // Update our internal grid guessedPosition.Hit = true;

// Check if want to add this to our Attack Queue if(res.PlayerIndex != _index && CheckGuessStack(guessedPosition) && (CanFitBattleship(guessedPosition, Direction.Horizontal) || CanFitBattleship(guessedPosition, Direction.Vertical))) { AttackQueue.Enqueue(guessedPosition); } } } GuessedPositions.Add(guessedPosition); }

/* * Determines if a battleship can logically fit in a given location in the provided direction on the grid */ private bool CanFitBattleship(Position position, Direction direction) { int negativeSpaces = 0; int positiveSpaces = 0; bool positiveEndReached = false; bool negativeEndReached = false; int counter = 1; // We only care if a battleship can fit if we still have an open space that we can guess there! bool hasOpenSpot = false;

while(!negativeEndReached && counter <= BattleshipSize) { if(direction == Direction.Horizontal) { if (position.X - counter >= 0 && GetGridStatus(new Position(position.X - counter, position.Y)) != GridStatusType.Miss) { hasOpenSpot |= GetGridStatus(new Position(position.X - counter, position.Y)) == GridStatusType.Open; negativeSpaces++; } else negativeEndReached = true; } else { if (position.Y - counter >= 0 && GetGridStatus(new Position(position.X, position.Y - counter)) != GridStatusType.Miss) { hasOpenSpot |= GetGridStatus(new Position(position.X, position.Y - counter)) == GridStatusType.Open; negativeSpaces++; } else negativeEndReached = true; } counter++; } counter = 1; while(!positiveEndReached && counter <= BattleshipSize) { if(direction == Direction.Horizontal) { if (position.X + counter < _gridSize && GetGridStatus(new Position(position.X + counter, position.Y)) != GridStatusType.Miss) { hasOpenSpot |= GetGridStatus(new Position(position.X + counter, position.Y)) == GridStatusType.Open; positiveSpaces++; } else positiveEndReached = true; } else { if (position.Y + counter < _gridSize && GetGridStatus(new Position(position.X, position.Y + counter)) != GridStatusType.Miss) { hasOpenSpot |= GetGridStatus(new Position(position.X, position.Y + counter)) == GridStatusType.Open; positiveSpaces++; } else positiveEndReached = true; } counter++; } return (negativeSpaces + positiveSpaces + 1 >= BattleshipSize && hasOpenSpot); }

/* * Checks our local grid of results for a specific position and returns the grid type there */ private GridStatusType GetGridStatus(Position position) { foreach(Position guessed in GuessedPositions) { if(guessed.X == position.X && guessed.Y == position.Y) { if(guessed.Hit) { return GridStatusType.Hit; } else { return GridStatusType.Miss; } } } return GridStatusType.Open; }

/* * Determines if a position given would hit our battleship, this used to determine our guess order and * decide if we'll act on a hit */ private bool CheckOwnBattleshipPostion(Position pos) { bool isInPosition = false; foreach(Ship ship in OurShips._ships) { if(ship.IsBattleShip) { foreach(Position battleshipPosition in ship.Positions) { isInPosition |= (battleshipPosition.X == pos.X && battleshipPosition.Y == pos.Y); } } } return isInPosition; }

/* * Checks if a Position is in our Guesses Stack * TODO: COMPLETE THIS! */ private bool CheckGuessStack(Position position) { // Temp return return true; } } } }

Code attempt snippet for attack mode----

int shipCounter = 0, trend = 0; static Random rnd = new Random(); bool gameOver = false, playerTurn = false; int[] score = { 0, 0 };

struct cellDetection { public bool occupied, hit, marked; } cellDetection[,,] data;

public void aiTurn() { Point target = aiHunt();

try { if (data[1, target.X, target.Y].hit) aiTurn(); else { data[1, target.X, target.Y].hit = true; if (data[1, target.X, target.Y].occupied) { attacking = true; score[0]++; aiTurn(); } }

playerTurn = true; } catch (IndexOutOfRangeException) { aiTurn(); } }

public Point aiHunt() { Point origin = new Point(-1, -1);

//find a point that's been hit. int x = 0, y = 0;

while (x < gridSize && y < gridSize) { if (data[1, x, y].hit && data[1, x, y].occupied && !data[1, x, y].marked) { origin = new Point(x, y); break; } x++; if (x == gridSize && y != gridSize) { x = 0; y++; } }

return huntShips(origin); }

public Point huntShips(Point origin) { Point[] lim = { origin, origin, origin, origin }; Point[] possibleTargets = { origin, origin, origin, origin };

//Find the edges.

while (lim[0].X >= -1 && ((!data[1, lim[0].X, lim[0].Y].hit && !data[1, lim[0].X, lim[0].Y].occupied) || (data[1, lim[0].X, lim[0].Y].hit && data[1, lim[0].X, lim[0].Y].occupied))) { lim[0].X--; if (lim[0].X == -1) break; } while (lim[1].Y >= -1 && ((!data[1, lim[0].X, lim[0].Y].hit && !data[1, lim[0].X, lim[0].Y].occupied) || (data[1, lim[0].X, lim[0].Y].hit && data[1, lim[0].X, lim[0].Y].occupied))) { lim[1].Y--; if (lim[1].Y == -1) break; } while (lim[2].X <= gridSize && ((!data[1, lim[0].X, lim[0].Y].hit && !data[1, lim[0].X, lim[0].Y].occupied) || (data[1, lim[0].X, lim[0].Y].hit && data[1, lim[0].X, lim[0].Y].occupied))) { lim[2].X++; if (lim[2].X == gridSize) break; } while (lim[3].Y <= gridSize && ((!data[1, lim[0].X, lim[0].Y].hit && !data[1, lim[0].X, lim[0].Y].occupied) || (data[1, lim[0].X, lim[0].Y].hit && data[1, lim[0].X, lim[0].Y].occupied))) { lim[3].Y++; if (lim[3].Y == gridSize) break; }

//Cell targeting AI

} return new Point(rnd.Next(10), rnd.Next(10)); }

Ex

Step by Step Solution

There are 3 Steps involved in it

Step: 1

blur-text-image

Get Instant Access to Expert-Tailored Solutions

See step-by-step solutions with expert insights and AI powered tools for academic success

Step: 2

blur-text-image

Step: 3

blur-text-image

Ace Your Homework with AI

Get the answers you need in no time with our AI-driven, step-by-step assistance

Get Started

Recommended Textbook for

More Books

Students also viewed these Databases questions