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.)

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.

This question exists because it has historical significance, but it is not considered a good, on-topic question for this site, so please do not use it as evidence that you can ask similar questions here. This question and its answers are frozen and cannot be changed. More info: help center.

7

Could he offer a bounty if its community wiki? I think not. Altho this is a very interesting application of the SO framework.
–
JustLorenOct 27 '09 at 15:05

5

@JustLoren He can offer a bounty whether or not it's community wiki. He can offer a bounty 2 days after it's open, whether or not it is community wiki.
–
George Stocker♦Oct 27 '09 at 15:33

4

I'm sure i'll get flamed for this, but voted to close as 'not a real question'. It is interesting, clever, creative, and fun - but it's not a programming question in the sense described in the FAQ. 'Thin end of the wedge' and all that, must vote to close.
–
Steven A. LoweOct 27 '09 at 17:22

@[John Gietzen]: Two logical fallacies in the same comment (a) its-popular and (b) appeal-to-authority. To quote Sgt. Friday: "Just the FAQs, ma'am." Jeff also said "Use your own judgement", and questions about ponies often get many upvotes ;-)
–
Steven A. LoweOct 28 '09 at 0:32

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.

on my test machine (a ULV Celeron netbook) this code loses by timeout consistently. When I let it take all the time it wants it whips Simple (roughly 90% success rate). If you are relying heavily on the spec of the machine you're going to be running on to hit you timelimits you might want to give yourself some wiggle room...
–
ShuggyCoUkNov 16 '09 at 10:45

Interesting... It runs fine on the tournament machine. However, a "perfect" engine would adapt to however much time it had already spent.
–
John GietzenNov 16 '09 at 14:18

Congratulations, your engine was undefeated! (And sorry about granting the bounty so early in the day...)
–
John GietzenNov 18 '09 at 3:42

Damnit! who upvoted? If I could down-vote my own answer, I would.
–
John GietzenOct 27 '09 at 15:32

52

Actually, this answer is nice because it shows in a very concise form the API's you'd need to implement to compete... :)
–
dicroceOct 27 '09 at 15:51

2

This could attempt to place overlapping ships couldn't it?
–
user159335Oct 27 '09 at 16:27

6

Yes, but the engine will disallow this. It will then tell the AI to place them again, but this time, with a sterner voice. (Seen by pop ax \ cmp ax, 1 \ je stern)
–
John GietzenOct 27 '09 at 16:54

5

Important note foranyone who, like me, figured they could easily beat this by remembering the previously placed shots and not repeating. The framework will ignore repeats and give you another chance so long as your total time is less than the limit. This is poor in my opinion, if someone messes up their algo they should be penalized...
–
ShuggyCoUkNov 1 '09 at 1:07

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:

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:

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.

Holy, bad-ass! This is exactly the kind of solution I wanted to come up with. ;)
–
John GietzenNov 13 '09 at 3:36

So far, you are beating our only other full solution by about 67.7% to 32.3% :)
–
John GietzenNov 13 '09 at 3:43

2

I'm definitely curious to see how a "probability approach" compares to a "geometric approach". I've noticed that this probability opponent actually makes moves that follow the geometric patterns discussed in other answers. It could be that using geometry is just as good, and a lot faster. :)
–
Nate KohlNov 13 '09 at 3:49

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.

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.

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 always considered it a strategy to place two ships in an L configuration to make my opponent think they had sunk a battleship when in fact they hadn't. I was never under the impression you had to declare which boat was sunk.
–
Josh SmeatonOct 31 '09 at 8:02

3

@DJ: I'm going by the original pen-and-paper rules. Remember that Hasbro is a toy company and that this game predates Hasbro.
–
John GietzenNov 2 '09 at 13:44

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.

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.

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.

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.

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)

Sorry I don't have time to make a better description, or compete, but perhaps this will give others some ideas to work with.
–
Adam DavisNov 2 '09 at 16:22

The strategy of waiting to sink ships until all are found depends heavily on the one-shot-per-turn variation. Under the (number of surviving ships)-shots-per-turn version, it is advantageous to sink ships as quickly as possible so as to slow your opponent.
–
Jason OwenNov 12 '09 at 7:20

Potentially, yes, they could play on their own. That is not how this will be run. Great idea, though. In this competition, I want it to be possible to statistically avoid your opponent's shots.
–
John GietzenOct 27 '09 at 16:39

2

I see. Using data from prior games against the same opponent one might be able to adapt to him?
–
ziggystarOct 27 '09 at 16:42

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.

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

So... I just need to stay away from the middle? :)
–
darronOct 27 '09 at 18:11

14

You also need to stay away from the edges, because an edge hit contains more information for your opponent than a non-edge hit. So you should place all your ships in a non-middle, non-edge region. Unless that's what they're expecting you to do.
–
JhericoOct 27 '09 at 18:44

18

The ship with two holes is not a goddamn sub, it's a goddamn PT boat. The sub has three holes. :)
–
ravenOct 30 '09 at 13:09

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.

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 had reflection in mind when I said "Interfering". I don't want competitors to win because they bit-twiddled another engine to death.
–
John GietzenOct 27 '09 at 15:53

8

I'd suggest that espionage is an important part of modern warfare, so reflecting to find the targets would be ideal -- after all, it was one of the methods used during the second world war...
–
Rowland ShawOct 27 '09 at 16:28

I have a framework for isolating the engines onto different PCs, communicating over TCP/IP, rendering Reflection worthless. However, due to my estimated number of entries, This would make the competition take a prohibitively long time.
–
John GietzenOct 27 '09 at 16:36

6

I didn't know they had Reflection back then!
–
Markus NigburOct 27 '09 at 18:45

The opponents have the option of using a CSPRNG.
–
John GietzenOct 27 '09 at 17:14

Good point, although I admit that reverse engineering such a seed is beyond my expertise anyway. I guess that most vulnerable aspect would be the fire pattern selection algorithm- but even then you wouldn't necessarily gain much from breaking it, as there is no way you could move your ships once the game has started.
–
Triston AttridgeOct 27 '09 at 17:26

When I was applying for research internship, we wrote battleship programs and competed. By setting random seed was exactly how I won X)
–
Pavel ShvedOct 28 '09 at 0:02

1

Assuming a reasonably-simple ship placement algorithm, I would imagine one could, after getting a couple of hits on different ships, start using most of one's turn looping through all possible random seeds (probably starting with somewhere near the current time and moving forwards/backwards one step or so) and seeing which ones generate ship placements compatible with observed hits.
–
DomenicOct 31 '09 at 7:13

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.

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.

This will be run on an Intel Q9550SX quad core, 8 GB ram, Vista 64 machine. Is 1 second going to be a limiting factor?
–
John GietzenNov 1 '09 at 12:43

I guess you should make your battleship AI multithreaded, then, to calculate the max # of shots per that time interval.
–
Jeff Atwood♦Nov 3 '09 at 6:19

the trick is how to limit the turn time interval. If I limit it 0.00005 seconds I am safe from running over the time limit, but I am significantly limiting the search space. If I increase the turn time limit, the search space is increased but I run the risk of running out of time.
–
TonyAbellNov 3 '09 at 16:18

@TonyAbell: If it's important to have a turn based time limit, why not start with an initial value and then adjust it from round to round? After about half the rounds you will most likely have found the optimal turn length for the opponent you're facing.
–
ShakaNov 3 '09 at 17:26

You should keep track of your time left, and limit it to 1/2 of the available time left.
–
John GietzenNov 13 '09 at 23:38

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.