···19192020Files must match pattern `memory_functions_*.cpp`
21212222+## Required Function Signatures
2323+2424+**IMPORTANT**: Your submission must implement exactly these three functions with these exact signatures:
2525+2626+```cpp
2727+void initMemoryYOURNAME(ComputerMemory &memory);
2828+string smartMoveYOURNAME(const ComputerMemory &memory);
2929+void updateMemoryYOURNAME(int row, int col, int result, ComputerMemory &memory);
3030+```
3131+3232+Replace `YOURNAME` with your chosen suffix (must match your filename `memory_functions_YOURNAME.cpp`).
3333+3434+**Example** for `memory_functions_alice.cpp`:
3535+```cpp
3636+#include "memory_functions_alice.h"
3737+#include "battleship.h"
3838+#include "kasbs.h"
3939+#include "memory.h"
4040+#include <string>
4141+4242+using namespace std;
4343+4444+inline string formatMove(int row, int col) {
4545+ char letter = static_cast<char>('A' + row);
4646+ return string(1, letter) + to_string(col + 1);
4747+}
4848+4949+void initMemoryAlice(ComputerMemory &memory) {
5050+ // Initialize your memory structure
5151+ for (int i = 0; i < BOARDSIZE; i++) {
5252+ for (int j = 0; j < BOARDSIZE; j++) {
5353+ memory.grid[i][j] = '?';
5454+ }
5555+ }
5656+}
5757+5858+void updateMemoryAlice(int row, int col, int result, ComputerMemory &memory) {
5959+ // Update memory based on shot result
6060+ // result constants: HIT, MISS, SUNK (from kasbs.h)
6161+ if (result == HIT || result == SUNK) {
6262+ memory.grid[row][col] = 'h';
6363+ } else {
6464+ memory.grid[row][col] = 'm';
6565+ }
6666+}
6767+6868+string smartMoveAlice(const ComputerMemory &memory) {
6969+ // Return your next move as a string (e.g., "A1", "B5", "J10")
7070+ int row = 0; // your logic here
7171+ int col = 0; // your logic here
7272+ return formatMove(row, col);
7373+}
7474+```
7575+7676+**Key Points**:
7777+- Function names must match your filename suffix exactly (case-sensitive)
7878+- Must return `string` from `smartMove`, not integer array
7979+- Must use `ComputerMemory &` parameter, not custom structs
8080+- Use `formatMove(row, col)` helper to convert coordinates to string format
8181+- Result constants available: `HIT`, `MISS`, `SUNK` from `kasbs.h`
8282+- Grid size constant: `BOARDSIZE` from `kasbs.h`
8383+2284## Testing Flow
238524861. Student uploads file via SCP → saved to `./submissions/username/`
···49111See `Dockerfile`, `docker-compose.yml`, or `battleship-arena.service` for systemd.
5011251113Web runs on port 8080, SSH on port 2222.
114114+115115+## Performance Stages
116116+117117+Submissions are categorized into stages based on average moves per game:
118118+119119+- **Expert** (<85 moves): Significantly better than random shooting
120120+- **Advanced** (85-95 moves): Better than random shooting
121121+- **Intermediate** (95-99 moves): Around random shooting performance
122122+- **Beginner** (≥99 moves): Worse than random shooting
123123+124124+*Benchmark: Pure random shooting averages 95.5 moves over 1000 games (range: 64-100)*
+272-6
database.go
···1414 Wins int
1515 Losses int
1616 AvgMoves float64
1717+ Stage string
1718 LastPlayed time.Time
1819}
1920···2526 Status string // pending, testing, completed, failed
2627}
27282929+type Tournament struct {
3030+ ID int
3131+ CreatedAt time.Time
3232+ Status string // active, completed
3333+ CurrentRound int
3434+ WinnerID int // ID of winning submission
3535+}
3636+3737+type BracketMatch struct {
3838+ ID int
3939+ TournamentID int
4040+ Round int
4141+ Position int
4242+ Player1ID int
4343+ Player2ID int
4444+ WinnerID int
4545+ Player1Wins int
4646+ Player2Wins int
4747+ Player1Moves int
4848+ Player2Moves int
4949+ Status string // pending, in_progress, completed
5050+ Player1Name string // For display
5151+ Player2Name string
5252+}
5353+2854func initDB(path string) (*sql.DB, error) {
2955 db, err := sql.Open("sqlite3", path+"?parseTime=true")
3056 if err != nil {
···4167 is_active BOOLEAN DEFAULT 1
4268 );
43697070+ CREATE TABLE IF NOT EXISTS tournaments (
7171+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7272+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
7373+ status TEXT DEFAULT 'active',
7474+ current_round INTEGER DEFAULT 1,
7575+ winner_id INTEGER,
7676+ FOREIGN KEY (winner_id) REFERENCES submissions(id)
7777+ );
7878+7979+ CREATE TABLE IF NOT EXISTS bracket_matches (
8080+ id INTEGER PRIMARY KEY AUTOINCREMENT,
8181+ tournament_id INTEGER,
8282+ round INTEGER,
8383+ position INTEGER,
8484+ player1_id INTEGER,
8585+ player2_id INTEGER,
8686+ winner_id INTEGER,
8787+ player1_wins INTEGER DEFAULT 0,
8888+ player2_wins INTEGER DEFAULT 0,
8989+ player1_moves INTEGER,
9090+ player2_moves INTEGER,
9191+ status TEXT DEFAULT 'pending',
9292+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
9393+ FOREIGN KEY (tournament_id) REFERENCES tournaments(id),
9494+ FOREIGN KEY (player1_id) REFERENCES submissions(id),
9595+ FOREIGN KEY (player2_id) REFERENCES submissions(id),
9696+ FOREIGN KEY (winner_id) REFERENCES submissions(id)
9797+ );
9898+4499 CREATE TABLE IF NOT EXISTS matches (
45100 id INTEGER PRIMARY KEY AUTOINCREMENT,
46101 player1_id INTEGER,
···54109 FOREIGN KEY (winner_id) REFERENCES submissions(id)
55110 );
56111112112+ CREATE INDEX IF NOT EXISTS idx_bracket_matches_tournament ON bracket_matches(tournament_id);
113113+ CREATE INDEX IF NOT EXISTS idx_bracket_matches_status ON bracket_matches(status);
114114+ CREATE INDEX IF NOT EXISTS idx_tournaments_status ON tournaments(status);
57115 CREATE INDEX IF NOT EXISTS idx_matches_player1 ON matches(player1_id);
58116 CREATE INDEX IF NOT EXISTS idx_matches_player2 ON matches(player2_id);
59117 CREATE INDEX IF NOT EXISTS idx_submissions_username ON submissions(username);
···69127 query := `
70128 SELECT
71129 s.username,
7272- COUNT(CASE WHEN m.winner_id = s.id THEN 1 END) as wins,
7373- COUNT(CASE WHEN (m.player1_id = s.id OR m.player2_id = s.id) AND m.winner_id != s.id THEN 1 END) as losses,
7474- AVG(CASE WHEN m.player1_id = s.id THEN m.player1_moves ELSE m.player2_moves END) as avg_moves,
7575- MAX(m.timestamp) as last_played
130130+ COUNT(CASE WHEN bm.winner_id = s.id THEN 1 END) as wins,
131131+ COUNT(CASE WHEN (bm.player1_id = s.id OR bm.player2_id = s.id) AND bm.winner_id != s.id AND bm.winner_id IS NOT NULL THEN 1 END) as losses,
132132+ AVG(CASE WHEN bm.player1_id = s.id THEN bm.player1_moves ELSE bm.player2_moves END) as avg_moves,
133133+ MAX(bm.timestamp) as last_played
76134 FROM submissions s
7777- LEFT JOIN matches m ON (m.player1_id = s.id OR m.player2_id = s.id)
135135+ LEFT JOIN bracket_matches bm ON (bm.player1_id = s.id OR bm.player2_id = s.id) AND bm.status = 'completed'
78136 WHERE s.is_active = 1
79137 GROUP BY s.username
8080- HAVING COUNT(m.id) > 0
138138+ HAVING COUNT(bm.id) > 0
81139 ORDER BY wins DESC, losses ASC, avg_moves ASC
82140 LIMIT ?
83141 `
···100158 // Parse the timestamp string
101159 e.LastPlayed, _ = time.Parse("2006-01-02 15:04:05", lastPlayed)
102160161161+ // Determine stage based on average moves
162162+ // Based on random AI benchmark: avg=95.459, p25=94, p75=99
163163+ if e.AvgMoves >= 99 {
164164+ e.Stage = "Beginner"
165165+ } else if e.AvgMoves >= 95 {
166166+ e.Stage = "Intermediate"
167167+ } else if e.AvgMoves >= 85 {
168168+ e.Stage = "Advanced"
169169+ } else {
170170+ e.Stage = "Expert"
171171+ }
172172+103173 entries = append(entries, e)
104174 }
105175···248318249319 return matches, rows.Err()
250320}
321321+322322+// Tournament functions
323323+324324+func getActiveTournament() (*Tournament, error) {
325325+ var t Tournament
326326+ var winnerID sql.NullInt64
327327+ err := globalDB.QueryRow(
328328+ "SELECT id, created_at, status, current_round, winner_id FROM tournaments WHERE status = 'active' ORDER BY id DESC LIMIT 1",
329329+ ).Scan(&t.ID, &t.CreatedAt, &t.Status, &t.CurrentRound, &winnerID)
330330+331331+ if err == sql.ErrNoRows {
332332+ return nil, nil
333333+ }
334334+ if winnerID.Valid {
335335+ t.WinnerID = int(winnerID.Int64)
336336+ }
337337+ return &t, err
338338+}
339339+340340+func getLatestTournament() (*Tournament, error) {
341341+ var t Tournament
342342+ var winnerID sql.NullInt64
343343+ err := globalDB.QueryRow(
344344+ "SELECT id, created_at, status, current_round, winner_id FROM tournaments ORDER BY id DESC LIMIT 1",
345345+ ).Scan(&t.ID, &t.CreatedAt, &t.Status, &t.CurrentRound, &winnerID)
346346+347347+ if err == sql.ErrNoRows {
348348+ return nil, nil
349349+ }
350350+ if winnerID.Valid {
351351+ t.WinnerID = int(winnerID.Int64)
352352+ }
353353+ return &t, err
354354+}
355355+356356+func createTournament() (*Tournament, error) {
357357+ result, err := globalDB.Exec("INSERT INTO tournaments (status, current_round) VALUES ('active', 1)")
358358+ if err != nil {
359359+ return nil, err
360360+ }
361361+362362+ id, _ := result.LastInsertId()
363363+ return &Tournament{
364364+ ID: int(id),
365365+ Status: "active",
366366+ CurrentRound: 1,
367367+ }, nil
368368+}
369369+370370+func updateTournamentRound(tournamentID, round int) error {
371371+ _, err := globalDB.Exec("UPDATE tournaments SET current_round = ? WHERE id = ?", round, tournamentID)
372372+ return err
373373+}
374374+375375+func completeTournament(tournamentID, winnerID int) error {
376376+ _, err := globalDB.Exec("UPDATE tournaments SET status = 'completed', winner_id = ? WHERE id = ?", winnerID, tournamentID)
377377+ return err
378378+}
379379+380380+func addBracketMatch(tournamentID, round, position, player1ID, player2ID int) error {
381381+ _, err := globalDB.Exec(
382382+ "INSERT INTO bracket_matches (tournament_id, round, position, player1_id, player2_id, status) VALUES (?, ?, ?, ?, ?, 'pending')",
383383+ tournamentID, round, position, player1ID, player2ID,
384384+ )
385385+ return err
386386+}
387387+388388+func getPendingBracketMatches(tournamentID int) ([]BracketMatch, error) {
389389+ query := `
390390+ SELECT
391391+ bm.id, bm.tournament_id, bm.round, bm.position,
392392+ bm.player1_id, bm.player2_id, bm.winner_id,
393393+ bm.player1_wins, bm.player2_wins,
394394+ bm.player1_moves, bm.player2_moves, bm.status,
395395+ s1.username as player1_name, s2.username as player2_name
396396+ FROM bracket_matches bm
397397+ JOIN submissions s1 ON bm.player1_id = s1.id
398398+ JOIN submissions s2 ON bm.player2_id = s2.id
399399+ WHERE bm.tournament_id = ? AND bm.status = 'pending'
400400+ ORDER BY bm.round, bm.position
401401+ `
402402+403403+ rows, err := globalDB.Query(query, tournamentID)
404404+ if err != nil {
405405+ return nil, err
406406+ }
407407+ defer rows.Close()
408408+409409+ var matches []BracketMatch
410410+ for rows.Next() {
411411+ var m BracketMatch
412412+ var winnerID sql.NullInt64
413413+ var player1Moves, player2Moves sql.NullInt64
414414+ err := rows.Scan(
415415+ &m.ID, &m.TournamentID, &m.Round, &m.Position,
416416+ &m.Player1ID, &m.Player2ID, &winnerID,
417417+ &m.Player1Wins, &m.Player2Wins,
418418+ &player1Moves, &player2Moves, &m.Status,
419419+ &m.Player1Name, &m.Player2Name,
420420+ )
421421+ if err != nil {
422422+ return nil, err
423423+ }
424424+ if winnerID.Valid {
425425+ m.WinnerID = int(winnerID.Int64)
426426+ }
427427+ if player1Moves.Valid {
428428+ m.Player1Moves = int(player1Moves.Int64)
429429+ }
430430+ if player2Moves.Valid {
431431+ m.Player2Moves = int(player2Moves.Int64)
432432+ }
433433+ matches = append(matches, m)
434434+ }
435435+436436+ return matches, rows.Err()
437437+}
438438+439439+func getAllBracketMatches(tournamentID int) ([]BracketMatch, error) {
440440+ query := `
441441+ SELECT
442442+ bm.id, bm.tournament_id, bm.round, bm.position,
443443+ bm.player1_id, bm.player2_id, bm.winner_id,
444444+ bm.player1_wins, bm.player2_wins,
445445+ bm.player1_moves, bm.player2_moves, bm.status,
446446+ s1.username as player1_name, s2.username as player2_name
447447+ FROM bracket_matches bm
448448+ LEFT JOIN submissions s1 ON bm.player1_id = s1.id
449449+ LEFT JOIN submissions s2 ON bm.player2_id = s2.id
450450+ WHERE bm.tournament_id = ?
451451+ ORDER BY bm.round, bm.position
452452+ `
453453+454454+ rows, err := globalDB.Query(query, tournamentID)
455455+ if err != nil {
456456+ return nil, err
457457+ }
458458+ defer rows.Close()
459459+460460+ var matches []BracketMatch
461461+ for rows.Next() {
462462+ var m BracketMatch
463463+ var player1Name, player2Name sql.NullString
464464+ var winnerID, player1Moves, player2Moves sql.NullInt64
465465+ err := rows.Scan(
466466+ &m.ID, &m.TournamentID, &m.Round, &m.Position,
467467+ &m.Player1ID, &m.Player2ID, &winnerID,
468468+ &m.Player1Wins, &m.Player2Wins,
469469+ &player1Moves, &player2Moves, &m.Status,
470470+ &player1Name, &player2Name,
471471+ )
472472+ if err != nil {
473473+ return nil, err
474474+ }
475475+ if winnerID.Valid {
476476+ m.WinnerID = int(winnerID.Int64)
477477+ }
478478+ if player1Moves.Valid {
479479+ m.Player1Moves = int(player1Moves.Int64)
480480+ }
481481+ if player2Moves.Valid {
482482+ m.Player2Moves = int(player2Moves.Int64)
483483+ }
484484+ if player1Name.Valid {
485485+ m.Player1Name = player1Name.String
486486+ }
487487+ if player2Name.Valid {
488488+ m.Player2Name = player2Name.String
489489+ }
490490+ matches = append(matches, m)
491491+ }
492492+493493+ return matches, rows.Err()
494494+}
495495+496496+func updateBracketMatchResult(matchID, winnerID, player1Wins, player2Wins, player1Moves, player2Moves int) error {
497497+ _, err := globalDB.Exec(
498498+ `UPDATE bracket_matches
499499+ SET winner_id = ?, player1_wins = ?, player2_wins = ?,
500500+ player1_moves = ?, player2_moves = ?, status = 'completed'
501501+ WHERE id = ?`,
502502+ winnerID, player1Wins, player2Wins, player1Moves, player2Moves, matchID,
503503+ )
504504+ return err
505505+}
506506+507507+func isRoundComplete(tournamentID, round int) (bool, error) {
508508+ var pendingCount int
509509+ err := globalDB.QueryRow(
510510+ "SELECT COUNT(*) FROM bracket_matches WHERE tournament_id = ? AND round = ? AND status != 'completed'",
511511+ tournamentID, round,
512512+ ).Scan(&pendingCount)
513513+514514+ return pendingCount == 0, err
515515+}
516516+