a geicko-2 based round robin ranking system designed to test c++ battleship submissions
battleship.dunkirk.sh
1// Lightweight cross-platform battleship implementation
2// Author: Kieran Klukas
3// Date: November 2025
4// Purpose: Test smart battleship AI with benchmarking on non-Linux systems
5
6#include "battleship.h"
7#include "memory.h"
8#include "memory_functions_klukas.h"
9#include <iostream>
10#include <chrono>
11#include <thread>
12#include <vector>
13#include <mutex>
14#include <atomic>
15
16using namespace std;
17
18struct BenchmarkStats {
19 atomic<int> wins{0};
20 atomic<int> losses{0};
21 atomic<int> ties{0};
22 atomic<long long> totalMoves{0};
23 atomic<long long> totalTimeNs{0};
24 atomic<int> minMovesWin{999999};
25 atomic<int> maxMovesWin{0};
26 atomic<int> minMovesLoss{999999};
27 atomic<int> maxMovesLoss{0};
28
29 mutex updateMutex; // For min/max updates
30};
31
32void printStats(const BenchmarkStats &stats, int gamesPlayed) {
33 const int MAX_MOVES = 200; // Theoretical max (both players shoot all 100 squares)
34 double avgMoves = (double)stats.totalMoves.load() / gamesPlayed;
35 double movesPercent = (avgMoves / MAX_MOVES) * 100.0;
36
37 cout << "\n========== BENCHMARK RESULTS ==========" << endl;
38 cout << "Games played: " << gamesPlayed << endl;
39 cout << "Smart AI wins: " << stats.wins.load() << " ("
40 << (100.0 * stats.wins.load() / gamesPlayed) << "%)" << endl;
41 cout << "Dumb AI wins: " << stats.losses.load() << " ("
42 << (100.0 * stats.losses.load() / gamesPlayed) << "%)" << endl;
43 cout << "Ties: " << stats.ties.load() << endl;
44 cout << "Avg moves per game: " << (int)avgMoves
45 << " (" << movesPercent << "% of max)" << endl;
46
47 if (stats.wins.load() > 0) {
48 cout << "Win move range: " << stats.minMovesWin.load() << "-" << stats.maxMovesWin.load() << endl;
49 }
50 if (stats.losses.load() > 0) {
51 cout << "Loss move range: " << stats.minMovesLoss.load() << "-" << stats.maxMovesLoss.load() << endl;
52 }
53
54 double avgTimeMs = (double)stats.totalTimeNs.load() / gamesPlayed / 1000000.0;
55 cout << "Avg time per game: " << avgTimeMs << "ms" << endl;
56 cout << "========================================\n" << endl;
57}
58
59// Thread-safe game runner function
60void runGames(int startGame, int endGame, BenchmarkStats &stats, bool logLosses,
61 atomic<int> &gamesCompleted, int totalGames, unsigned int threadSeed) {
62 // Each thread gets its own random seed
63 srand(threadSeed);
64
65 for (int game = startGame; game < endGame; game++) {
66 auto startTime = chrono::high_resolution_clock::now();
67
68 Board dumbComputerBoard, smartComputerBoard;
69 ComputerMemory smartComputerMemory;
70
71 string dumbComputerMove, smartComputerMove;
72 int numDumbComputerShipsSunk = 0;
73 int numSmartComputerShipsSunk = 0;
74 int dumbComputerRow, dumbComputerColumn;
75 int smartComputerRow, smartComputerColumn;
76 int checkValue, dumbComputerResult, smartComputerResult;
77 int moveCount = 0;
78
79 initializeBoard(dumbComputerBoard);
80 initializeBoard(smartComputerBoard);
81 initMemoryKlukas(smartComputerMemory);
82
83 while (true) {
84 moveCount++;
85
86 // Dumb computer move
87 dumbComputerMove = randomMove();
88 checkValue = checkMove(dumbComputerMove, smartComputerBoard,
89 dumbComputerRow, dumbComputerColumn);
90
91 while (checkValue != VALID_MOVE) {
92 dumbComputerMove = randomMove();
93 checkValue = checkMove(dumbComputerMove, smartComputerBoard,
94 dumbComputerRow, dumbComputerColumn);
95 }
96
97 // Smart computer move
98 smartComputerMove = smartMoveKlukas(smartComputerMemory);
99 int checkResult = checkMove(smartComputerMove, dumbComputerBoard,
100 smartComputerRow, smartComputerColumn);
101
102 while (checkResult != VALID_MOVE) {
103 smartComputerMove = randomMove();
104 checkResult = checkMove(smartComputerMove, dumbComputerBoard,
105 smartComputerRow, smartComputerColumn);
106 }
107
108 // Execute moves
109 dumbComputerResult = playMove(dumbComputerRow, dumbComputerColumn,
110 smartComputerBoard);
111 smartComputerResult = playMove(smartComputerRow, smartComputerColumn,
112 dumbComputerBoard);
113 updateMemoryKlukas(smartComputerRow, smartComputerColumn,
114 smartComputerResult, smartComputerMemory);
115
116 if (isASunk(dumbComputerResult)) {
117 numDumbComputerShipsSunk++;
118 }
119 if (isASunk(smartComputerResult)) {
120 numSmartComputerShipsSunk++;
121 }
122
123 if (numDumbComputerShipsSunk == 5 || numSmartComputerShipsSunk == 5) {
124 break;
125 }
126 }
127
128 auto endTime = chrono::high_resolution_clock::now();
129 auto duration = chrono::duration_cast<chrono::nanoseconds>(endTime - startTime);
130
131 // Update stats atomically
132 stats.totalMoves += moveCount;
133 stats.totalTimeNs += duration.count();
134
135 if (numDumbComputerShipsSunk == 5 && numSmartComputerShipsSunk == 5) {
136 stats.ties++;
137 } else if (numSmartComputerShipsSunk == 5) {
138 stats.wins++;
139
140 // Update min/max with mutex protection
141 lock_guard<mutex> lock(stats.updateMutex);
142 int currentMin = stats.minMovesWin.load();
143 if (moveCount < currentMin) stats.minMovesWin = moveCount;
144 int currentMax = stats.maxMovesWin.load();
145 if (moveCount > currentMax) stats.maxMovesWin = moveCount;
146 } else {
147 stats.losses++;
148
149 lock_guard<mutex> lock(stats.updateMutex);
150 int currentMin = stats.minMovesLoss.load();
151 if (moveCount < currentMin) stats.minMovesLoss = moveCount;
152 int currentMax = stats.maxMovesLoss.load();
153 if (moveCount > currentMax) stats.maxMovesLoss = moveCount;
154 }
155
156 // Update progress counter
157 gamesCompleted++;
158 }
159}
160
161int main(int argc, char* argv[]) {
162 bool benchmark = false;
163 bool verbose = false;
164 bool logLosses = false;
165 bool catchGuards = false;
166 int numGames = 1;
167
168 // Parse command line args
169 for (int i = 1; i < argc; i++) {
170 string arg = argv[i];
171 if (arg == "--benchmark" || arg == "-b") {
172 benchmark = true;
173 if (i + 1 < argc) {
174 numGames = atoi(argv[++i]);
175 if (numGames <= 0) numGames = 100;
176 } else {
177 numGames = 100;
178 }
179 } else if (arg == "--verbose" || arg == "-v") {
180 verbose = true;
181 } else if (arg == "--log-losses" || arg == "-l") {
182 logLosses = true;
183 } else if (arg == "--catch-guards" || arg == "-g") {
184 catchGuards = true;
185 }
186 }
187
188 BenchmarkStats stats;
189 srand(time(NULL));
190
191 // Catch-guards mode: run games until we hit a guard
192 if (catchGuards) {
193 cout << "Running games until guard is tripped..." << endl;
194 int gamesRun = 0;
195
196 while (true) {
197 gamesRun++;
198 resetGuardTripped();
199
200 Board dumbComputerBoard, smartComputerBoard;
201 ComputerMemory smartComputerMemory;
202 string dumbComputerMove, smartComputerMove;
203 int numDumbComputerShipsSunk = 0;
204 int numSmartComputerShipsSunk = 0;
205 int dumbComputerRow, dumbComputerColumn;
206 int smartComputerRow, smartComputerColumn;
207 int checkValue, dumbComputerResult, smartComputerResult;
208 int moveCount = 0;
209
210 initializeBoard(dumbComputerBoard);
211 initializeBoard(smartComputerBoard);
212 initMemoryKlukas(smartComputerMemory);
213
214 bool guardTripped = false;
215 while (true) {
216 moveCount++;
217
218 // Dumb computer move
219 dumbComputerMove = randomMove();
220 checkValue = checkMove(dumbComputerMove, smartComputerBoard,
221 dumbComputerRow, dumbComputerColumn);
222 while (checkValue != VALID_MOVE) {
223 dumbComputerMove = randomMove();
224 checkValue = checkMove(dumbComputerMove, smartComputerBoard,
225 dumbComputerRow, dumbComputerColumn);
226 }
227
228 // Smart computer move
229 smartComputerMove = smartMoveKlukas(smartComputerMemory);
230
231 // Check if guard was tripped
232 if (getGuardTripped()) {
233 guardTripped = true;
234 break;
235 }
236
237 int checkResult = checkMove(smartComputerMove, dumbComputerBoard,
238 smartComputerRow, smartComputerColumn);
239 while (checkResult != VALID_MOVE) {
240 smartComputerMove = randomMove();
241 checkResult = checkMove(smartComputerMove, dumbComputerBoard,
242 smartComputerRow, smartComputerColumn);
243 }
244
245 // Execute moves
246 dumbComputerResult = playMove(dumbComputerRow, dumbComputerColumn,
247 smartComputerBoard);
248 smartComputerResult = playMove(smartComputerRow, smartComputerColumn,
249 dumbComputerBoard);
250 updateMemoryKlukas(smartComputerRow, smartComputerColumn,
251 smartComputerResult, smartComputerMemory);
252
253 if (isASunk(dumbComputerResult)) {
254 numDumbComputerShipsSunk++;
255 }
256 if (isASunk(smartComputerResult)) {
257 numSmartComputerShipsSunk++;
258 }
259
260 if (numDumbComputerShipsSunk == 5 || numSmartComputerShipsSunk == 5) {
261 break;
262 }
263 }
264
265 if (guardTripped) {
266 cout << "\n==================================" << endl;
267 cout << "GUARD TRIPPED after " << gamesRun << " games!" << endl;
268 cout << "==================================" << endl;
269 cout << "\nDebug log (last 50 entries):" << endl;
270 cout << "----------------------------------" << endl;
271
272 vector<string> log = getDebugLog();
273 int start = max(0, (int)log.size() - 50);
274 for (int i = start; i < (int)log.size(); i++) {
275 cout << log[i] << endl;
276 }
277
278 return 0;
279 }
280
281 if (gamesRun % 100 == 0) {
282 cout << "Completed " << gamesRun << " games..." << endl;
283 }
284 }
285 }
286
287 if (!benchmark) {
288 setDebugMode(true);
289 welcome(true);
290 verbose = true; // Always show moves in interactive mode
291 } else {
292 setDebugMode(false);
293
294 // Determine number of threads (use hardware concurrency)
295 unsigned int numThreads = thread::hardware_concurrency();
296 if (numThreads == 0) numThreads = 4; // Fallback if detection fails
297
298 cout << "Running " << numGames << " games on " << numThreads << " threads..." << endl;
299
300 // Progress tracking
301 atomic<int> gamesCompleted{0};
302
303 // Launch threads
304 vector<thread> threads;
305 int gamesPerThread = numGames / numThreads;
306 int remainder = numGames % numThreads;
307
308 int startGame = 0;
309 for (unsigned int t = 0; t < numThreads; t++) {
310 int endGame = startGame + gamesPerThread + (t < (unsigned)remainder ? 1 : 0);
311 unsigned int threadSeed = time(NULL) + t; // Unique seed per thread
312
313 threads.emplace_back(runGames, startGame, endGame, ref(stats),
314 logLosses, ref(gamesCompleted), numGames, threadSeed);
315 startGame = endGame;
316 }
317
318 // Progress monitoring thread
319 thread progressThread([&]() {
320 int lastReported = 0;
321 int interval;
322 if (numGames >= 10000) interval = 1000;
323 else if (numGames >= 1000) interval = 100;
324 else if (numGames >= 100) interval = 10;
325 else interval = numGames / 5;
326
327 while (gamesCompleted.load() < numGames) {
328 this_thread::sleep_for(chrono::milliseconds(100));
329 int completed = gamesCompleted.load();
330 if (interval > 0 && completed >= lastReported + interval) {
331 cout << "Completed " << completed << " games..." << endl;
332 lastReported = (completed / interval) * interval;
333 }
334 }
335 });
336
337 // Wait for all game threads to complete
338 for (auto &t : threads) {
339 t.join();
340 }
341
342 // Stop progress thread
343 progressThread.join();
344 }
345
346 if (!benchmark) {
347 // Single game mode (existing code)
348 Board dumbComputerBoard, smartComputerBoard;
349 ComputerMemory smartComputerMemory;
350
351 string dumbComputerMove, smartComputerMove;
352 int numDumbComputerShipsSunk = 0;
353 int numSmartComputerShipsSunk = 0;
354 int dumbComputerRow, dumbComputerColumn;
355 int smartComputerRow, smartComputerColumn;
356 int checkValue, dumbComputerResult, smartComputerResult;
357
358 initializeBoard(dumbComputerBoard);
359 initializeBoard(smartComputerBoard);
360 initMemoryKlukas(smartComputerMemory);
361
362 while (true) {
363 if (verbose) {
364 clearTheScreen();
365 cout << "Dumb Computer Board:" << endl;
366 displayBoard(1, 5, HUMAN, dumbComputerBoard);
367 cout << "Smart Computer Board:" << endl;
368 displayBoard(1, 40, HUMAN, smartComputerBoard);
369 }
370
371 // Dumb computer move
372 dumbComputerMove = randomMove();
373 checkValue = checkMove(dumbComputerMove, smartComputerBoard,
374 dumbComputerRow, dumbComputerColumn);
375
376 while (checkValue != VALID_MOVE) {
377 dumbComputerMove = randomMove();
378 checkValue = checkMove(dumbComputerMove, smartComputerBoard,
379 dumbComputerRow, dumbComputerColumn);
380 }
381
382 // Smart computer move
383 smartComputerMove = smartMoveKlukas(smartComputerMemory);
384 int checkResult = checkMove(smartComputerMove, dumbComputerBoard,
385 smartComputerRow, smartComputerColumn);
386
387 while (checkResult != VALID_MOVE) {
388 if (verbose) {
389 debug("INVALID! Using random instead", 0, 0);
390 }
391 smartComputerMove = randomMove();
392 checkResult = checkMove(smartComputerMove, dumbComputerBoard,
393 smartComputerRow, smartComputerColumn);
394 }
395
396 // Execute moves
397 dumbComputerResult = playMove(dumbComputerRow, dumbComputerColumn,
398 smartComputerBoard);
399 smartComputerResult = playMove(smartComputerRow, smartComputerColumn,
400 dumbComputerBoard);
401 updateMemoryKlukas(smartComputerRow, smartComputerColumn,
402 smartComputerResult, smartComputerMemory);
403
404 if (verbose) {
405 clearTheScreen();
406 cout << "Dumb Computer Board:" << endl;
407 displayBoard(1, 5, HUMAN, dumbComputerBoard);
408 cout << "Smart Computer Board:" << endl;
409 displayBoard(1, 40, HUMAN, smartComputerBoard);
410
411 writeMessage(15, 0, "The dumb computer chooses: " + dumbComputerMove);
412 writeMessage(16, 0, "The smart computer chooses: " + smartComputerMove);
413
414 writeResult(18, 0, dumbComputerResult, COMPUTER);
415 writeResult(19, 0, smartComputerResult, HUMAN);
416
417 // Delay so the game is watchable
418 this_thread::sleep_for(chrono::milliseconds(50));
419 }
420
421 if (isASunk(dumbComputerResult)) {
422 numDumbComputerShipsSunk++;
423 }
424 if (isASunk(smartComputerResult)) {
425 numSmartComputerShipsSunk++;
426 }
427
428 if (numDumbComputerShipsSunk == 5 || numSmartComputerShipsSunk == 5) {
429 break;
430 }
431 }
432
433 cout << "\nFinal Dumb Computer Board:" << endl;
434 displayBoard(1, 5, HUMAN, dumbComputerBoard);
435 cout << "Final Smart Computer Board:" << endl;
436 displayBoard(1, 40, HUMAN, smartComputerBoard);
437
438 if (numDumbComputerShipsSunk == 5 && numSmartComputerShipsSunk == 5) {
439 writeMessage(21, 1, "The game is a tie.");
440 } else if (numDumbComputerShipsSunk == 5) {
441 writeMessage(21, 1, "Amazing, the dumb computer won.");
442 } else {
443 writeMessage(21, 1, "Smart AI won! As it should.");
444 }
445 }
446
447 if (benchmark) {
448 printStats(stats, numGames);
449 }
450
451 return 0;
452}