···11+# Battleship Arena - Tournament System
22+33+## Overview
44+Tournament-style battleship AI competition with automatic header generation and local compilation.
55+66+## Architecture
77+88+### Battleship Engine
99+- Located in `./battleship-engine/`
1010+- Contains the lightweight battleship game engine
1111+- No external dependencies on the school repo
1212+- Auto-generates header files for submissions
1313+1414+### Submission Flow
1515+1. User uploads `memory_functions_<name>.cpp` via SCP/SFTP
1616+2. System auto-generates `memory_functions_<name>.h` header
1717+3. Compiles submission with the battleship engine
1818+4. If successful, runs tournament matches against all active submissions
1919+5. Updates leaderboard with results
2020+2121+### Tournament Matching
2222+- Each match compiles both AIs into a single binary
2323+- Runs 10 games per match
2424+- Winner determined by total wins
2525+- All results stored in database
2626+2727+## Test Submissions
2828+2929+Three AI implementations for testing:
3030+3131+1. **random** - Pure random shooter (baseline)
3232+2. **hunter** - Checkerboard hunt + adjacent targeting
3333+3. **klukas** - Advanced probability-based AI
3434+3535+## Testing
3636+3737+```bash
3838+# Upload test submissions
3939+./scripts/test-upload.sh
4040+4141+# This uploads:
4242+# - alice with random AI
4343+# - bob with hunter AI
4444+# - charlie with klukas AI
4545+```
4646+4747+## Requirements
4848+4949+Submissions must:
5050+- Be named `memory_functions_<name>.cpp`
5151+- Implement three functions:
5252+ - `void initMemory<Name>(ComputerMemory &memory)`
5353+ - `std::string smartMove<Name>(const ComputerMemory &memory)`
5454+ - `void updateMemory<Name>(int row, int col, int result, ComputerMemory &memory)`
5555+- Use only standard includes and provided headers (battleship.h, kasbs.h, memory.h)
5656+5757+Headers are auto-generated - users only need to upload the `.cpp` file!
5858+5959+## Usage
6060+6161+Start server:
6262+```bash
6363+./battleship-arena
6464+```
6565+6666+View results:
6767+- SSH TUI: `ssh -p 2222 username@0.0.0.0`
6868+- Web: http://0.0.0.0:8080
···11+#ifndef BATTLESHIP_H
22+#define BATTLESHIP_H
33+44+// Author: Keith Shomper
55+// Date: 24 Oct 03
66+// Purpose: Header file for implementing a text-based battleship game
77+// Updated: 14 Nov 08 to align class types with Cedarville standard
88+// Updated: 3 Apr 14 to use struct rather than class for the defined types
99+// Updated: 5 Nov 15 to introduce new functions to replace the ISAxxx macros
1010+// and also the SHIPNUM and DEBUGOUT macros
1111+1212+// include files for implementing battleship
1313+#include <iostream>
1414+#include <cstdlib>
1515+#include <time.h>
1616+#include <curses.h>
1717+#include "kasbs.h"
1818+1919+using namespace std;
2020+2121+// use these constants to indicate if the player is a human or a computer
2222+// battleship board size is 10x10 grid (defined in kasbs.h)
2323+2424+// data structure for position
2525+struct Position {
2626+ int startRow; // ship's initial row
2727+ int startCol; // ship's initial column
2828+ int orient; // indicates whether the ship is running across
2929+ // or up and down
3030+};
3131+3232+// data structure for ship
3333+struct Ship {
3434+ Position pos; // where the ship is on the board
3535+ int size; // number of hits required to sink the ship
3636+ int hitsToSink; // number of hits remaining before the ship is sunk
3737+ char marker; // the ASCII marker used to denote the ship on the
3838+ // board
3939+};
4040+4141+// a game board is made up of a 10x10 playing grid and the ships
4242+struct Board {
4343+ char grid[BOARDSIZE][BOARDSIZE];
4444+ Ship s[6]; // NOTE: the first (zeroth) position is left empty
4545+};
4646+4747+// use these constants for designating to which player we are referring
4848+const int HUMAN = 0;
4949+const int COMPUTER = 1;
5050+5151+// use these constants for deciding whether or not the user gave a proper move
5252+const int VALID_MOVE = 0;
5353+const int ILLEGAL_FORMAT = 1;
5454+const int REUSED_MOVE = 2;
5555+5656+// functions for screen control and I/O
5757+void welcome(bool debug = false, bool pf = false);
5858+void clearTheLine(int x);
5959+void clearTheScreen(void);
6060+void pauseForEnter(void);
6161+string getResponse(int x, int y, string prompt);
6262+void writeMessage(int x, int y, string message);
6363+void writeResult(int x, int y, int result, int playerType);
6464+void displayBoard(int x, int y, int playerType, const Board &gameBoard);
6565+6666+// functions to control the board situation
6767+void initializeBoard(Board &gameBoard, bool file = false);
6868+int playMove(int row, int col, Board &gameBoard);
6969+7070+// function to tell what happened in the last play_move() command
7171+bool isAMiss(int playMoveResult);
7272+bool isAHit (int playMoveResult);
7373+bool isASunk(int playMoveResult); // formerly named isItSunk()
7474+int isShip (int playMoveResult);
7575+7676+// misc game functions
7777+string randomMove(void);
7878+int checkMove(string move, const Board &gameBoard, int &row, int &col);
7979+void debug(string s, int x = 22, int y = 1);
8080+string numToString(int x);
8181+8282+#ifdef BATTLESHIP_BACKWARD_COMPATIBILITY
8383+8484+// former function signatures
8585+void debug(int x, int y, string s);
8686+bool isItSunk(int playMoveResult);
8787+8888+// a debug macro
8989+#ifdef DEBUG
9090+#define DEBUGOUT(str) debug (22, 1, (str));
9191+#else
9292+#define DEBUGOUT(str)
9393+#endif // DEBUG
9494+9595+#endif // BATTLESHIP_BACKWARD_COMPATABILITY
9696+9797+#endif // BATTLESHIP_H
+317
battleship-engine/src/battleship_light.cpp
···11+#include "battleship_light.h"
22+#include <sstream>
33+#include <cctype>
44+#include <vector>
55+#include <mutex>
66+77+// Global flag for debug output
88+static bool g_debugEnabled = false;
99+static bool g_guardTripped = false;
1010+static vector<string> g_debugLog;
1111+static mutex g_debugLogMutex;
1212+static const size_t MAX_DEBUG_LOG_SIZE = 1000;
1313+1414+void setDebugMode(bool enabled) {
1515+ g_debugEnabled = enabled;
1616+}
1717+1818+bool getGuardTripped() {
1919+ return g_guardTripped;
2020+}
2121+2222+void resetGuardTripped() {
2323+ g_guardTripped = false;
2424+ lock_guard<mutex> lock(g_debugLogMutex);
2525+ g_debugLog.clear();
2626+}
2727+2828+vector<string> getDebugLog() {
2929+ lock_guard<mutex> lock(g_debugLogMutex);
3030+ return g_debugLog;
3131+}
3232+3333+void welcome(bool debug) {
3434+ clearTheScreen();
3535+ cout << "========================================" << endl;
3636+ cout << " BATTLESHIP - Lightweight" << endl;
3737+ cout << "========================================" << endl;
3838+ if (debug) {
3939+ cout << "Debug mode enabled" << endl;
4040+ }
4141+ cout << endl;
4242+}
4343+4444+void clearTheScreen() {
4545+ // Simple cross-platform clear
4646+ cout << "\033[2J\033[1;1H";
4747+}
4848+4949+void pauseForEnter() {
5050+ cout << "Press Enter to continue...";
5151+ cin.ignore();
5252+ cin.get();
5353+}
5454+5555+void writeMessage(int x, int y, string message) {
5656+ cout << message << endl;
5757+}
5858+5959+void writeResult(int x, int y, int result, int playerType) {
6060+ string player = (playerType == HUMAN) ? "Player" : "Computer";
6161+6262+ if (isASunk(result)) {
6363+ int shipNum = isShip(result);
6464+ char shipName;
6565+ switch(shipNum) {
6666+ case AC: shipName = 'A'; break;
6767+ case BS: shipName = 'B'; break;
6868+ case CR: shipName = 'C'; break;
6969+ case SB: shipName = 'S'; break;
7070+ case DS: shipName = 'D'; break;
7171+ default: shipName = '?'; break;
7272+ }
7373+ cout << player << " SUNK a ship (" << shipName << ")!" << endl;
7474+ } else if (isAHit(result)) {
7575+ cout << player << " HIT!" << endl;
7676+ } else {
7777+ cout << player << " MISS" << endl;
7878+ }
7979+}
8080+8181+void displayBoard(int x, int y, int playerType, const Board &gameBoard) {
8282+ cout << " ";
8383+ for (int col = 0; col < BOARDSIZE; col++) {
8484+ cout << (col + 1) << " ";
8585+ }
8686+ cout << endl;
8787+8888+ for (int row = 0; row < BOARDSIZE; row++) {
8989+ cout << char('A' + row) << " ";
9090+ for (int col = 0; col < BOARDSIZE; col++) {
9191+ char cell = gameBoard.grid[row][col];
9292+9393+ // Hide ships if showing to computer player
9494+ if (playerType == COMPUTER) {
9595+ if (cell != HIT_MARKER && cell != MISS_MARKER && cell != SUNK_MARKER) {
9696+ cell = EMPTY_MARKER;
9797+ }
9898+ }
9999+100100+ cout << cell << " ";
101101+ }
102102+ cout << endl;
103103+ }
104104+ cout << endl;
105105+}
106106+107107+bool placeShip(Board &gameBoard, int shipNum, int row, int col, int orient) {
108108+ Ship &ship = gameBoard.s[shipNum];
109109+110110+ // Check bounds
111111+ if (orient == HORZ) {
112112+ if (col + ship.size > BOARDSIZE) return false;
113113+ } else {
114114+ if (row + ship.size > BOARDSIZE) return false;
115115+ }
116116+117117+ // Check for collisions
118118+ for (int i = 0; i < ship.size; i++) {
119119+ int r = (orient == VERT) ? row + i : row;
120120+ int c = (orient == HORZ) ? col + i : col;
121121+ if (gameBoard.grid[r][c] != EMPTY_MARKER) {
122122+ return false;
123123+ }
124124+ }
125125+126126+ // Place the ship
127127+ ship.pos.startRow = row;
128128+ ship.pos.startCol = col;
129129+ ship.pos.orient = orient;
130130+131131+ for (int i = 0; i < ship.size; i++) {
132132+ int r = (orient == VERT) ? row + i : row;
133133+ int c = (orient == HORZ) ? col + i : col;
134134+ gameBoard.grid[r][c] = ship.marker;
135135+ }
136136+137137+ return true;
138138+}
139139+140140+void initializeBoard(Board &gameBoard, bool file) {
141141+ // Initialize grid
142142+ for (int i = 0; i < BOARDSIZE; i++) {
143143+ for (int j = 0; j < BOARDSIZE; j++) {
144144+ gameBoard.grid[i][j] = EMPTY_MARKER;
145145+ }
146146+ }
147147+148148+ // Initialize ships
149149+ gameBoard.s[AC].size = AC_SIZE;
150150+ gameBoard.s[AC].hitsToSink = AC_SIZE;
151151+ gameBoard.s[AC].marker = AC_MARKER;
152152+153153+ gameBoard.s[BS].size = BS_SIZE;
154154+ gameBoard.s[BS].hitsToSink = BS_SIZE;
155155+ gameBoard.s[BS].marker = BS_MARKER;
156156+157157+ gameBoard.s[CR].size = CR_SIZE;
158158+ gameBoard.s[CR].hitsToSink = CR_SIZE;
159159+ gameBoard.s[CR].marker = CR_MARKER;
160160+161161+ gameBoard.s[SB].size = SB_SIZE;
162162+ gameBoard.s[SB].hitsToSink = SB_SIZE;
163163+ gameBoard.s[SB].marker = SB_MARKER;
164164+165165+ gameBoard.s[DS].size = DS_SIZE;
166166+ gameBoard.s[DS].hitsToSink = DS_SIZE;
167167+ gameBoard.s[DS].marker = DS_MARKER;
168168+169169+ // Place ships randomly
170170+ for (int shipNum = AC; shipNum <= DS; shipNum++) {
171171+ bool placed = false;
172172+ while (!placed) {
173173+ int row = rand() % BOARDSIZE;
174174+ int col = rand() % BOARDSIZE;
175175+ int orient = rand() % 2;
176176+ placed = placeShip(gameBoard, shipNum, row, col, orient);
177177+ }
178178+ }
179179+}
180180+181181+int playMove(int row, int col, Board &gameBoard) {
182182+ char cell = gameBoard.grid[row][col];
183183+184184+ // Already hit
185185+ if (cell == HIT_MARKER || cell == MISS_MARKER || cell == SUNK_MARKER) {
186186+ return MISS;
187187+ }
188188+189189+ // Miss
190190+ if (cell == EMPTY_MARKER) {
191191+ gameBoard.grid[row][col] = MISS_MARKER;
192192+ return MISS;
193193+ }
194194+195195+ // Hit a ship
196196+ int shipNum = 0;
197197+ if (cell == AC_MARKER) shipNum = AC;
198198+ else if (cell == BS_MARKER) shipNum = BS;
199199+ else if (cell == CR_MARKER) shipNum = CR;
200200+ else if (cell == SB_MARKER) shipNum = SB;
201201+ else if (cell == DS_MARKER) shipNum = DS;
202202+203203+ if (shipNum == 0) {
204204+ gameBoard.grid[row][col] = MISS_MARKER;
205205+ return MISS;
206206+ }
207207+208208+ Ship &ship = gameBoard.s[shipNum];
209209+ ship.hitsToSink--;
210210+211211+ gameBoard.grid[row][col] = HIT_MARKER;
212212+213213+ if (ship.hitsToSink == 0) {
214214+ // Mark all parts as sunk
215215+ for (int i = 0; i < ship.size; i++) {
216216+ int r = (ship.pos.orient == VERT) ? ship.pos.startRow + i : ship.pos.startRow;
217217+ int c = (ship.pos.orient == HORZ) ? ship.pos.startCol + i : ship.pos.startCol;
218218+ gameBoard.grid[r][c] = SUNK_MARKER;
219219+ }
220220+ return SUNK | shipNum;
221221+ }
222222+223223+ return HIT | shipNum;
224224+}
225225+226226+bool isAMiss(int playMoveResult) {
227227+ return !(playMoveResult & HIT);
228228+}
229229+230230+bool isAHit(int playMoveResult) {
231231+ return (playMoveResult & HIT) != 0;
232232+}
233233+234234+bool isASunk(int playMoveResult) {
235235+ return (playMoveResult & SUNK) != 0;
236236+}
237237+238238+int isShip(int playMoveResult) {
239239+ return playMoveResult & SHIP;
240240+}
241241+242242+string randomMove() {
243243+ int row = rand() % BOARDSIZE;
244244+ int col = rand() % BOARDSIZE;
245245+246246+ char letter = 'A' + row;
247247+ return string(1, letter) + " " + to_string(col + 1);
248248+}
249249+250250+int checkMove(string move, const Board &gameBoard, int &row, int &col) {
251251+ // Trim whitespace
252252+ move.erase(0, move.find_first_not_of(" \t\n\r"));
253253+ move.erase(move.find_last_not_of(" \t\n\r") + 1);
254254+255255+ if (move.empty()) {
256256+ return ILLEGAL_FORMAT;
257257+ }
258258+259259+ // Parse format: "A 5" or "A5"
260260+ char letter = toupper(move[0]);
261261+ if (letter < 'A' || letter > 'J') {
262262+ return ILLEGAL_FORMAT;
263263+ }
264264+265265+ row = letter - 'A';
266266+267267+ // Extract number
268268+ string numStr = move.substr(1);
269269+ numStr.erase(0, numStr.find_first_not_of(" \t"));
270270+271271+ if (numStr.empty()) {
272272+ return ILLEGAL_FORMAT;
273273+ }
274274+275275+ try {
276276+ col = stoi(numStr) - 1;
277277+ } catch (...) {
278278+ return ILLEGAL_FORMAT;
279279+ }
280280+281281+ if (col < 0 || col >= BOARDSIZE) {
282282+ return ILLEGAL_FORMAT;
283283+ }
284284+285285+ // Check if already used
286286+ char cell = gameBoard.grid[row][col];
287287+ if (cell == HIT_MARKER || cell == MISS_MARKER || cell == SUNK_MARKER) {
288288+ return REUSED_MOVE;
289289+ }
290290+291291+ return VALID_MOVE;
292292+}
293293+294294+void debug(string s, int x, int y) {
295295+ // Only accumulate logs if debug mode is enabled or we need to track guards
296296+ // This prevents memory bloat during benchmarks
297297+ if (g_debugEnabled || s.find("*** GUARD TRIPPED ***") != string::npos) {
298298+ lock_guard<mutex> lock(g_debugLogMutex);
299299+ g_debugLog.push_back(s);
300300+301301+ // Limit log size to prevent unbounded growth
302302+ if (g_debugLog.size() > MAX_DEBUG_LOG_SIZE) {
303303+ g_debugLog.erase(g_debugLog.begin(),
304304+ g_debugLog.begin() + (g_debugLog.size() - MAX_DEBUG_LOG_SIZE));
305305+ }
306306+ }
307307+308308+ if (g_debugEnabled) {
309309+ cout << "[DEBUG] " << s << endl;
310310+ }
311311+}
312312+313313+string numToString(int x) {
314314+ stringstream ss;
315315+ ss << x;
316316+ return ss.str();
317317+}
+65
battleship-engine/src/battleship_light.h
···11+#ifndef BATTLESHIP_LIGHT_H
22+#define BATTLESHIP_LIGHT_H
33+44+#include <iostream>
55+#include <cstdlib>
66+#include <ctime>
77+#include <string>
88+#include <vector>
99+1010+// Use the same constants and types as the normal version
1111+#include "kasbs.h"
1212+1313+using namespace std;
1414+1515+// Player types (not in kasbs.h)
1616+const int HUMAN = 0;
1717+const int COMPUTER = 1;
1818+1919+// Move validation (not in kasbs.h)
2020+const int VALID_MOVE = 0;
2121+const int ILLEGAL_FORMAT = 1;
2222+const int REUSED_MOVE = 2;
2323+2424+// Position, Ship, and Board are compatible with normal version
2525+struct Position {
2626+ int startRow;
2727+ int startCol;
2828+ int orient;
2929+};
3030+3131+struct Ship {
3232+ Position pos;
3333+ int size;
3434+ int hitsToSink;
3535+ char marker;
3636+};
3737+3838+struct Board {
3939+ char grid[BOARDSIZE][BOARDSIZE];
4040+ Ship s[6]; // index 0 unused
4141+};
4242+4343+// Core functions - lightweight implementations
4444+void setDebugMode(bool enabled);
4545+bool getGuardTripped();
4646+void resetGuardTripped();
4747+vector<string> getDebugLog();
4848+void welcome(bool debug = false);
4949+void clearTheScreen();
5050+void pauseForEnter();
5151+void writeMessage(int x, int y, string message);
5252+void writeResult(int x, int y, int result, int playerType);
5353+void displayBoard(int x, int y, int playerType, const Board &gameBoard);
5454+void initializeBoard(Board &gameBoard, bool file = false);
5555+int playMove(int row, int col, Board &gameBoard);
5656+bool isAMiss(int playMoveResult);
5757+bool isAHit(int playMoveResult);
5858+bool isASunk(int playMoveResult);
5959+int isShip(int playMoveResult);
6060+string randomMove();
6161+int checkMove(string move, const Board &gameBoard, int &row, int &col);
6262+void debug(string s, int x = 22, int y = 1);
6363+string numToString(int x);
6464+6565+#endif // BATTLESHIP_LIGHT_H
+78
battleship-engine/src/kasbs.h
···11+// Author: Keith Shomper
22+// Date: 24 Oct 03
33+// Purpose: Type definitions for implementing a text-based battleship game
44+// Modified:15 Nov 2005 - Moved the class definitions into the file battleship.h
55+// to make the data structure more visible.
66+// Modified: 5 Nov 2013 - Moved the ISA macros higher in the file to make them
77+// more visible. Added ISAMISS macro
88+// Modified: 5 Nov 2015 - Moved the ISA macros back to a lower section and made
99+// them conditionally compilable. The purpose of this
1010+// change was to promote their replacement by like-named
1111+// functions, while maintaining backward compatibility.
1212+1313+// use these constants to indicate whether a ship goes across the grid or up
1414+// and down
1515+1616+#ifndef BATTLESHIP_TYPE_DEFINITIONS_H
1717+#define BATTLESHIP_TYPE_DEFINITIONS_H
1818+1919+// Board size
2020+const int BOARDSIZE = 10;
2121+2222+// use these constants to indicate whether a ship goes across the grid or up
2323+// and down
2424+const int HORZ = 0;
2525+const int VERT = 1;
2626+2727+// these constants allow use to refer to the ships by numerical values
2828+const int AC = 1;
2929+const int BS = 2;
3030+const int CR = 3;
3131+const int SB = 4;
3232+const int DS = 5;
3333+3434+// constants for the ship size
3535+const int AC_SIZE = 5;
3636+const int BS_SIZE = 4;
3737+const int CR_SIZE = 3;
3838+const int SB_SIZE = 3;
3939+const int DS_SIZE = 2;
4040+4141+// markers for keeping track of game play
4242+const char HIT_MARKER = 'H';
4343+const char MISS_MARKER = '*';
4444+const char SUNK_MARKER = 'X';
4545+const char EMPTY_MARKER = ' ';
4646+const char AC_MARKER = 'A';
4747+const char BS_MARKER = 'B';
4848+const char CR_MARKER = 'C';
4949+const char SB_MARKER = 'S';
5050+const char DS_MARKER = 'D';
5151+5252+// TO STUDENTS: It should not be necessary in your assignment for you to use
5353+// the information appearing below this comment
5454+5555+// color constants for diferent game situations
5656+const int BATTLE_WHITE = 1;
5757+const int BATTLE_GREEN = 2;
5858+const int BATTLE_YELLOW = 3;
5959+const int BATTLE_RED = 4;
6060+6161+// use these constants to set the return value in playMove(), they allow us
6262+// to send back mutiple pieces of information about the result of the move in
6363+// a single integer variable
6464+const int MISS = 0;
6565+const int SHIP = 7;
6666+const int HIT = 8;
6767+const int SUNK = 16;
6868+6969+#ifdef BATTLESHIP_BACKWARD_COMPATIBILITY
7070+// these macros use the MISS, HIT, etc. constants below to determine the
7171+// result of the move
7272+#define ISAMISS(parm) (!((parm) & HIT))
7373+#define ISAHIT(parm) ((parm) & HIT)
7474+#define ISASUNK(parm) ((parm) & SUNK)
7575+#define SHIPNUM(parm) ((parm) & SHIP)
7676+#endif // BATTLESHIP_BACKWARD_COMPATIBILITY
7777+7878+#endif // BATTLESHIP_TYPE_DEFINITIONS_H
+38
battleship-engine/src/memory.h
···11+#ifndef MEMORY_H
22+#define MEMORY_H
33+44+#include "kasbs.h"
55+66+using namespace std;
77+88+#define RANDOM 1
99+#define SEARCH 2
1010+#define DESTROY 3
1111+1212+#define NONE 0
1313+#define NORTH 1
1414+#define SOUTH 2
1515+#define EAST 3
1616+#define WEST 4
1717+1818+struct ComputerMemory {
1919+ int hitRow, hitCol;
2020+ int hitShip;
2121+ int fireDir;
2222+ int fireDist;
2323+ int lastResult;
2424+ int mode;
2525+ char grid[BOARDSIZE][BOARDSIZE];
2626+2727+ // optional attributes for students wanting to keep track of hits on
2828+ // multiple ships
2929+ int depth;
3030+ int hitRows[5], hitCols[5];
3131+ int hitShips[5];
3232+ int fireDirs[5];
3333+ int fireDists[5];
3434+ int lastResults[5];
3535+ int modes[5];
3636+};
3737+3838+#endif // MEMORY_H