-
Notifications
You must be signed in to change notification settings - Fork 4
Writing a Computer Player
This wiki pages describes how you can build a computer player.
Your computer player needs to be in the NBattleshipCodingContest.Players project.
A computer player needs to derive from PlayerBase
. Particularly important is its GetShot
method that you have to implement. Let's look at a simple example (from RandomShots.cs) that just does random shots:
using NBattleshipCodingContest.Logic;
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
/// <summary>
/// Implements a battleship player that randomly shoots at squares
/// </summary>
public class RandomShots : PlayerBase
{
/// <inheritdoc />
public override async Task GetShot(Guid _, string __, IReadOnlyBoard ___, Shoot shoot)
{
// Return a random shot between A1 and J10
var rand = new Random();
await shoot(new BoardIndex(rand.Next(10), rand.Next(10)));
}
}
GetShot
receives the following parameters:
Parameter | Description |
---|---|
gameId |
Unique identifier of the current game. Use it to identify all the shots that belong to a single game. Simple players will probably ignore this parameter. |
board |
Current board content (see also IReadOnlyBoard ). |
shoot |
Callback with which the method has to do the shooting. Pass it the location where you want to shoot. The result it the shot's result (water or hit). |
Here is a more sophisticated sample with a player that does not shoot twice on the same square (from SmartRandomShots.cs):
using NBattleshipCodingContest.Logic;
using System;
using System.Threading.Tasks;
/// <summary>
/// Implements a battleship player that randomly shoots at squares
/// </summary>
/// <remarks>
/// This player is smarter than <see cref="RandomShots"/> because it does not
/// shoot on squares that have already been shot at.
/// </remarks>
public class SmartRandomShots : PlayerBase
{
/// <inheritdoc />
public override async Task GetShot(Guid _, IReadOnlyBoard board, Shoot shoot)
{
var rand = new Random();
while (true)
{
var ix = new BoardIndex(rand.Next(10), rand.Next(10));
if (board[ix] == SquareContent.Unknown)
{
await shoot(ix);
break;
}
}
}
}
The PlayerBase
class offers a property LastShot
which contains the last shot of the player. The following example demonstrates how to use it by implementing a player that shoots sequentially one square after another:
/// <summary>
/// Implements a battleship player that shoots at one cell after the other
/// </summary>
public class SequentialWithLastShot : PlayerBase
{
/// <inheritdoc />
public override async Task GetShot(Guid gameId, IReadOnlyBoard board, Shoot shoot)
{
BoardIndex ix;
if (LastShot == null)
{
// Player did not shoot before
ix = new BoardIndex();
}
else
{
// Player did shoot before. Take location of last shot and increment it by one.
ix = (BoardIndex)LastShot;
ix++;
}
// Shoot at current square
await shoot(ix);
}
}
The system creates a new instance of the player for each shot. Therefore, we need to store state in a static dictionary and access the appropriate state using the game's ID.
Players do not run in parallel. Therefore, it is not necessary to synchronize access to the dictionary with locking.
Here is an example for a player with state. It stores the last shot in a static dictionary instead of using the built-in LastShot
property (see above).
/// <summary>
/// Implements a battleship player that shoots at one cell after the other
/// </summary>
public class SequentialWithState : PlayerBase
{
// The system create a new instance of the player for each shot. Therefore,
// we need to store state in a static dictionary. Players do not run in parallel.
// Therefore, it is not necessary to synchronize access to the dictionary with locking.
private static readonly Dictionary<Guid, BoardIndex> indexes = new Dictionary<Guid, BoardIndex>();
/// <inheritdoc />
public override async Task GetShot(Guid gameId, IReadOnlyBoard board, Shoot shoot)
{
if (!indexes.TryGetValue(gameId, out BoardIndex ix))
{
ix = indexes[gameId] = new BoardIndex();
}
// Shoot at first current square
await shoot(ix);
indexes[gameId]++;
}
}
- Trying to find out the location of the ships by using reflection is useless. Games run in separate processes where the location of the ships is not known. You would have to access process of a separate process (or in some cases even Docker containers). Good luck with that ;-)