-
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. |
opponent |
Identifier of the opponent. Use this if you want to adjust your strategy based on the opponent. 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 _, string __, 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 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:
/// <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, string __, 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 ;-)