C# stack push call issue - c#

I'm having an issue where my C# stack will accept a pushed value but then overwrite the previously existing elements in the stack with the new value as well.
Here's a chunk of the constructor for reference:
public class MazNav //handles processing and navigation
{
private Maze m;
private static Stack<int[]> path;
private int[] currCell;
private int visCell;
private Random rng;
//constructor
public MazNav(Maze mz)
{
m = mz; //assigns this object to a maze
path = new Stack<int[]>(); //initialize the stack
currCell = m.getStart(); //starts the pathfinding at start cell
visCell = 1; //initializes the visited cells count
path.Push(currCell); //adds this cell to the stack
rng = new Random(); //initializes the randomizer
The problem occurs in the if block towards the end of this method (sorry it's still ugly; I'm in the middle of debugging and will clean this up once I have something that works :) ):
public void buildMaze()
{
//variables to represent the current cell
int[] currPos; //coordinates
int nextDir = 0; //direction towards next cell
//variables to represent the next cell
int[] nextPos; //coordinates
int backDir = 0; //holds direction towards previous cell
bool deadEnd = false; //flags true when a backtrack is required
bool outOfBounds = false; //flags true when a move would leave the array
while (visCell < m.getTotCells()) //while the number of visited cells is less than the total cells
{
if (path.Count > 0) // if there is something in the stack
{
currPos = path.Peek(); //check the current coordinates
nextPos = currPos; //movement will happen one cell at a time; setting next cell coordinates the same as current allows easy adjustment
nextDir = findNextUnv(currPos); //find the direction of the next cell to check
deadEnd = false;
outOfBounds = false;
switch (nextDir)
{
case 0: //North
if (nextPos[0] - 1 >= 0)
{
nextPos[0]--;
backDir = 2;
}
else
{
outOfBounds = true;
}
break;
case 1: //East
if (nextPos[1] + 1 < m.getCols())
{
nextPos[1]++;
backDir = 3;
}
else
{
outOfBounds = true;
}
break;
case 2: //South
if(nextPos[0] + 1 < m.getRows())
{
nextPos[0]++;
backDir = 0;
}
else
{
outOfBounds = true;
}
break;
case 3: //West
if (nextPos[1] - 1 >= 0)
{
nextPos[1]--;
backDir = 1;
}
else
{
outOfBounds = true;
}
break;
case 99: //dead end
try
{
deadEnd = true;
path.Pop();
currPos = path.Peek();
int diff;
if (currPos[0] == nextPos[0])
{
diff = currPos[1] - nextPos[1];
if (diff == -1)
{
backDir = 3;
}
else if (diff == 1)
{
backDir = 1;
}
}
else if (currPos[1] == nextPos[1])
{
diff = currPos[0] - nextPos[0];
if (diff == -1)
{
backDir = 2;
}
else if (diff == 1)
{
backDir = 0;
}
}
m.getCell(nextPos[0], nextPos[1]).setBck(backDir, true);
}
catch (Exception) { }
break;
}
if (!deadEnd && !outOfBounds)
{
m.getCell(currPos[0], currPos[1]).setWal(nextDir, false);
m.getCell(nextPos[0], nextPos[1]).setWal(backDir, false);
path.Push(nextPos);
visCell++;
}
}
}
}e
The push call is only executed once but when I watch it on the debugger, after that line runs, the count increases by 1 but every element in the stack is now identical. Has anyone come across this behavior before? Where am I going wrong?

There are a lot of bugs in your code because of the way you are treating nextPos and curPos.
In particular, this line:
nextPos = currPos;
Here you are assigning nextPos to be the same array as curPos, so if you modify one, the other one will be modified along with it. And if you push one on to the stack and then modify either one, the array on the stack will be modified along with it.
The solution I would suggest is to use an immutable data type for your positions, rather than an array (which isn't particularly well suited for coordinates anyway):
internal struct Point
{
internal int X { get; private set; }
internal int Y { get; private set; }
internal Point(int x, int y)
{
X = x;
Y = y;
}
internal Point NewX(int deltaX)
{
return new Point(X + deltaX, Y);
}
internal Point NewY(int deltaY)
{
return new Point(X, Y + deltaY);
}
}
This way, when you need to transition to a new point, you can create a new one instead of modifying an existing one:
if (nextPos.X - 1 >= 0)
{
nextPos = nextPos.NewX(-1);
backDir = 2;
}
This should help you to keep a handle on your values and keep them from overwriting each other every which way.

Related

Array value is set to zero after the whole process generated by user

I want to create a minigame which select random buttons from an array of game objects and store the value in an array. After the first step is completed the user need to tap on the same buttons or he will lose. The problem is when I want to compare the values from this two arrays, every value from index 0-2 is set to 0 in both arrays. I tried to debug the adding part and that works fine, even in my editor I can see the stored values. Here is a photo:
storedobjectsEditor. I even put two for loops to check the values from array in the game, instead of getting 3 prints I get 6 of them, first 3 represent the values right and the other 3 have value = 0 (this apply to both arrays). In my CheckWin() the result will be always true because the values which are compared there are 0 for every position from both arrays. I can't figure it out what force the arrays to have all components set to zero in that method. Here is the script:
public class Script : MonoBehaviour
{
[SerializeField] private GameObject[] buttons;
[SerializeField] private int[] culoriINT;
[SerializeField] private int[] culoriComparareINT;
int index = 0;
int index2 = 0;
private bool win = false;
private void Start()
{
StartCoroutine(ChangeColors2());
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
for (int i = 0; i < culoriINT.Length; i++)
{
Debug.Log("INT vector[" + i + "]: " + culoriINT[i]);
}
}
if (Input.GetKeyDown(KeyCode.W))
{
for (int i = 0; i < culoriComparareINT.Length; i++)
{
Debug.Log("Al doilea vector[" + i + "]: " + culoriComparareINT[i]);
}
}
}
IEnumerator ChangeColors2()
{
yield return new WaitForSeconds(1f);
for (int j = 0; j < buttons.Length; j++)
{
var randomBtn = UnityEngine.Random.Range(0, buttons.Length);
buttons[randomBtn].GetComponent<Image>().color = Color.green;
var introducereIndex = buttons[randomBtn].GetComponent<IndexButtons>().index;
culoriINT[index] = introducereIndex;
Debug.Log($"Index adaugat {introducereIndex} total {culoriINT.Length}");
index++;
yield return new WaitForSeconds(0.5f); //seteaza coloare alb pe acelas buton in cazu in care nimereste acelas sa se vada
buttons[randomBtn].GetComponent<Image>().color = Color.white;
Debug.Log("verde");
yield return new WaitForSeconds(1f);
}
}
public void OnButtonClick()
{
index2++;
}
public void numePeClick()
{
if (index2 < buttons.Length)
{
string a = EventSystem.current.currentSelectedGameObject.name;
culoriComparareINT[index2] = Convert.ToInt32(a);
Debug.Log($"Index adaugat {Convert.ToInt32(a)} total {culoriComparareINT.Length}");
}
else
{
Debug.Log("Array plin");
}
}
public void CheckWin()
{
win = true;
for (var i = 0; i < culoriINT.Length; i++)
{
if (culoriINT[i] != culoriComparareINT[i])
{
win = false;
break;
}
else
{
win = true;
}
}
if (win)
{
Debug.Log("Ai castigat!");
}
else
{
Debug.Log("Ai pierdut!");
}
}
}
It sounds like you have two instances of the same Script component in your scene. One of them has the correct values 3,2,3, the other one has the wrong values 0,0,0. This is why six values get printed to the console instead of three with a single input.

Unity crashes as the program goes through WaitForSeconds the last time

i have a strange problem. My Code works fine. Everything works as it should until it goes the last time through a foreach loop. The code:
public List<string> waveInput = new List<string>(); //die Eingabe der Welle als Zeichenfolge
public List<GameObject> enemyTyps = new List<GameObject>();
public List<Vector3> path = new List<Vector3>();
public GameObject tempPathStart;
public int currentWave = 0;
public int currentAmountOfEnemies;
private int spawnAmountOfEnemies;
private double spawnDelay;
private List<GameObject> enemyToSpawn = new List<GameObject>();
void Start() {
path.Add(tempPathStart.transform.position);
StartCoroutine(function());
}
IEnumerator function() {
while(waveInput.Capacity >= currentWave) {
if(waveInput[currentWave] == "" && currentAmountOfEnemies <= 0) {
currentWave++;
enemyToSpawn.Clear();
spawnAmountOfEnemies = 0;
} else if(currentAmountOfEnemies <= 0) {
string _substring = waveInput[currentWave].Substring(0, waveInput[currentWave].IndexOf(";") + 1);
ManageSubstring(_substring);
for(int i = 0; i < spawnAmountOfEnemies; i++) {
foreach(GameObject element in enemyToSpawn) {
this.SpawnEnemy(element);
yield return new WaitForSeconds((float)spawnDelay);
}
}
}
}
}
void ManageSubstring(string _substring) {
string _tempStringAmount = "";
string _tempStringDelay = "";
string _tempStringType = "";
bool _switchAmountDelay = false;
for(int i = 0; i < _substring.Length; i++) {
char c = _substring[i];
if(c >= '0' && c <= '9') {
if(_switchAmountDelay) {
_tempStringDelay += c;
} else {
_tempStringAmount += c;
}
} else if(c == ';') {
} else if(c == '.') {
_tempStringDelay += c;
} else {
_switchAmountDelay = true;
_tempStringType += c;
}
}
spawnDelay = double.Parse(_tempStringDelay);
spawnAmountOfEnemies = int.Parse(_tempStringAmount);
foreach(char c in _tempStringType) { //die Buchstaben in GameObjekte / Gegner umwandeln
int _tempConvertedInt = TranslateStringToInt(c);
enemyToSpawn.Add(enemyTyps[_tempConvertedInt]);
}
}
int TranslateStringToInt(char pToConvertChar) {
List<char> _alphabet = new List<char>() {'a','b','c','d','e','f','g','h','i','j','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
return _alphabet.IndexOf(pToConvertChar);
}
void SpawnEnemy(GameObject prefab) {
currentAmountOfEnemies++;
GameObject e = Instantiate(prefab) as GameObject;
e.transform.position = path[0];
}
the very strange thing as i already said is: the code works, even the line works fine until it goes through the last time. Then Unity crashes and i have no idea what i should do. If some more code is needed for context just say.
Thanks for every answer!
I just pulled your script into the debugger and stepped through the last iteration:
When the iteration is over, it goes back into the while() loop which encloses the whole body of function().
The while condition remains true since waveInput.Capacity is greater than the currentWave (still zero).
The if(waveInput[currentWave] == "" && currentAmountOfEnemies <= 0) is false since the waveInput[] array has an element zero which is not "", and the enemycount was not reset yet.
the else if(currentAmountOfEnemies <= 0) condition is also false since the current amount was not reset.
nothing more in the while-loop, nothing chagned, you have an endless-loop, which never hits a yield return. This locks up the Update() cycle of Unity and only allow killing.

Trying to select from a list the first previous eligible index

I have a List<List<Object>> in which one of these objects has the property isCurrentlySelected = true. This nested list represents a grid of these objects that might be constructed in any configuration (i.e. the grid may be any dimensions, and may even be jagged).
Now, some of these objects have the property isSelectable = false, so I'm trying to create a method which takes a single parameter for "direction" and returns the first eligible index in the specified direction from the one currently marked as selected.
So far I've only been able to do this using several nested for loops and if-else statements, and even then, it will only work for one direction. I'm wondering if there's a more elegant way to check for the first eligible element, preferably using a single method.
Thanks in advance for any help,
~KWiP
EDIT: with code sample
So I have the Menu class:
public class Menu
{
public int x;
public int y;
public int width;
public int height;
public int lineHeight;
public float menuTimer = 0.0f;
public bool menuKeysPressed = false;
public List<List<Selection>> selections = new List<List<Selection>>();
public int selectedList = 0;
public int selectedItem = 0;
in which is the child class Selection:
public class Selection
{
public int x;
public int y;
public bool isCurrentlySelected = false;
public bool isSelectable = true;
public Color selectedColor = Color.White;
public Color unselectedColor = Color.Gray;
public bool isNonText = false;
public string displayText = "";
public int textSize;
public Selection(int xPos, int yPos, string text, int size, bool selectable)
{
x = xPos;
y = yPos;
displayText = text;
textSize = size;
if (!selectable)
{
isCurrentlySelected = false;
}
}
}
Each Selection in a Menu object has X and Y coordinates in the form of its indices in the nested lists found in Menu.selections. In each Menu, there is typically exactly one Selection with its isCurrentlySelected property set to true.
public Menu(int xPos, int yPos, int wdth, int hght, int lists = 0, int items = 0, bool allSelectable = true)
{
x = xPos;
y = yPos;
width = wdth;
height = hght;
if (items > 0 && lists <= 0)
{
lists = 1;
}
for (int i = 0; i < lists; i++)
{
selections.Add(new List<Selection>());
for (int j = 0; j < items; j++)
{
selections.ElementAt(i).Add(new Selection(((wdth / items) * j) + xPos, ((hght / lists) * i) + yPos, "", 20, allSelectable));
}
}
if (items > 0)
{
selections.ElementAt(0).ElementAt(0).isCurrentlySelected = true;
}
}
Now, within the Menu class, I'm trying to make a method which will deselect the currently selected index, and then select the next closest eligible index in a particular direction, wrapping around to the other side if the end of the range of indices is reached. Unfortunately, all I've been able to come up with is this mess, which currently only works going North, and would need to be expanded to roughly 4x its size to accommodate all directions.
public void nav(Point currentSelected, int dir) // The 4 cardinal directions are represented by an int: 0 for North and continuing clockwise from there.
{
int newRow = currentSelected.Y;
int newIndex = currentSelected.X;
switch (dir)
{
case 0: // Operations to select next eligible N index.
if (this.selections.Count <= 1)
{
return;
}
else
{
int firstOpenRow = this.selections.Count;
for (int i = 0; i < this.selections.Count; i++)
{
int difference = i - currentSelected.Y;
if (difference > 0 && difference < firstOpenRow && this.selections.ElementAt(i).ElementAt(currentSelected.X).isSelectable == true)
{
firstOpenRow = i;
}
}
if (firstOpenRow == this.selections.Count)
{
for (int i = 0; i < this.selections.Count; i++)
{
int difference = i - currentSelected.Y;
if (difference < 0 && difference < firstOpenRow && this.selections.ElementAt(i).ElementAt(currentSelected.X).isSelectable == true)
{
firstOpenRow = i;
}
}
if (firstOpenRow == this.selections.Count)
{
firstOpenRow = currentSelected.Y;
}
}
this.selections.ElementAt(currentSelected.Y).ElementAt(currentSelected.X).isCurrentlySelected = false;
this.selections.ElementAt(firstOpenRow).ElementAt(currentSelected.X).isCurrentlySelected = true;
}
break;
case 1:
// Add operations for E here
break;
case 2:
// Add operations for S here
break;
case 3:
// Add operations for W here
break;
}
}
Your choice of data-structure isn't really well made for the kind of queries you need to make. Maybe you should consider investing a little more time into creating a linked grid. Something like:
class Item
{
Item North { get; private set; }
Item South { get; private set; }
Item West { get; private set; }
Item East { get; private set; }
}
Like that, if you look for the next 'left' item, you can just go West until you either reach null or you encounter an item that is selectable.
Otherwise, it makes sense to distinguish between the different kinds of directions. Just because you have less code doesn't mean your program runs faster. Think of your algorithm in terms of how many operations you have to make to reach your goal, instead of how long it is.
Sorry if that's not the answer your're looking for.

Mixed managed C++ method does not always return the same result to the calling C# code

A C++ method returns the correct value when I use a conditional breakpoint, but an incorrect value without a breakpoint.
C# method which calls C++:
bool SetProperty(Element element, Node referencePoint, List<Materializer> materializers, List<ulong> properties)
{
// Loop over STLs
for (int i = 0; i < materializers.Count; i++)
{
Materializer materializer = materializers[i];
if (materializer.IsPointInside(referencePoint.X, referencePoint.Y, referencePoint.Z, pentalTreeDatasets[i].top))
{
element.PropertyId = properties[i];
return true;
};
}
return false;
}
C++ methods in the header file:
int CountIntersects(double x, double y, double z, PentalTreeNode ^root)
{
Math3d::M3d rayPoints[2], intersectionPoint;
rayPoints[0].set(x,y,z);
rayPoints[1].set(x,y,1.0e6);
if(!root)
return 0;
else
{
int special = CountIntersects(x,y,z,root->special);
if (x <= root->xMax && x >= root->xMin && y <= root->yMax && y >= root->yMin)
{
if( _stlMesh->IsRayIntersectsPoly(root->index, rayPoints, intersectionPoint))
{
return (1 + special);
}
else
return special;
}
else
{
if (y>root->yMax)
{
return (CountIntersects(x,y,z,root->top)+special);
}
else if(y<root->yMin)
{
return (CountIntersects(x,y,z,root->bottom)+special);
}
else if(x<root->xMin)
{
return (CountIntersects(x,y,z,root->left)+special);
}
else if(x>root->xMax)
{
return (CountIntersects(x,y,z,root->right)+special);
}
else
return special;
}
}
}
bool IsPointInside(double x, double y, double z, PentalTreeNode ^root)
{
int intersectionCount = 0;
Math3d::M3d rayPoints[2], intersectionPoint;
rayPoints[0].set(x,y,z);
rayPoints[1].set(x,y,1.0e6);
if(_box->IsContainingPoint(x,y,z))
{
intersectionCount=CountIntersects(x,y,z,root);
return (intersectionCount%2!=0);
}
}
C++ methods in other header files:
bool IsRayIntersectsPoly(int nPolygonIndex, Math3d::M3d RayPoints[2], CVector3D& IntersectionPoint)
{
CMeshPolygonBase& Poly = m_PolygonArray[nPolygonIndex];
CArrayResultI Result;
int* pPolygonPoints = GetPolygonPoints(Poly, Result);
Math3d::MPlane TrianglePlane;
double Atmp[3], A;
CVector3D* pPoints[3];
pPoints[0] = &m_PointArray[*pPolygonPoints].m_Position;
for(int i = 1; i < Result.GetSize() - 1; i++)
{
pPoints[1] = &m_PointArray[*(pPolygonPoints+i)].m_Position;
pPoints[2] = &m_PointArray[*(pPolygonPoints+i+1)].m_Position;
TrianglePlane.Init(*pPoints[0], *pPoints[1], *pPoints[2]);
TrianglePlane.IntersectLine(RayPoints[0], RayPoints[1], IntersectionPoint);
A = GetTriangleArea(*pPoints[0], *pPoints[1], *pPoints[2]);
for(int j = 0; j < 3; j++)
{
Atmp[j] = GetTriangleArea(*pPoints[j], *pPoints[(j+1)%3], IntersectionPoint);
}
if( fabs(A - Atmp[0] - Atmp[1] - Atmp[2]) < 1.0e-5 ) return true;
}
return false;
};
double GetTriangleArea(CVector3D& T1, CVector3D& T2, CVector3D& T3)
{
double a, b, c, s;
a = (T1 - T2).length();
b = (T2 - T3).length();
c = (T3 - T1).length();
s = 0.5 * (a + b + c);
return( sqrt(s * (s - a)* (s - b)* (s - c)) );
}
When I start the program which calls SetProperty() within the for-loop, the results for some iterator values are wrong. When I set conditional breakpoints for critical iterator values in the for-loop and step over it, then the result is OK for that item. What may be the problem?
This is method in which I post breakpoint. For example, for critical element.Id==2393.
private void StartButton_Click(object sender, EventArgs e)
{
DateTime startTime = DateTime.Now;
List<Materializer> materializers = new List<Materializer>();
List<ulong> properties = new List<ulong>();
// Load STLs
for (int i = 0; (int)i < (this.dataGridView.RowCount - 1); i++)
{
if (dataGridView.Rows[i].Cells[1].Value != null && (string)dataGridView.Rows[i].Cells[1].Value != "")
{
Materializer materializer = new Materializer();
materializer.LoadSTLMesh(dataGridView.Rows[i].Cells[0].Value.ToString());
materializers.Add(materializer);
properties.Add((ulong)dataGridView.Rows[i].Cells[1].Tag);
}
}
CreatePentalTrees(materializers);
int processedElementCount = 0;
int changedElementCount = 0;
// Loop over elements
foreach (Element element in model.ElementsList.Values)
if ((element.Topology == 7 || element.Topology == 8) && !lockedProperties.ContainsKey(element.PropertyId)) // 3D elements only
{
Node center = this.CenterPoint(element, model.NodesList);
if (element.Id == 2393)
{
//if breakpoints thats ok, else not ok
Console.WriteLine(element.Id);
Console.WriteLine(element.PropertyId);
}
if (SetProperty(element, center, materializers, properties)) // Check for center point
{
//changedElements.Add(element.Id, true);
changedElementCount++;
}
else
{
// Check for all nodes if center point does not belong to any STL
int[] nodeOrder;
switch (element.Topology)
{
case 7:
nodeOrder = wedgeNodeOrder;
break;
case 8:
nodeOrder = brickNodeOrder;
break;
default:
throw new Exception("Unknown topology " + element.Topology.ToString());
}
for (int i = 0; i < nodeOrder.Length; i++)
{
Node node = model.NodesList[element.NodeIds[nodeOrder[i]]];
if (SetProperty(element, node, materializers, properties))
{
//changedElements.Add(element.Id, true);
changedElementCount++;
break;
}
}
}
if (++processedElementCount % 100 == 0)
{
labelTime.Text = "Changed/processed elements: " + changedElementCount.ToString() + "/" + processedElementCount.ToString();
labelTime.Refresh();
Application.DoEvents();
}
}
DateTime endTime = DateTime.Now;
labelTime.Text = "Total time: " + (endTime - startTime).TotalSeconds.ToString() + " s";
MessageBox.Show("Completed.");
SaveFileDialog saveFileDlg = new SaveFileDialog();
saveFileDlg.Title = "Save FEMAP neutral file";
saveFileDlg.Filter = "(*.neu)|*.neu";
if (saveFileDlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
FemapNeutral.ExportNeu(saveFileDlg.FileName, model);
}
}
You seem to be calling a lot of methods you haven't listed, and/or the wall of code made me get lost. Adding that code won't help: reducing your problem to a simpler one that demonstrates the problem might.
However, the most likely cause of your problem, if you have unmanaged code reading managed data, is that you failed to marshal or pin the data prior to using the managed code.
Unpinned data can be moved around by the garbage collector in unexpected ways.

What is the best Battleship AI?

Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
Battleship!
Back in 2003 (when I was 17), I competed in a Battleship AI coding competition. Even though I lost that tournament, I had a lot of fun and learned a lot from it.
Now, I would like to resurrect this competition, in the search of the best battleship AI.
Here is the framework, now hosted on Bitbucket.
The winner will be awarded +450 reputation! The competition will be held starting on the 17th of November, 2009. No entries or edits later than zero-hour on the 17th will be accepted. (Central Standard Time)
Submit your entries early, so you don't miss your opportunity!
To keep this OBJECTIVE, please follow the spirit of the competition.
Rules of the game:
The game is be played on a 10x10 grid.
Each competitor will place each of 5 ships (of lengths 2, 3, 3, 4, 5) on their grid.
No ships may overlap, but they may be adjacent.
The competitors then take turns firing single shots at their opponent.
A variation on the game allows firing multiple shots per volley, one for each surviving ship.
The opponent will notify the competitor if the shot sinks, hits, or misses.
Game play ends when all of the ships of any one player are sunk.
Rules of the competition:
The spirit of the competition is to find the best Battleship algorithm.
Anything that is deemed against the spirit of the competition will be grounds for disqualification.
Interfering with an opponent is against the spirit of the competition.
Multithreading may be used under the following restrictions:
No more than one thread may be running while it is not your turn. (Though, any number of threads may be in a "Suspended" state).
No thread may run at a priority other than "Normal".
Given the above two restrictions, you will be guaranteed at least 3 dedicated CPU cores during your turn.
A limit of 1 second of CPU time per game is allotted to each competitor on the primary thread.
Running out of time results in losing the current game.
Any unhandled exception will result in losing the current game.
Network access and disk access is allowed, but you may find the time restrictions fairly prohibitive. However, a few set-up and tear-down methods have been added to alleviate the time strain.
Code should be posted on stack overflow as an answer, or, if too large, linked.
Max total size (un-compressed) of an entry is 1 MB.
Officially, .Net 2.0 / 3.5 is the only framework requirement.
Your entry must implement the IBattleshipOpponent interface.
Scoring:
Best 51 games out of 101 games is the winner of a match.
All competitors will play matched against each other, round-robin style.
The best half of the competitors will then play a double-elimination tournament to determine the winner. (Smallest power of two that is greater than or equal to half, actually.)
I will be using the TournamentApi framework for the tournament.
The results will be posted here.
If you submit more than one entry, only your best-scoring entry is eligible for the double-elim.
Good luck! Have fun!
EDIT 1:
Thanks to Freed, who has found an error in the Ship.IsValid function. It has been fixed. Please download the updated version of the framework.
EDIT 2:
Since there has been significant interest in persisting stats to disk and such, I have added a few non-timed set-up and tear-down events that should provide the required functionality. This is a semi-breaking change. That is to say: the interface has been modified to add functions, but no body is required for them. Please download the updated version of the framework.
EDIT 3:
Bug Fix 1: GameWon and GameLost were only getting called in the case of a time out.
Bug Fix 2: If an engine was timing out every game, the competition would never end.
Please download the updated version of the framework.
EDIT 4:
Tournament Results:
I second the motion to do a lot more games per match. Doing 50 games is just flipping a coin. I needed to do 1000 games to get any reasonable distinction between test algorithms.
Download Dreadnought 1.2.
Strategies:
keep track of all possible positions for ships that have >0 hits. The list never gets bigger than ~30K so it can be kept exactly, unlike the list of all possible positions for all ships (which is very large).
The GetShot algorithm has two parts, one which generates random shots and the other which
tries to finish sinking an already hit ship. We do random shots if there is a possible position (from the list above) in which all hit ships are sunk. Otherwise, we try to finish sinking a ship by picking a location to shoot at which eliminates the most possible positions (weighted).
For random shots, compute best location to shoot based on the likelihood of one of the unsunk ships overlapping the location.
adaptive algorithm which places ships in locations where the opponent is statistically less likely to shoot.
adaptive algorithm which prefers to shoot at locations where the opponent is statistically more likely to place his ships.
place ships mostly not touching each other.
Here is my entry! (The most naive solution possible)
"Random 1.1"
namespace Battleship
{
using System;
using System.Collections.ObjectModel;
using System.Drawing;
public class RandomOpponent : IBattleshipOpponent
{
public string Name { get { return "Random"; } }
public Version Version { get { return this.version; } }
Random rand = new Random();
Version version = new Version(1, 1);
Size gameSize;
public void NewGame(Size size, TimeSpan timeSpan)
{
this.gameSize = size;
}
public void PlaceShips(ReadOnlyCollection<Ship> ships)
{
foreach (Ship s in ships)
{
s.Place(
new Point(
rand.Next(this.gameSize.Width),
rand.Next(this.gameSize.Height)),
(ShipOrientation)rand.Next(2));
}
}
public Point GetShot()
{
return new Point(
rand.Next(this.gameSize.Width),
rand.Next(this.gameSize.Height));
}
public void NewMatch(string opponent) { }
public void OpponentShot(Point shot) { }
public void ShotHit(Point shot, bool sunk) { }
public void ShotMiss(Point shot) { }
public void GameWon() { }
public void GameLost() { }
public void MatchOver() { }
}
}
Here's an opponent for people to play against:
http://natekohl.net/files/FarnsworthOpponent.cs
Instead of using a fixed geometry-inspired strategy, I thought it would be interesting to attempt to estimate the underlying probabilities that any particular unexplored space holds a ship.
To do this right, you'd explore all possible configurations of ships that fit your current view of the world, and then compute probabilities based on those configurations. You could think of it like exploring a tree:
an expansion of possible battleship states http://natekohl.net/media/battleship-tree.png
After considering all leaves of that tree that jive with what you know about the world (e.g. ships can't overlap, all hit squares must be ships, etc.) you can count how often ships occur at each unexplored position to estimate the likelihood that a ship is sitting there.
This can be visualized as a heat map, where hot spots are more likely to contain ships:
a heat map of probabilities for each unexplored position http://natekohl.net/media/battleship-probs.png
One thing I like about this Battleship competition is that the tree above is almost small enough to brute-force this kind of algorithm. If there are ~150 possible positions for each of the 5 ships, that's 1505 = 75 billion possibilities. And that number only gets smaller, especially if you can eliminate whole ships.
The opponent that I linked to above doesn't explore the whole tree; 75 billion is still to big to get in under a second. It does attempt to estimate these probabilities, though, with the help of a few heuristics.
Not a fully fledged answer but there seems little point cluttering the real answers with code that is common.
I thus present some extensions/general classes in the spirit of open source.
If you use these then please change the namespace or trying to compile everything into one dll isn't going to work.
BoardView lets you easily work with an annotated board.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
namespace Battleship.ShuggyCoUk
{
public enum Compass
{
North,East,South,West
}
class Cell<T>
{
private readonly BoardView<T> view;
public readonly int X;
public readonly int Y;
public T Data;
public double Bias { get; set; }
public Cell(BoardView<T> view, int x, int y)
{
this.view = view; this.X = x; this.Y = y; this.Bias = 1.0;
}
public Point Location
{
get { return new Point(X, Y); }
}
public IEnumerable<U> FoldAll<U>(U acc, Func<Cell<T>, U, U> trip)
{
return new[] { Compass.North, Compass.East, Compass.South, Compass.West }
.Select(x => FoldLine(x, acc, trip));
}
public U FoldLine<U>(Compass direction, U acc, Func<Cell<T>, U, U> trip)
{
var cell = this;
while (true)
{
switch (direction)
{
case Compass.North:
cell = cell.North; break;
case Compass.East:
cell = cell.East; break;
case Compass.South:
cell = cell.South; break;
case Compass.West:
cell = cell.West; break;
}
if (cell == null)
return acc;
acc = trip(cell, acc);
}
}
public Cell<T> North
{
get { return view.SafeLookup(X, Y - 1); }
}
public Cell<T> South
{
get { return view.SafeLookup(X, Y + 1); }
}
public Cell<T> East
{
get { return view.SafeLookup(X+1, Y); }
}
public Cell<T> West
{
get { return view.SafeLookup(X-1, Y); }
}
public IEnumerable<Cell<T>> Neighbours()
{
if (North != null)
yield return North;
if (South != null)
yield return South;
if (East != null)
yield return East;
if (West != null)
yield return West;
}
}
class BoardView<T> : IEnumerable<Cell<T>>
{
public readonly Size Size;
private readonly int Columns;
private readonly int Rows;
private Cell<T>[] history;
public BoardView(Size size)
{
this.Size = size;
Columns = size.Width;
Rows = size.Height;
this.history = new Cell<T>[Columns * Rows];
for (int y = 0; y < Rows; y++)
{
for (int x = 0; x < Rows; x++)
history[x + y * Columns] = new Cell<T>(this, x, y);
}
}
public T this[int x, int y]
{
get { return history[x + y * Columns].Data; }
set { history[x + y * Columns].Data = value; }
}
public T this[Point p]
{
get { return history[SafeCalc(p.X, p.Y, true)].Data; }
set { this.history[SafeCalc(p.X, p.Y, true)].Data = value; }
}
private int SafeCalc(int x, int y, bool throwIfIllegal)
{
if (x < 0 || y < 0 || x >= Columns || y >= Rows)
{ if (throwIfIllegal)
throw new ArgumentOutOfRangeException("["+x+","+y+"]");
else
return -1;
}
return x + y * Columns;
}
public void Set(T data)
{
foreach (var cell in this.history)
cell.Data = data;
}
public Cell<T> SafeLookup(int x, int y)
{
int index = SafeCalc(x, y, false);
if (index < 0)
return null;
return history[index];
}
#region IEnumerable<Cell<T>> Members
public IEnumerator<Cell<T>> GetEnumerator()
{
foreach (var cell in this.history)
yield return cell;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public BoardView<U> Transform<U>(Func<T, U> transform)
{
var result = new BoardView<U>(new Size(Columns, Rows));
for (int y = 0; y < Rows; y++)
{
for (int x = 0; x < Columns; x++)
{
result[x,y] = transform(this[x, y]);
}
}
return result;
}
public void WriteAsGrid(TextWriter w)
{
WriteAsGrid(w, "{0}");
}
public void WriteAsGrid(TextWriter w, string format)
{
WriteAsGrid(w, x => string.Format(format, x.Data));
}
public void WriteAsGrid(TextWriter w, Func<Cell<T>,string> perCell)
{
for (int y = 0; y < Rows; y++)
{
for (int x = 0; x < Columns; x++)
{
if (x != 0)
w.Write(",");
w.Write(perCell(this.SafeLookup(x, y)));
}
w.WriteLine();
}
}
#endregion
}
}
Some extensions, some of this duplicates functionality in the main framework but should really be done by you.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Collections.ObjectModel;
namespace Battleship.ShuggyCoUk
{
public static class Extensions
{
public static bool IsIn(this Point p, Size size)
{
return p.X >= 0 && p.Y >= 0 && p.X < size.Width && p.Y < size.Height;
}
public static bool IsLegal(this Ship ship,
IEnumerable<Ship> ships,
Size board,
Point location,
ShipOrientation direction)
{
var temp = new Ship(ship.Length);
temp.Place(location, direction);
if (!temp.GetAllLocations().All(p => p.IsIn(board)))
return false;
return ships.Where(s => s.IsPlaced).All(s => !s.ConflictsWith(temp));
}
public static bool IsTouching(this Point a, Point b)
{
return (a.X == b.X - 1 || a.X == b.X + 1) &&
(a.Y == b.Y - 1 || a.Y == b.Y + 1);
}
public static bool IsTouching(this Ship ship,
IEnumerable<Ship> ships,
Point location,
ShipOrientation direction)
{
var temp = new Ship(ship.Length);
temp.Place(location, direction);
var occupied = new HashSet<Point>(ships
.Where(s => s.IsPlaced)
.SelectMany(s => s.GetAllLocations()));
if (temp.GetAllLocations().Any(p => occupied.Any(b => b.IsTouching(p))))
return true;
return false;
}
public static ReadOnlyCollection<Ship> MakeShips(params int[] lengths)
{
return new System.Collections.ObjectModel.ReadOnlyCollection<Ship>(
lengths.Select(l => new Ship(l)).ToList());
}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Rand rand)
{
T[] elements = source.ToArray();
// Note i > 0 to avoid final pointless iteration
for (int i = elements.Length - 1; i > 0; i--)
{
// Swap element "i" with a random earlier element it (or itself)
int swapIndex = rand.Next(i + 1);
T tmp = elements[i];
elements[i] = elements[swapIndex];
elements[swapIndex] = tmp;
}
// Lazily yield (avoiding aliasing issues etc)
foreach (T element in elements)
{
yield return element;
}
}
public static T RandomOrDefault<T>(this IEnumerable<T> things, Rand rand)
{
int count = things.Count();
if (count == 0)
return default(T);
return things.ElementAt(rand.Next(count));
}
}
}
Something I end up using a lot.
enum OpponentsBoardState
{
Unknown = 0,
Miss,
MustBeEmpty,
Hit,
}
Randomization.
Secure but testable, useful for testing.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace Battleship.ShuggyCoUk
{
public class Rand
{
Random r;
public Rand()
{
var rand = System.Security.Cryptography.RandomNumberGenerator.Create();
byte[] b = new byte[4];
rand.GetBytes(b);
r = new Random(BitConverter.ToInt32(b, 0));
}
public int Next(int maxValue)
{
return r.Next(maxValue);
}
public double NextDouble(double maxValue)
{
return r.NextDouble() * maxValue;
}
public T Pick<T>(IEnumerable<T> things)
{
return things.ElementAt(Next(things.Count()));
}
public T PickBias<T>(Func<T, double> bias, IEnumerable<T> things)
{
double d = NextDouble(things.Sum(x => bias(x)));
foreach (var x in things)
{
if (d < bias(x))
return x;
d -= bias(x);
}
throw new InvalidOperationException("fell off the end!");
}
}
}
I don't have the time right now to write a full-fledged algorithm, but here's a thought: if your opponent placed ships randomly, wouldn't the placement probabilities be a simple distribution centered at (5.5,5.5)? For example, the placement possibilities for the battleship (5 units long) in the x dimension are here:
x 1 2 3 4 5 6 7 8 9 10
P(x) 2 4 6 8 10 10 8 6 4 2
The same calculations would be valid for y. The other ships would not have as steep of distributions, but your best guess is still the center. After that, the mathematical approach would be slowly radiating diagonals (perhaps with the length of the average ship, 17/5) out of the center. Ex:
...........
....x.x....
.....x.....
....x.x....
...........
Obviously some randomness would need to be added to the idea, but I think that purely mathematically that's the way to go.
Nothing that sophisticated but heres what I came up with. It beats the random opponent 99.9% of the time. Would be interested if anyone has any other little challenges like this, it was good fun.
namespace Battleship
{
using System;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
public class AgentSmith : IBattleshipOpponent
{
public string Name { get { return "Agent Smith"; } }
public Version Version { get { return this.version; } }
private Random rand = new Random();
private Version version = new Version(2, 1);
private Size gameSize;
private enum Direction { Up, Down, Left, Right }
private int MissCount;
private Point?[] EndPoints = new Point?[2];
private LinkedList<Point> HitShots = new LinkedList<Point>();
private LinkedList<Point> Shots = new LinkedList<Point>();
private List<Point> PatternShots = new List<Point>();
private Direction ShotDirection = Direction.Up;
private void NullOutTarget()
{
EndPoints = new Point?[2];
MissCount = 0;
}
private void SetupPattern()
{
for (int y = 0; y < gameSize.Height; y++)
for (int x = 0; x < gameSize.Width; x++)
if ((x + y) % 2 == 0) PatternShots.Add(new Point(x, y));
}
private bool InvalidShot(Point p)
{
bool InvalidShot = (Shots.Where(s => s.X == p.X && s.Y == p.Y).Any());
if (p.X < 0 | p.Y<0) InvalidShot = true;
if (p.X >= gameSize.Width | p.Y >= gameSize.Height) InvalidShot = true;
return InvalidShot;
}
private Point FireDirectedShot(Direction? direction, Point p)
{
ShotDirection = (Direction)direction;
switch (ShotDirection)
{
case Direction.Up: p.Y--; break;
case Direction.Down: p.Y++; break;
case Direction.Left: p.X--; break;
case Direction.Right: p.X++; break;
}
return p;
}
private Point FireAroundPoint(Point p)
{
if (!InvalidShot(FireDirectedShot(ShotDirection,p)))
return FireDirectedShot(ShotDirection, p);
Point testShot = FireDirectedShot(Direction.Left, p);
if (InvalidShot(testShot)) { testShot = FireDirectedShot(Direction.Right, p); }
if (InvalidShot(testShot)) { testShot = FireDirectedShot(Direction.Up, p); }
if (InvalidShot(testShot)) { testShot = FireDirectedShot(Direction.Down, p); }
return testShot;
}
private Point FireRandomShot()
{
Point p;
do
{
if (PatternShots.Count > 0)
PatternShots.Remove(p = PatternShots[rand.Next(PatternShots.Count)]);
else do
{
p = FireAroundPoint(HitShots.First());
if (InvalidShot(p)) HitShots.RemoveFirst();
} while (InvalidShot(p) & HitShots.Count > 0);
}
while (InvalidShot(p));
return p;
}
private Point FireTargettedShot()
{
Point p;
do
{
p = FireAroundPoint(new Point(EndPoints[1].Value.X, EndPoints[1].Value.Y));
if (InvalidShot(p) & EndPoints[1] != EndPoints[0])
EndPoints[1] = EndPoints[0];
else if (InvalidShot(p)) NullOutTarget();
} while (InvalidShot(p) & EndPoints[1] != null);
if (InvalidShot(p)) p = FireRandomShot();
return p;
}
private void ResetVars()
{
Shots.Clear();
HitShots.Clear();
PatternShots.Clear();
MissCount = 0;
}
public void NewGame(Size size, TimeSpan timeSpan)
{
gameSize = size;
ResetVars();
SetupPattern();
}
public void PlaceShips(ReadOnlyCollection<Ship> ships)
{
foreach (Ship s in ships)
s.Place(new Point(rand.Next(this.gameSize.Width), rand.Next(this.gameSize.Height)), (ShipOrientation)rand.Next(2));
}
public Point GetShot()
{
if (EndPoints[1] != null) Shots.AddLast(FireTargettedShot());
else Shots.AddLast(FireRandomShot());
return Shots.Last();
}
public void ShotHit(Point shot, bool sunk)
{
HitShots.AddLast(shot);
MissCount = 0;
EndPoints[1] = shot;
if (EndPoints[0] == null) EndPoints[0] = shot;
if (sunk) NullOutTarget();
}
public void ShotMiss(Point shot)
{
if (++MissCount == 6) NullOutTarget();
}
public void GameWon() { }
public void GameLost() { }
public void NewMatch(string opponent) { }
public void OpponentShot(Point shot) { }
public void MatchOver() { }
}
}
Slightly condensed to take up minimal space on here and still be readable.
Some comments about the Competition Engine:
NewGame parameters:
If IBattleshipOpponent::NewGame is intended for pre-game setup and takes a boardsize, it should also take a list of ships and their respective sizes. It makes no sense to allow for variable board-size without allowing for variable ship configurations.
Ships are sealed:
I don't see any reason why class Ship is sealed. Among other basic things, I would like Ships to have a Name, so I can output messages like ("You sunk my {0}", ship.Name);. I have other extensions in mind too, so I think Ship should be inheritable.
Time Limits:
While the time limit of 1 second makes sense for a tournament rule, it totally messes with debugging. BattleshipCompetition should have an easy setting to ignore time-violations to aid with development/debugging. I would also suggest investigating System.Diagnostics.Process::UserProcessorTime / Privileged ProcessorTime / TotalProcessorTime for a more accurate view of how much time is being used.
Sunk Ships:
The current API informs you when you've sunk an oppenent's ship:
ShotHit(Point shot, bool sunk);
but not which ship you sunk! I consider it part of the human-Battleship rules that you are required to declare "You sunk my Battleship!" (or destroyer, or sub, etc).
This is especially critical when an AI is trying to flush out ships that butt-up against each other. I'd like to request an API change to:
ShotHit(Point shot, Ship ship);
If ship is non-null, it implies that the shot was a sinking-shot, and you know which ship you sunk, and how long it was. If the shot was a non-sinking shot, then ship is null, and you have no further information.
I'm not going to be able to participate, but here's the algorithm I'd implement if I had time:
First, when I detect a hit I do not pursue the rest of the ship immediately - I build a table of ship locations and figure out whether I've hit all five at least once before starting to fully sink them. (Note that this is a bad policy for the multiple shot variant - see comments)
Hit the center (see final note below - 'center' is just a convenience for description)
Hit the spot 4 to the right of the center
Hit the spot 1 down and one to the right of the center
Hit the spot four to the right of the previous hit
Continue in that pattern (should end up with diagonal lines separated by 3 spaces filling the board) This should hit all 4 and 5 length boats, and a statistically large number of 3 and 2 boats.
Start randomly hitting spots inbetween the diagonals, this will catch the 2 and 3 length boats that haven't already been noticed.
Once I have detected 5 hits, I'd determine if the 5 hits are on separate boats. This is relatively easy by making a few more shots near locations where two hits are on the same horizontal or vertical line and are within 5 locations of each other (might be two hits on the same boat). If they are separate boats then continue to sink all the ships. If they are found to be the same boat, continue the filling patterns above until all 5 boats are located.
This algorithm is a simple filling algorithm. The key features are that it does not waste time sinking ships it knows about when there are still ships it's unaware of, and it doesn't use an inefficient filling pattern (ie, a fully random pattern would be wasteful).
Final notes:
A) "Center" is a random starting point on the board. This eliminates the primary weakness of this algorithm.
B) While the description indicates drawing diagonals immediately from the start, ideally the algorithm merely shoots at 'random' locations that are along those diagonals. This helps prevent the competitor from timing how long until their ships are hit by predictable patterns.
This describes a 'perfect' algorithm in that it'll get all the ships in under (9x9)/2+10 shots.
However, it can be improved significantly:
Once a ship is hit, identify its size before doing the 'internal' diagonal lines. You may have found the 2 ship, in which case the internal diagonals can be simplified to find the 3 size ships more quickly.
Identify stages in the game and act accordingly. This algorithm may be good up to a certain point in the game, but other algorithms may yield better benefits as part of the endgame. Also, if the other player is very close to defeating you, another algorithm might work better - for instance a high risk algorithm might fail more often, but when it works it works quickly and you may beat your opponent who is closer to winning than you.
Identify the play style of the competitor - it may give you clues as to how they plan ship placement (ie, chances are good that their own algorithm most quickly identifies how they place their own ships - if the only tool you have is a hammer, everything looks like a nail)
-Adam
CrossFire updated.
I know it can't compete with Farnsworth or Dreadnought but it is a lot faster than the latter and simple to play with in case anyone wants to try.
This relies on the current state of my libraries,included here to make it easy to use.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
using System.Collections.ObjectModel;
namespace Battleship.ShuggyCoUk
{
public class Simple : IBattleshipOpponent
{
BoardView<OpponentsBoardState> opponentsBoard = new BoardView<OpponentsBoardState>(new Size(10,10));
Rand rand = new Rand();
int gridOddEven;
Size size;
public string Name { get { return "Simple"; } }
public Version Version { get { return new Version(2, 1); }}
public void NewMatch(string opponent) {}
public void NewGame(System.Drawing.Size size, TimeSpan timeSpan)
{
this.size = size;
this.opponentsBoard = new BoardView<OpponentsBoardState>(size);
this.gridOddEven = rand.Pick(new[] { 0, 1 });
}
public void PlaceShips(System.Collections.ObjectModel.ReadOnlyCollection<Ship> ships)
{
BoardView<bool> board = new BoardView<bool>(size);
var AllOrientations = new[] {
ShipOrientation.Horizontal,
ShipOrientation.Vertical };
foreach (var ship in ships)
{
int avoidTouching = 3;
while (!ship.IsPlaced)
{
var l = rand.Pick(board.Select(c => c.Location));
var o = rand.Pick(AllOrientations);
if (ship.IsLegal(ships, size, l, o))
{
if (ship.IsTouching(ships, l, o)&& --avoidTouching > 0)
continue;
ship.Place(l, o);
}
}
}
}
protected virtual Point PickWhenNoTargets()
{
return rand.PickBias(x => x.Bias,
opponentsBoard
// nothing 1 in size
.Where(c => (c.Location.X + c.Location.Y) % 2 == gridOddEven)
.Where(c => c.Data == OpponentsBoardState.Unknown))
.Location;
}
private int SumLine(Cell<OpponentsBoardState> c, int acc)
{
if (acc >= 0)
return acc;
if (c.Data == OpponentsBoardState.Hit)
return acc - 1;
return -acc;
}
public System.Drawing.Point GetShot()
{
var targets = opponentsBoard
.Where(c => c.Data == OpponentsBoardState.Hit)
.SelectMany(c => c.Neighbours())
.Where(c => c.Data == OpponentsBoardState.Unknown)
.ToList();
if (targets.Count > 1)
{
var lines = targets.Where(
x => x.FoldAll(-1, SumLine).Select(r => Math.Abs(r) - 1).Max() > 1).ToList();
if (lines.Count > 0)
targets = lines;
}
var target = targets.RandomOrDefault(rand);
if (target == null)
return PickWhenNoTargets();
return target.Location;
}
public void OpponentShot(System.Drawing.Point shot)
{
}
public void ShotHit(Point shot, bool sunk)
{
opponentsBoard[shot] = OpponentsBoardState.Hit;
Debug(shot, sunk);
}
public void ShotMiss(Point shot)
{
opponentsBoard[shot] = OpponentsBoardState.Miss;
Debug(shot, false);
}
public const bool DebugEnabled = false;
public void Debug(Point shot, bool sunk)
{
if (!DebugEnabled)
return;
opponentsBoard.WriteAsGrid(
Console.Out,
x =>
{
string t;
switch (x.Data)
{
case OpponentsBoardState.Unknown:
return " ";
case OpponentsBoardState.Miss:
t = "m";
break;
case OpponentsBoardState.MustBeEmpty:
t = "/";
break;
case OpponentsBoardState.Hit:
t = "x";
break;
default:
t = "?";
break;
}
if (x.Location == shot)
t = t.ToUpper();
return t;
});
if (sunk)
Console.WriteLine("sunk!");
Console.ReadLine();
}
public void GameWon()
{
}
public void GameLost()
{
}
public void MatchOver()
{
}
#region Library code
enum OpponentsBoardState
{
Unknown = 0,
Miss,
MustBeEmpty,
Hit,
}
public enum Compass
{
North, East, South, West
}
class Cell<T>
{
private readonly BoardView<T> view;
public readonly int X;
public readonly int Y;
public T Data;
public double Bias { get; set; }
public Cell(BoardView<T> view, int x, int y)
{
this.view = view; this.X = x; this.Y = y; this.Bias = 1.0;
}
public Point Location
{
get { return new Point(X, Y); }
}
public IEnumerable<U> FoldAll<U>(U acc, Func<Cell<T>, U, U> trip)
{
return new[] { Compass.North, Compass.East, Compass.South, Compass.West }
.Select(x => FoldLine(x, acc, trip));
}
public U FoldLine<U>(Compass direction, U acc, Func<Cell<T>, U, U> trip)
{
var cell = this;
while (true)
{
switch (direction)
{
case Compass.North:
cell = cell.North; break;
case Compass.East:
cell = cell.East; break;
case Compass.South:
cell = cell.South; break;
case Compass.West:
cell = cell.West; break;
}
if (cell == null)
return acc;
acc = trip(cell, acc);
}
}
public Cell<T> North
{
get { return view.SafeLookup(X, Y - 1); }
}
public Cell<T> South
{
get { return view.SafeLookup(X, Y + 1); }
}
public Cell<T> East
{
get { return view.SafeLookup(X + 1, Y); }
}
public Cell<T> West
{
get { return view.SafeLookup(X - 1, Y); }
}
public IEnumerable<Cell<T>> Neighbours()
{
if (North != null)
yield return North;
if (South != null)
yield return South;
if (East != null)
yield return East;
if (West != null)
yield return West;
}
}
class BoardView<T> : IEnumerable<Cell<T>>
{
public readonly Size Size;
private readonly int Columns;
private readonly int Rows;
private Cell<T>[] history;
public BoardView(Size size)
{
this.Size = size;
Columns = size.Width;
Rows = size.Height;
this.history = new Cell<T>[Columns * Rows];
for (int y = 0; y < Rows; y++)
{
for (int x = 0; x < Rows; x++)
history[x + y * Columns] = new Cell<T>(this, x, y);
}
}
public T this[int x, int y]
{
get { return history[x + y * Columns].Data; }
set { history[x + y * Columns].Data = value; }
}
public T this[Point p]
{
get { return history[SafeCalc(p.X, p.Y, true)].Data; }
set { this.history[SafeCalc(p.X, p.Y, true)].Data = value; }
}
private int SafeCalc(int x, int y, bool throwIfIllegal)
{
if (x < 0 || y < 0 || x >= Columns || y >= Rows)
{
if (throwIfIllegal)
throw new ArgumentOutOfRangeException("[" + x + "," + y + "]");
else
return -1;
}
return x + y * Columns;
}
public void Set(T data)
{
foreach (var cell in this.history)
cell.Data = data;
}
public Cell<T> SafeLookup(int x, int y)
{
int index = SafeCalc(x, y, false);
if (index < 0)
return null;
return history[index];
}
#region IEnumerable<Cell<T>> Members
public IEnumerator<Cell<T>> GetEnumerator()
{
foreach (var cell in this.history)
yield return cell;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public BoardView<U> Transform<U>(Func<T, U> transform)
{
var result = new BoardView<U>(new Size(Columns, Rows));
for (int y = 0; y < Rows; y++)
{
for (int x = 0; x < Columns; x++)
{
result[x, y] = transform(this[x, y]);
}
}
return result;
}
public void WriteAsGrid(TextWriter w)
{
WriteAsGrid(w, "{0}");
}
public void WriteAsGrid(TextWriter w, string format)
{
WriteAsGrid(w, x => string.Format(format, x.Data));
}
public void WriteAsGrid(TextWriter w, Func<Cell<T>, string> perCell)
{
for (int y = 0; y < Rows; y++)
{
for (int x = 0; x < Columns; x++)
{
if (x != 0)
w.Write(",");
w.Write(perCell(this.SafeLookup(x, y)));
}
w.WriteLine();
}
}
#endregion
}
public class Rand
{
Random r;
public Rand()
{
var rand = System.Security.Cryptography.RandomNumberGenerator.Create();
byte[] b = new byte[4];
rand.GetBytes(b);
r = new Random(BitConverter.ToInt32(b, 0));
}
public int Next(int maxValue)
{
return r.Next(maxValue);
}
public double NextDouble(double maxValue)
{
return r.NextDouble() * maxValue;
}
public T Pick<T>(IEnumerable<T> things)
{
return things.ElementAt(Next(things.Count()));
}
public T PickBias<T>(Func<T, double> bias, IEnumerable<T> things)
{
double d = NextDouble(things.Sum(x => bias(x)));
foreach (var x in things)
{
if (d < bias(x))
return x;
d -= bias(x);
}
throw new InvalidOperationException("fell off the end!");
}
}
#endregion
}
public static class Extensions
{
public static bool IsIn(this Point p, Size size)
{
return p.X >= 0 && p.Y >= 0 && p.X < size.Width && p.Y < size.Height;
}
public static bool IsLegal(this Ship ship,
IEnumerable<Ship> ships,
Size board,
Point location,
ShipOrientation direction)
{
var temp = new Ship(ship.Length);
temp.Place(location, direction);
if (!temp.GetAllLocations().All(p => p.IsIn(board)))
return false;
return ships.Where(s => s.IsPlaced).All(s => !s.ConflictsWith(temp));
}
public static bool IsTouching(this Point a, Point b)
{
return (a.X == b.X - 1 || a.X == b.X + 1) &&
(a.Y == b.Y - 1 || a.Y == b.Y + 1);
}
public static bool IsTouching(this Ship ship,
IEnumerable<Ship> ships,
Point location,
ShipOrientation direction)
{
var temp = new Ship(ship.Length);
temp.Place(location, direction);
var occupied = new HashSet<Point>(ships
.Where(s => s.IsPlaced)
.SelectMany(s => s.GetAllLocations()));
if (temp.GetAllLocations().Any(p => occupied.Any(b => b.IsTouching(p))))
return true;
return false;
}
public static ReadOnlyCollection<Ship> MakeShips(params int[] lengths)
{
return new System.Collections.ObjectModel.ReadOnlyCollection<Ship>(
lengths.Select(l => new Ship(l)).ToList());
}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Battleship.ShuggyCoUk.Simple.Rand rand)
{
T[] elements = source.ToArray();
// Note i > 0 to avoid final pointless iteration
for (int i = elements.Length - 1; i > 0; i--)
{
// Swap element "i" with a random earlier element it (or itself)
int swapIndex = rand.Next(i + 1);
T tmp = elements[i];
elements[i] = elements[swapIndex];
elements[swapIndex] = tmp;
}
// Lazily yield (avoiding aliasing issues etc)
foreach (T element in elements)
{
yield return element;
}
}
public static T RandomOrDefault<T>(this IEnumerable<T> things, Battleship.ShuggyCoUk.Simple.Rand rand)
{
int count = things.Count();
if (count == 0)
return default(T);
return things.ElementAt(rand.Next(count));
}
}
}
This is about the best that I could put together in my free time, which is about non-existent. There is some game and match tallying stats going on, as I set up the main function to loop and continuously run the BattleshipCompetition until I pressed a key.
namespace Battleship
{
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
public class BP7 : IBattleshipOpponent
{
public string Name { get { return "BP7"; } }
public Version Version { get { return this.version; } }
Random rand = new Random();
Version version = new Version(0, 7);
Size gameSize;
List<Point> scanShots;
List<NextShot> nextShots;
int wins, losses;
int totalWins = 0;
int totalLosses = 0;
int maxWins = 0;
int maxLosses = 0;
int matchWins = 0;
int matchLosses = 0;
public enum Direction { VERTICAL = -1, UNKNOWN = 0, HORIZONTAL = 1 };
Direction hitDirection, lastShotDirection;
enum ShotResult { UNKNOWN, MISS, HIT };
ShotResult[,] board;
public struct NextShot
{
public Point point;
public Direction direction;
public NextShot(Point p, Direction d)
{
point = p;
direction = d;
}
}
public struct ScanShot
{
public Point point;
public int openSpaces;
public ScanShot(Point p, int o)
{
point = p;
openSpaces = o;
}
}
public void NewGame(Size size, TimeSpan timeSpan)
{
this.gameSize = size;
scanShots = new List<Point>();
nextShots = new List<NextShot>();
fillScanShots();
hitDirection = Direction.UNKNOWN;
board = new ShotResult[size.Width, size.Height];
}
private void fillScanShots()
{
int x;
for (x = 0; x < gameSize.Width - 1; x++)
{
scanShots.Add(new Point(x, x));
}
if (gameSize.Width == 10)
{
for (x = 0; x < 3; x++)
{
scanShots.Add(new Point(9 - x, x));
scanShots.Add(new Point(x, 9 - x));
}
}
}
public void PlaceShips(System.Collections.ObjectModel.ReadOnlyCollection<Ship> ships)
{
foreach (Ship s in ships)
{
s.Place(
new Point(
rand.Next(this.gameSize.Width),
rand.Next(this.gameSize.Height)),
(ShipOrientation)rand.Next(2));
}
}
public Point GetShot()
{
Point shot;
if (this.nextShots.Count > 0)
{
if (hitDirection != Direction.UNKNOWN)
{
if (hitDirection == Direction.HORIZONTAL)
{
this.nextShots = this.nextShots.OrderByDescending(x => x.direction).ToList();
}
else
{
this.nextShots = this.nextShots.OrderBy(x => x.direction).ToList();
}
}
shot = this.nextShots.First().point;
lastShotDirection = this.nextShots.First().direction;
this.nextShots.RemoveAt(0);
return shot;
}
List<ScanShot> scanShots = new List<ScanShot>();
for (int x = 0; x < gameSize.Width; x++)
{
for (int y = 0; y < gameSize.Height; y++)
{
if (board[x, y] == ShotResult.UNKNOWN)
{
scanShots.Add(new ScanShot(new Point(x, y), OpenSpaces(x, y)));
}
}
}
scanShots = scanShots.OrderByDescending(x => x.openSpaces).ToList();
int maxOpenSpaces = scanShots.FirstOrDefault().openSpaces;
List<ScanShot> scanShots2 = new List<ScanShot>();
scanShots2 = scanShots.Where(x => x.openSpaces == maxOpenSpaces).ToList();
shot = scanShots2[rand.Next(scanShots2.Count())].point;
return shot;
}
int OpenSpaces(int x, int y)
{
int ctr = 0;
Point p;
// spaces to the left
p = new Point(x - 1, y);
while (p.X >= 0 && board[p.X, p.Y] == ShotResult.UNKNOWN)
{
ctr++;
p.X--;
}
// spaces to the right
p = new Point(x + 1, y);
while (p.X < gameSize.Width && board[p.X, p.Y] == ShotResult.UNKNOWN)
{
ctr++;
p.X++;
}
// spaces to the top
p = new Point(x, y - 1);
while (p.Y >= 0 && board[p.X, p.Y] == ShotResult.UNKNOWN)
{
ctr++;
p.Y--;
}
// spaces to the bottom
p = new Point(x, y + 1);
while (p.Y < gameSize.Height && board[p.X, p.Y] == ShotResult.UNKNOWN)
{
ctr++;
p.Y++;
}
return ctr;
}
public void NewMatch(string opponenet)
{
wins = 0;
losses = 0;
}
public void OpponentShot(Point shot) { }
public void ShotHit(Point shot, bool sunk)
{
board[shot.X, shot.Y] = ShotResult.HIT;
if (!sunk)
{
hitDirection = lastShotDirection;
if (shot.X != 0)
{
this.nextShots.Add(new NextShot(new Point(shot.X - 1, shot.Y), Direction.HORIZONTAL));
}
if (shot.Y != 0)
{
this.nextShots.Add(new NextShot(new Point(shot.X, shot.Y - 1), Direction.VERTICAL));
}
if (shot.X != this.gameSize.Width - 1)
{
this.nextShots.Add(new NextShot(new Point(shot.X + 1, shot.Y), Direction.HORIZONTAL));
}
if (shot.Y != this.gameSize.Height - 1)
{
this.nextShots.Add(new NextShot(new Point(shot.X, shot.Y + 1), Direction.VERTICAL));
}
}
else
{
hitDirection = Direction.UNKNOWN;
this.nextShots.Clear(); // so now this works like gangbusters ?!?!?!?!?!?!?!?!?
}
}
public void ShotMiss(Point shot)
{
board[shot.X, shot.Y] = ShotResult.MISS;
}
public void GameWon()
{
wins++;
}
public void GameLost()
{
losses++;
}
public void MatchOver()
{
if (wins > maxWins)
{
maxWins = wins;
}
if (losses > maxLosses)
{
maxLosses = losses;
}
totalWins += wins;
totalLosses += losses;
if (wins >= 51)
{
matchWins++;
}
else
{
matchLosses++;
}
}
public void FinalStats()
{
Console.WriteLine("Games won: " + totalWins.ToString());
Console.WriteLine("Games lost: " + totalLosses.ToString());
Console.WriteLine("Game winning percentage: " + (totalWins * 1.0 / (totalWins + totalLosses)).ToString("P"));
Console.WriteLine("Game losing percentage: " + (totalLosses * 1.0 / (totalWins + totalLosses)).ToString("P"));
Console.WriteLine();
Console.WriteLine("Matches won: " + matchWins.ToString());
Console.WriteLine("Matches lost: " + matchLosses.ToString());
Console.WriteLine("Match winning percentage: " + (matchWins * 1.0 / (matchWins + matchLosses)).ToString("P"));
Console.WriteLine("Match losing percentage: " + (matchLosses * 1.0 / (matchWins + matchLosses)).ToString("P"));
Console.WriteLine("Match games won high: " + maxWins.ToString());
Console.WriteLine("Match games lost high: " + maxLosses.ToString());
Console.WriteLine();
}
}
}
This logic is the closest that I had to beating Dreadnought, winning about 41% of the individual games. (It actually did win one match by a count of 52 to 49.) Oddly enough, this class does not do as well against FarnsworthOpponent as an earlier version that was much less advanced.
My computer is being repaired by dell right now, but this is where i was at last week:
namespace Battleship
{
using System;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
public class BSKiller4 : OpponentExtended, IBattleshipOpponent
{
public string Name { get { return "BSKiller4"; } }
public Version Version { get { return this.version; } }
public bool showBoard = false;
Random rand = new Random();
Version version = new Version(0, 4);
Size gameSize;
List<Point> nextShots;
Queue<Point> scanShots;
char[,] board;
private void printBoard()
{
Console.WriteLine();
for (int y = 0; y < this.gameSize.Height; y++)
{
for (int x = 0; x < this.gameSize.Width; x++)
{
Console.Write(this.board[x, y]);
}
Console.WriteLine();
}
Console.ReadKey();
}
public void NewGame(Size size, TimeSpan timeSpan)
{
this.gameSize = size;
board = new char[size.Width, size.Height];
this.nextShots = new List<Point>();
this.scanShots = new Queue<Point>();
fillScanShots();
initializeBoard();
}
private void initializeBoard()
{
for (int y = 0; y < this.gameSize.Height; y++)
{
for (int x = 0; x < this.gameSize.Width; x++)
{
this.board[x, y] = 'O';
}
}
}
private void fillScanShots()
{
int x, y;
int num = gameSize.Width * gameSize.Height;
for (int j = 0; j < 3; j++)
{
for (int i = j; i < num; i += 3)
{
x = i % gameSize.Width;
y = i / gameSize.Height;
scanShots.Enqueue(new Point(x, y));
}
}
}
public void PlaceShips(ReadOnlyCollection<Ship> ships)
{
foreach (Ship s in ships)
{
s.Place(new Point(
rand.Next(this.gameSize.Width),
rand.Next(this.gameSize.Height)),
(ShipOrientation)rand.Next(2));
}
}
public Point GetShot()
{
if (showBoard) printBoard();
Point shot;
shot = findShotRun();
if (shot.X != -1)
{
return shot;
}
if (this.nextShots.Count > 0)
{
shot = this.nextShots[0];
this.nextShots.RemoveAt(0);
}
else
{
shot = this.scanShots.Dequeue();
}
return shot;
}
public void ShotHit(Point shot, bool sunk)
{
this.board[shot.X, shot.Y] = 'H';
if (!sunk)
{
addToNextShots(new Point(shot.X - 1, shot.Y));
addToNextShots(new Point(shot.X, shot.Y + 1));
addToNextShots(new Point(shot.X + 1, shot.Y));
addToNextShots(new Point(shot.X, shot.Y - 1));
}
else
{
this.nextShots.Clear();
}
}
private Point findShotRun()
{
int run_forward_horizontal = 0;
int run_backward_horizontal = 0;
int run_forward_vertical = 0;
int run_backward_vertical = 0;
List<shotPossibilities> possible = new List<shotPossibilities>(5);
// this only works if width = height for the board;
for (int y = 0; y < this.gameSize.Height; y++)
{
for (int x = 0; x < this.gameSize.Width; x++)
{
// forward horiz
if (this.board[x, y] == 'M')
{
run_forward_horizontal = 0;
}
else if (this.board[x, y] == 'O')
{
if (run_forward_horizontal >= 2)
{
possible.Add(
new shotPossibilities(
run_forward_horizontal,
new Point(x, y),
true));
}
else
{
run_forward_horizontal = 0;
}
}
else
{
run_forward_horizontal++;
}
// forward vertical
if (this.board[y, x] == 'M')
{
run_forward_vertical = 0;
}
else if (this.board[y, x] == 'O')
{
if (run_forward_vertical >= 2)
{
possible.Add(
new shotPossibilities(
run_forward_vertical,
new Point(y, x),
false));
}
else
{
run_forward_vertical = 0;
}
}
else
{
run_forward_vertical++;
}
// backward horiz
if (this.board[this.gameSize.Width - x - 1, y] == 'M')
{
run_backward_horizontal = 0;
}
else if (this.board[this.gameSize.Width - x - 1, y] == 'O')
{
if (run_backward_horizontal >= 2)
{
possible.Add(
new shotPossibilities(
run_backward_horizontal,
new Point(this.gameSize.Width - x - 1, y),
true));
}
else
{
run_backward_horizontal = 0;
}
}
else
{
run_backward_horizontal++;
}
// backward vertical
if (this.board[y, this.gameSize.Height - x - 1] == 'M')
{
run_backward_vertical = 0;
}
else if (this.board[y, this.gameSize.Height - x - 1] == 'O')
{
if (run_backward_vertical >= 2)
{
possible.Add(
new shotPossibilities(
run_backward_vertical,
new Point(y, this.gameSize.Height - x - 1),
false));
}
else
{
run_backward_vertical = 0;
}
}
else
{
run_backward_vertical++;
}
}
run_forward_horizontal = 0;
run_backward_horizontal = 0;
run_forward_vertical = 0;
run_backward_vertical = 0;
}
Point shot;
if (possible.Count > 0)
{
shotPossibilities shotp = possible.OrderByDescending(a => a.run).First();
//this.nextShots.Clear();
shot = shotp.shot;
//if (shotp.isHorizontal)
//{
// this.nextShots.RemoveAll(p => p.X != shot.X);
//}
//else
//{
// this.nextShots.RemoveAll(p => p.Y != shot.Y);
//}
}
else
{
shot = new Point(-1, -1);
}
return shot;
}
private void addToNextShots(Point p)
{
if (!this.nextShots.Contains(p) &&
p.X >= 0 &&
p.X < this.gameSize.Width &&
p.Y >= 0 &&
p.Y < this.gameSize.Height)
{
if (this.board[p.X, p.Y] == 'O')
{
this.nextShots.Add(p);
}
}
}
public void GameWon()
{
this.GameWins++;
}
public void NewMatch(string opponent)
{
System.Threading.Thread.Sleep(5);
this.rand = new Random(System.Environment.TickCount);
}
public void OpponentShot(Point shot) { }
public void ShotMiss(Point shot)
{
this.board[shot.X, shot.Y] = 'M';
}
public void GameLost()
{
if (showBoard) Console.WriteLine("-----Game Over-----");
}
public void MatchOver() { }
}
public class OpponentExtended
{
public int GameWins { get; set; }
public int MatchWins { get; set; }
public OpponentExtended() { }
}
public class shotPossibilities
{
public shotPossibilities(int r, Point s, bool h)
{
this.run = r;
this.shot = s;
this.isHorizontal = h;
}
public int run { get; set; }
public Point shot { get; set; }
public bool isHorizontal { get; set; }
}
}
If you are brute forcing your analysis then you may find the mechanics of the supplied RandomOpponent highly inefficient. It allows itself to reselect already targeted locations and lets the framework force it to repeat till it hits one it hasn't touched yet or the timelimit per move expires.
This opponent has similar behaviour (the effective placement distribution is the same) it just does the sanity checking itself and only consumes one random number generation per call (amortized)).
This uses the classes in my extensions/library answer and I only supply the key methods/state.
Shuffle is lifted from Jon Skeet's answer here
class WellBehavedRandomOpponent : IBattleShipOpponent
{
Rand rand = new Rand();
List<Point> guesses;
int nextGuess = 0;
public void PlaceShips(IEnumerable<Ship> ships)
{
BoardView<bool> board = new BoardView<bool>(BoardSize);
var AllOrientations = new[] {
ShipOrientation.Horizontal,
ShipOrientation.Vertical };
foreach (var ship in ships)
{
while (!ship.IsPlaced)
{
var l = rand.Pick(board.Select(c => c.Location));
var o = rand.Pick(AllOrientations);
if (ship.IsLegal(ships, BoardSize, l, o))
ship.Place(l, o);
}
}
}
public void NewGame(Size size, TimeSpan timeSpan)
{
var board = new BoardView<bool>(size);
this.guesses = new List<Point>(
board.Select(x => x.Location).Shuffle(rand));
nextGuess = 0;
}
public System.Drawing.Point GetShot()
{
return guesses[nextGuess++];
}
// empty methods left out
}
My entry.
Nothing terribly special, and I didn't get time to add all the good ideas I had.
But it seems to play fairly well. We'll see how it does in competition:
(put this in file Missouri.cs and added to project.)
using System;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
namespace Battleship
{
// The Empire of Japan surrendered on the deck of the USS Missouri on Sept. 2, 1945
public class USSMissouri : IBattleshipOpponent
{
public String Name { get { return name; } }
public Version Version { get { return ver; } }
#region IBattleship Interface
// IBattleship::NewGame
public void NewGame(Size gameSize, TimeSpan timeSpan)
{
size = gameSize;
shotBoard = new ShotBoard(size);
attackVector = new Stack<Attack>();
}
// IBattleship::PlaceShips
public void PlaceShips(ReadOnlyCollection<Ship> ships)
{
HunterBoard board;
targetBoards = new List<HunterBoard>();
shotBoard = new ShotBoard(size);
foreach (Ship s in ships)
{
board = new HunterBoard(this, size, s);
targetBoards.Add(board);
// REWRITE: to ensure valid board placement.
s.Place(
new Point(
rand.Next(size.Width),
rand.Next(size.Height)),
(ShipOrientation)rand.Next(2));
}
}
// IBattleship::GetShot
public Point GetShot()
{
Point p = new Point();
if (attackVector.Count() > 0)
{
p = ExtendShot();
return p;
}
// Contemplate a shot at every-single point, and measure how effective it would be.
Board potential = new Board(size);
for(p.Y=0; p.Y<size.Height; ++p.Y)
{
for(p.X=0; p.X<size.Width; ++p.X)
{
if (shotBoard.ShotAt(p))
{
potential[p] = 0;
continue;
}
foreach(HunterBoard b in targetBoards)
{
potential[p] += b.GetWeightAt(p);
}
}
}
// Okay, we have the shot potential of the board.
// Lets pick a weighted-random spot.
Point shot;
shot = potential.GetWeightedRandom(rand.NextDouble());
shotBoard[shot] = Shot.Unresolved;
return shot;
}
public Point ExtendShot()
{
// Lets consider North, South, East, and West of the current shot.
// and measure the potential of each
Attack attack = attackVector.Peek();
Board potential = new Board(size);
Point[] points = attack.GetNextTargets();
foreach(Point p in points)
{
if (shotBoard.ShotAt(p))
{
potential[p] = 0;
continue;
}
foreach(HunterBoard b in targetBoards)
{
potential[p] += b.GetWeightAt(p);
}
}
Point shot = potential.GetBestShot();
shotBoard[shot] = Shot.Unresolved;
return shot;
}
// IBattleship::NewMatch
public void NewMatch(string opponent)
{
}
public void OpponentShot(Point shot)
{
}
public void ShotHit(Point shot, bool sunk)
{
shotBoard[shot] = Shot.Hit;
if (!sunk)
{
if (attackVector.Count == 0) // This is a first hit, open an attackVector
{
attackVector.Push(new Attack(this, shot));
}
else
{
attackVector.Peek().AddHit(shot); // Add a hit to our current attack.
}
}
// What if it is sunk? Close the top attack, which we've been pursuing.
if (sunk)
{
if (attackVector.Count > 0)
{
attackVector.Pop();
}
}
}
public void ShotMiss(Point shot)
{
shotBoard[shot] = Shot.Miss;
foreach(HunterBoard b in targetBoards)
{
b.ShotMiss(shot); // Update the potential map.
}
}
public void GameWon()
{
Trace.WriteLine ("I won the game!");
}
public void GameLost()
{
Trace.WriteLine ("I lost the game!");
}
public void MatchOver()
{
Trace.WriteLine("This match is over.");
}
#endregion
public ShotBoard theShotBoard
{
get { return shotBoard; }
}
public Size theBoardSize
{
get { return size; }
}
private Random rand = new Random();
private Version ver = new Version(6, 3); // USS Missouri is BB-63, hence version 6.3
private String name = "USS Missouri (abelenky#alum.mit.edu)";
private Size size;
private List<HunterBoard> targetBoards;
private ShotBoard shotBoard;
private Stack<Attack> attackVector;
}
// An Attack is the data on the ship we are currently working on sinking.
// It consists of a set of points, horizontal and vertical, from a central point.
// And can be extended in any direction.
public class Attack
{
public Attack(USSMissouri root, Point p)
{
Player = root;
hit = p;
horzExtent = new Extent(p.X, p.X);
vertExtent = new Extent(p.Y, p.Y);
}
public Extent HorizontalExtent
{
get { return horzExtent; }
}
public Extent VerticalExtent
{
get { return vertExtent; }
}
public Point FirstHit
{
get { return hit; }
}
public void AddHit(Point p)
{
if (hit.X == p.X) // New hit in the vertical direction
{
vertExtent.Min = Math.Min(vertExtent.Min, p.Y);
vertExtent.Max = Math.Max(vertExtent.Max, p.Y);
}
else if (hit.Y == p.Y)
{
horzExtent.Min = Math.Min(horzExtent.Min, p.X);
horzExtent.Max = Math.Max(horzExtent.Max, p.X);
}
}
public Point[] GetNextTargets()
{
List<Point> bors = new List<Point>();
Point p;
p = new Point(hit.X, vertExtent.Min-1);
while (p.Y >= 0 && Player.theShotBoard[p] == Shot.Hit)
{
if (Player.theShotBoard[p] == Shot.Miss)
{
break; // Don't add p to the List 'bors.
}
--p.Y;
}
if (p.Y >= 0 && Player.theShotBoard[p] == Shot.None) // Add next-target only if there is no shot here yet.
{
bors.Add(p);
}
//-------------------
p = new Point(hit.X, vertExtent.Max+1);
while (p.Y < Player.theBoardSize.Height && Player.theShotBoard[p] == Shot.Hit)
{
if (Player.theShotBoard[p] == Shot.Miss)
{
break; // Don't add p to the List 'bors.
}
++p.Y;
}
if (p.Y < Player.theBoardSize.Height && Player.theShotBoard[p] == Shot.None)
{
bors.Add(p);
}
//-------------------
p = new Point(horzExtent.Min-1, hit.Y);
while (p.X >= 0 && Player.theShotBoard[p] == Shot.Hit)
{
if (Player.theShotBoard[p] == Shot.Miss)
{
break; // Don't add p to the List 'bors.
}
--p.X;
}
if (p.X >= 0 && Player.theShotBoard[p] == Shot.None)
{
bors.Add(p);
}
//-------------------
p = new Point(horzExtent.Max+1, hit.Y);
while (p.X < Player.theBoardSize.Width && Player.theShotBoard[p] == Shot.Hit)
{
if (Player.theShotBoard[p] == Shot.Miss)
{
break; // Don't add p to the List 'bors.
}
++p.X;
}
if (p.X < Player.theBoardSize.Width && Player.theShotBoard[p] == Shot.None)
{
bors.Add(p);
}
return bors.ToArray();
}
private Point hit;
private Extent horzExtent;
private Extent vertExtent;
private USSMissouri Player;
}
public struct Extent
{
public Extent(Int32 min, Int32 max)
{
Min = min;
Max = max;
}
public Int32 Min;
public Int32 Max;
}
public class Board // The potential-Board, which measures the full potential of each square.
{
// A Board is the status of many things.
public Board(Size boardsize)
{
size = boardsize;
grid = new int[size.Width , size.Height];
Array.Clear(grid,0,size.Width*size.Height);
}
public int this[int c,int r]
{
get { return grid[c,r]; }
set { grid[c,r] = value; }
}
public int this[Point p]
{
get { return grid[p.X, p.Y]; }
set { grid[p.X, p.Y] = value; }
}
public Point GetWeightedRandom(double r)
{
Int32 sum = 0;
foreach(Int32 i in grid)
{
sum += i;
}
Int32 index = (Int32)(r*sum);
Int32 x=0, y=0;
for(y=0; y<size.Height; ++y)
{
for(x=0; x<size.Width; ++x)
{
if (grid[x,y] == 0) continue; // Skip any zero-cells
index -= grid[x,y];
if (index < 0) break;
}
if (index < 0) break;
}
if (x == 10 || y == 10)
throw new Exception("WTF");
return new Point(x,y);
}
public Point GetBestShot()
{
int max=grid[0,0];
for(int y=0; y<size.Height; ++y)
{
for (int x=0; x<size.Width; ++x)
{
max = (grid[x,y] > max)? grid[x,y] : max;
}
}
for(int y=0; y<size.Height; ++y)
{
for (int x=0; x<size.Width; ++x)
{
if (grid[x,y] == max)
{
return new Point(x,y);
}
}
}
return new Point(0,0);
}
public bool IsZero()
{
foreach(Int32 p in grid)
{
if (p > 0)
{
return false;
}
}
return true;
}
public override String ToString()
{
String output = "";
String horzDiv = " +----+----+----+----+----+----+----+----+----+----+\n";
String disp;
int x,y;
output += " A B C D E F G H I J \n" + horzDiv;
for(y=0; y<size.Height; ++y)
{
output += String.Format("{0} ", y+1).PadLeft(3);
for(x=0; x<size.Width; ++x)
{
switch(grid[x,y])
{
case (int)Shot.None: disp = ""; break;
case (int)Shot.Hit: disp = "#"; break;
case (int)Shot.Miss: disp = "."; break;
case (int)Shot.Unresolved: disp = "?"; break;
default: disp = "!"; break;
}
output += String.Format("| {0} ", disp.PadLeft(2));
}
output += "|\n" + horzDiv;
}
return output;
}
protected Int32[,] grid;
protected Size size;
}
public class HunterBoard
{
public HunterBoard(USSMissouri root, Size boardsize, Ship target)
{
size = boardsize;
grid = new int[size.Width , size.Height];
Array.Clear(grid,0,size.Width*size.Height);
Player = root;
Target = target;
Initialize();
}
public void Initialize()
{
int x, y, i;
for(y=0; y<size.Height; ++y)
{
for(x=0; x<size.Width - Target.Length+1; ++x)
{
for(i=0; i<Target.Length; ++i)
{
grid[x+i,y]++;
}
}
}
for(y=0; y<size.Height-Target.Length+1; ++y)
{
for(x=0; x<size.Width; ++x)
{
for(i=0; i<Target.Length; ++i)
{
grid[x,y+i]++;
}
}
}
}
public int this[int c,int r]
{
get { return grid[c,r]; }
set { grid[c,r] = value; }
}
public int this[Point p]
{
get { return grid[p.X, p.Y]; }
set { grid[p.X, p.Y] = value; }
}
public void ShotMiss(Point p)
{
int x,y;
int min, max;
min = Math.Max(p.X-Target.Length+1, 0);
max = Math.Min(p.X, size.Width-Target.Length);
for(x=min; x<=max; ++x)
{
DecrementRow(p.Y, x, x+Target.Length-1);
}
min = Math.Max(p.Y-Target.Length+1, 0);
max = Math.Min(p.Y, size.Height-Target.Length);
for(y=min; y<=max; ++y)
{
DecrementColumn(p.X, y, y+Target.Length-1);
}
grid[p.X, p.Y] = 0;
}
public void ShotHit(Point p)
{
}
public override String ToString()
{
String output = String.Format("Target size is {0}\n", Target.Length);
String horzDiv = " +----+----+----+----+----+----+----+----+----+----+\n";
int x,y;
output += " A B C D E F G H I J \n" + horzDiv;
for(y=0; y<size.Height; ++y)
{
output += String.Format("{0} ", y+1).PadLeft(3);
for(x=0; x<size.Width; ++x)
{
output += String.Format("| {0} ", grid[x,y].ToString().PadLeft(2));
}
output += "|\n" + horzDiv;
}
return output;
}
// If we shoot at point P, how does that affect the potential of the board?
public Int32 GetWeightAt(Point p)
{
int x,y;
int potential = 0;
int min, max;
min = Math.Max(p.X-Target.Length+1, 0);
max = Math.Min(p.X, size.Width-Target.Length);
for(x=min; x<=max; ++x)
{
if (Player.theShotBoard.isMissInRow(p.Y, x, x+Target.Length-1) == false)
{
++potential;
}
}
min = Math.Max(p.Y-Target.Length+1, 0);
max = Math.Min(p.Y, size.Height-Target.Length);
for(y=min; y<=max; ++y)
{
if (Player.theShotBoard.isMissInColumn(p.X, y, y+Target.Length-1) == false)
{
++potential;
}
}
return potential;
}
public void DecrementRow(int row, int rangeA, int rangeB)
{
int x;
for(x=rangeA; x<=rangeB; ++x)
{
grid[x,row] = (grid[x,row]==0)? 0 : grid[x,row]-1;
}
}
public void DecrementColumn(int col, int rangeA, int rangeB)
{
int y;
for(y=rangeA; y<=rangeB; ++y)
{
grid[col,y] = (grid[col,y]==0)? 0 : grid[col,y]-1;
}
}
private Ship Target = null;
private USSMissouri Player;
private Int32[,] grid;
private Size size;
}
public enum Shot
{
None = 0,
Hit = 1,
Miss = 2,
Unresolved = 3
};
public class ShotBoard
{
public ShotBoard(Size boardsize)
{
size = boardsize;
grid = new Shot[size.Width , size.Height];
for(int y=0; y<size.Height; ++y)
{
for(int x=0; x<size.Width; ++x)
{
grid[x,y] = Shot.None;
}
}
}
public Shot this[int c,int r]
{
get { return grid[c,r]; }
set { grid[c,r] = value; }
}
public Shot this[Point p]
{
get { return grid[p.X, p.Y]; }
set { grid[p.X, p.Y] = value; }
}
public override String ToString()
{
String output = "";
String horzDiv = " +----+----+----+----+----+----+----+----+----+----+\n";
String disp;
int x,y;
output += " A B C D E F G H I J \n" + horzDiv;
for(y=0; y<size.Height; ++y)
{
output += String.Format("{0} ", y+1).PadLeft(3);
for(x=0; x<size.Width; ++x)
{
switch(grid[x,y])
{
case Shot.None: disp = ""; break;
case Shot.Hit: disp = "#"; break;
case Shot.Miss: disp = "."; break;
case Shot.Unresolved: disp = "?"; break;
default: disp = "!"; break;
}
output += String.Format("| {0} ", disp.PadLeft(2));
}
output += "|\n" + horzDiv;
}
return output;
}
// Functions to find shots on the board, at a specific point, or in a row or column, within a range
public bool ShotAt(Point p)
{
return !(this[p]==Shot.None);
}
public bool isMissInColumn(int col, int rangeA, int rangeB)
{
for(int y=rangeA; y<=rangeB; ++y)
{
if (grid[col,y] == Shot.Miss)
{
return true;
}
}
return false;
}
public bool isMissInRow(int row, int rangeA, int rangeB)
{
for(int x=rangeA; x<=rangeB; ++x)
{
if (grid[x,row] == Shot.Miss)
{
return true;
}
}
return false;
}
protected Shot[,] grid;
protected Size size;
}
}
This is not minimax. Actually after placing the ships, can't each player play on its own, resulting in a number of turns it took him to sink every opponent ship? The one that took less turns wins.
I don't think that there are any good general strategies beyond sinking hit ships and trying to minimize the number of shots to cover the remaining possible places where ships might hide.
Of course there might be counter-strategies for anything that's not random. But I don't think that there are strategies that are good against all possible players.
Actually, I think the biggest problem with the puzzle is that its essentially two moves. One move is placing your ships, the other is finding the enemy ships (however segmented that second part might be, aside from trying to beat a clock with a random factor, its just 'run your algorithm'). There's no mechanism to try to determine and then counter an enemy strategy, which is what makes similar competitions based around successive rounds of "rock paper scissors" pretty interesting.
Also, I think it would be cooler if you specified the game as a network protocol and then provided the framework to implement that protocol in C#, rather than dictate that all solutions should be C#, but that's just my opinion.
EDIT: I rescind my initial point, since I didn't read the competition rules carefully enough.
I always liked starting in the middle and spiraling away from that one point leaving no more than 1 blank space between any other points to account for that goddam sub... the space between shots was dependent on which ships were sunk. if the B-ship was last, the shots only had to leave 4 spaces in between to minimize wasted shots
There was a similar competition run by Dr James Heather of The University of Surrey on behalf of the British Computer Society.
Limitations were placed on resources - namely maximum processor time per turn, no state could be stored between moves, maximum heap size imposed. To limit time the AI could submit a move at any point within the time slot and would be asked for a move upon termination of the turn.
Very interesting - see more at: http://www.bcsstudentcontest.com/
Might give you some more ideas.
As it is, the solution opens and runs with no modification in monodevelop in ubuntu 9.10 linux
You wrote:
Anything that is deemed against the spirit of the competition will be grounds for disqualification.
Interfering with an opponent is against the spirit of the competition.
please define "against the spirit of the competition" and "interfering with an opponent"?
Also - to simplify, I recommend that you:
disallow using CPU at all during opponent's CPU slot.
disallow thread parallelism and instead give more CPU seconds on a single thread. This will simplify programming of AI and won't hurt anyone who is CPU/memory-bound anyway.
PS - a question for the CS post-docs lurking here: isn't this game solvable (i.e. is there a single, best strategy?). yes, the board size and number of steps makes minimax et al mandatory, but still I have to wonder... it's far from Go and chess in complexity.
I predict that the person who manages to reverse engineer their opponents random seed and call pattern will win.
Not sure how likely that is though.
It would also, presumably, be possible to run a series of these with variations on the game.
Adding in things like a 3d plane or being able to move a single ship instead of shoot for a turn would probably change the game a fair bit.
The one second total game time is machine specific. Once second worth of CPU operations will be different on my machine compared to the tournament machine. If I optimize the Battle Ship algorithm to utilize the most CPU time within 1 second, then it is run on a possible slower tournament machine, it will always lose.
I am not sure how to get around this limitation of the framework, but it should be addressed.
...
One idea is to do what was done in this competition http://www.bcsstudentcontest.com/
And have a maximum time per turn as opposed to maximum total game time. This way I could limit the algorithms to fit within a know turn time. A game might last 50 to 600+ turns, if the my algorithm manages its total game time, it might not give the enough time to do its best job or it could give too much time and lose. It is very hard to manage the total game time within the Battleship algorithm.
I would suggest changing the rules to limit the turn time not the total game time.
Edit
If I wrote an algorithm that enumerates all possible shots and then ranks them, then takes the highest ranking shot. It would take too long to generate all possible shots, so I would let the algorithm run for a certain amount of time then stop it.
If there was a turn based limit, I could let the algorithm run for 0.9 seconds and return the highest ranking shot, and be well withing the turn time limit.
If I am limited to total game time of one second, it will be difficult to determine how long the algorithm should run for each turn. I will want to maximum my CPU time. If a game lasted 500 round I could limit each turn to 0.002 seconds, but if a game lasted 100 rounds I could give each turn 0.01 seconds of CPU time.
It would be impractical for a algorithm to use an semi-exhaustive search of the shot space to find the best shot with the current limitation.
The 1 second total game time is limiting the type of algorithms that can be effectively used to compete in the game.
I'm copping out here by not putting actual code in - but I will hazard some general observations:
Since all ships are at least 2 cells in size, you can use an optimization I saw on an implementation of the game in Space Quest V - which only fires at alternate cells in a diamond pattern while it is "seeking" a target. This eliminates half the squares, while still guaranteeing that you will find all the ships eventually.
A random firing pattern when seeking targets will statistically yield the best results over many games.
![Probability Density][1]enter image description her
![enter image description here][2]
I experimented with comparing the results of randon shooting vs a dumb hunt/target and finally a sophisticated search.
The best solution appears to be to create a probability density function for the how likely any individual square is used by the remaining ships, and aim with the square with the highest value.
You can see my results here enter link description here
"Battleship" is what's known as a classic computer science NP-complete problem.
http://en.wikipedia.org/wiki/List_of_NP-complete_problems
(look for Battleship - it's there, under games and puzzles)

Categories

Resources