a geicko-2 based round robin ranking system designed to test c++ battleship submissions battleship.dunkirk.sh
at main 18 kB view raw
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}