← back

Sea Battle

ENGS110 · Spring 2026 · C + Raylib

A graphical sea battle game with Player vs Player and Player vs AI modes.
4 weapon types: Basic, MissileV, MissileH, Bomb. Built in C using Raylib.

// source files ↓ download .zip

main.c.c
#include <stdlib.h>
#include <time.h>
#include "game.h"
#include "gui.h"

int main(void) {
    srand((unsigned int)time(0));
    Game game;
    Game_init(&game);
    Gui_run(&game);
    Player_freeInventory(&game.player1);
    Player_freeInventory(&game.player2);
    return 0;
}
cell.h.h
#ifndef CELL_H
#define CELL_H

typedef struct {
    int hasShip;
    int isHit;
} Cell;

void Cell_init(Cell *c);
void Cell_placeShip(Cell *c);
int Cell_attack(Cell *c);
int Cell_isOccupied(const Cell *c);
int Cell_isHit(const Cell *c);
char Cell_toChar(const Cell *c, int reveal);

#endif
cell.c.c
#include "cell.h"

void Cell_init(Cell *c) {
    c->hasShip = 0;
    c->isHit = 0;
}

void Cell_placeShip(Cell *c) {
    c->hasShip = 1;
}

int Cell_attack(Cell *c) {
    if (c->isHit) return -1;
    c->isHit = 1;
    return c->hasShip;
}

int Cell_isOccupied(const Cell *c) { return c->hasShip; }
int Cell_isHit(const Cell *c) { return c->isHit; }

char Cell_toChar(const Cell *c, int reveal) {
    if (c->isHit && c->hasShip) return 'X';
    if (c->isHit) return 'o';
    if (reveal && c->hasShip) return '#';
    return '~';
}
board.h.h
#ifndef BOARD_H
#define BOARD_H

#include "cell.h"

#define BOARD_SIZE 10
#define SHIPS_COUNT 10

typedef struct {
    Cell cells[BOARD_SIZE][BOARD_SIZE];
} Board;

void Board_init(Board *b);
int Board_canPlaceShip(const Board *b, int row, int col);
int Board_placeShip(Board *b, int row, int col);
int Board_receiveAttack(Board *b, int row, int col);
Cell* Board_getCell(Board *b, int row, int col);
const Cell* Board_getCellConst(const Board *b, int row, int col);
void Board_print(const Board *b, int reveal);

#endif
board.c.c
#include <stdio.h>
#include "board.h"

void Board_init(Board *b) {
    for (int r = 0; r < BOARD_SIZE; r++)
        for (int c = 0; c < BOARD_SIZE; c++)
            Cell_init(&b->cells[r][c]);
}

int Board_canPlaceShip(const Board *b, int row, int col) {
    if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) return 0;
    if (b->cells[row][col].hasShip) return 0;
    for (int dr = -1; dr <= 1; dr++) {
        for (int dc = -1; dc <= 1; dc++) {
            if (dr == 0 && dc == 0) continue;
            int nr = row + dr;
            int nc = col + dc;
            if (nr >= 0 && nr < BOARD_SIZE && nc >= 0 && nc < BOARD_SIZE)
                if (b->cells[nr][nc].hasShip) return 0;
        }
    }
    return 1;
}

int Board_placeShip(Board *b, int row, int col) {
    if (!Board_canPlaceShip(b, row, col)) return 0;
    Cell_placeShip(&b->cells[row][col]);
    return 1;
}

int Board_receiveAttack(Board *b, int row, int col) {
    if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) return -1;
    return Cell_attack(&b->cells[row][col]);
}

Cell* Board_getCell(Board *b, int row, int col) {
    if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) return 0;
    return &b->cells[row][col];
}

const Cell* Board_getCellConst(const Board *b, int row, int col) {
    if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) return 0;
    return &b->cells[row][col];
}

void Board_print(const Board *b, int reveal) {
    for (int r = 0; r < BOARD_SIZE; r++) {
        for (int c = 0; c < BOARD_SIZE; c++) {
            putchar(Cell_toChar(&b->cells[r][c], reveal));
            putchar(' ');
        }
        putchar('\n');
    }
}
weapon.h.h
#ifndef WEAPON_H
#define WEAPON_H

typedef struct Player Player;

typedef enum {
    WEAPON_BASIC,
    WEAPON_MISSILE_V,
    WEAPON_MISSILE_H,
    WEAPON_BOMB
} WeaponType;

typedef struct Weapon Weapon;
struct Weapon {
    WeaponType type;
    void (*use)(Weapon *w, Player *attacker, Player *defender, int row, int col);
    int price;
    const char *displayName;
};

Weapon* Weapon_createBasic(void);
Weapon* Weapon_createMissile(int vertical);
Weapon* Weapon_createBomb(void);
const char* Weapon_typeName(WeaponType type);

#endif
weapon.c.c
#include <stdlib.h>
#include "weapon.h"
#include "board.h"
#include "player.h"

static void basic_use(Weapon *w, Player *attacker, Player *defender, int row, int col) {
    (void)w; (void)attacker;
    int result = Board_receiveAttack(&defender->board, row, col);
    if (result == 1) Player_reduceShips(defender);
}

static void missile_use(Weapon *w, Player *attacker, Player *defender, int row, int col) {
    (void)attacker;
    if (w->type == WEAPON_MISSILE_V) {
        for (int r = 0; r < BOARD_SIZE; r++) {
            const Cell *cell = Board_getCellConst(&defender->board, r, col);
            if (cell && !cell->isHit) {
                int result = Board_receiveAttack(&defender->board, r, col);
                if (result == 1) Player_reduceShips(defender);
            }
        }
    } else {
        for (int c = 0; c < BOARD_SIZE; c++) {
            const Cell *cell = Board_getCellConst(&defender->board, row, c);
            if (cell && !cell->isHit) {
                int result = Board_receiveAttack(&defender->board, row, c);
                if (result == 1) Player_reduceShips(defender);
            }
        }
    }
}

static void bomb_use(Weapon *w, Player *attacker, Player *defender, int row, int col) {
    (void)w; (void)attacker;
    int startR = row - 1 < 0 ? 0 : row - 1;
    int endR = row + 1 >= BOARD_SIZE ? BOARD_SIZE - 1 : row + 1;
    int startC = col - 1 < 0 ? 0 : col - 1;
    int endC = col + 1 >= BOARD_SIZE ? BOARD_SIZE - 1 : col + 1;
    for (int r = startR; r <= endR; r++) {
        for (int c = startC; c <= endC; c++) {
            const Cell *cell = Board_getCellConst(&defender->board, r, c);
            if (cell && !cell->isHit) {
                int result = Board_receiveAttack(&defender->board, r, c);
                if (result == 1) Player_reduceShips(defender);
            }
        }
    }
}

Weapon *Weapon_createBasic(void) {
    Weapon *w = (Weapon *)malloc(sizeof(Weapon));
    if (!w) return 0;
    w->type = WEAPON_BASIC;
    w->use = basic_use;
    w->price = 0;
    w->displayName = "Basic";
    return w;
}

Weapon *Weapon_createMissile(int vertical) {
    Weapon *w = (Weapon *)malloc(sizeof(Weapon));
    if (!w) return 0;
    w->type = vertical ? WEAPON_MISSILE_V : WEAPON_MISSILE_H;
    w->use = missile_use;
    w->price = 70;
    w->displayName = vertical ? "MissileV" : "MissileH";
    return w;
}

Weapon *Weapon_createBomb(void) {
    Weapon *w = (Weapon *)malloc(sizeof(Weapon));
    if (!w) return 0;
    w->type = WEAPON_BOMB;
    w->use = bomb_use;
    w->price = 60;
    w->displayName = "Bomb";
    return w;
}

const char* Weapon_typeName(WeaponType type) {
    switch (type) {
        case WEAPON_BASIC: return "Basic";
        case WEAPON_MISSILE_V: return "MissileV";
        case WEAPON_MISSILE_H: return "MissileH";
        case WEAPON_BOMB: return "Bomb";
        default: return "Unknown";
    }
}
player.h.h
#ifndef PLAYER_H
#define PLAYER_H

#include "board.h"
#include "weapon.h"

#define MAX_INVENTORY 20
#define PLAYER_NAME_MAX 32

typedef struct Player Player;
struct Player {
    char name[PLAYER_NAME_MAX];
    Board board;
    int budget;
    int shipsCount;
    Weapon *inventory[MAX_INVENTORY];
    int inventorySize;
    int isAI;
};

void Player_init(Player *p, const char *name, int budget, int isAI);
int Player_placeShip(Player *p, int row, int col);
int Player_buyWeapon(Player *p, const char *weaponName);
int Player_useWeapon(Player *p, const char *weaponName, Player *defender, int row, int col);
void Player_reduceShips(Player *p);
int Player_isDefeated(const Player *p);
void Player_freeInventory(Player *p);

#endif
player.c.c
#include <string.h>
#include <stdlib.h>
#include "player.h"

void Player_init(Player *p, const char *name, int budget, int isAI) {
    strncpy(p->name, name, PLAYER_NAME_MAX - 1);
    p->name[PLAYER_NAME_MAX - 1] = '\0';
    Board_init(&p->board);
    p->budget = budget;
    p->shipsCount = 0;
    p->inventorySize = 0;
    p->isAI = isAI;
    for (int i = 0; i < MAX_INVENTORY; i++) p->inventory[i] = 0;
}

int Player_placeShip(Player *p, int row, int col) {
    if (Board_placeShip(&p->board, row, col)) {
        p->shipsCount++;
        return 1;
    }
    return 0;
}

static Weapon *createWeaponByName(const char *name) {
    if (strcmp(name, "MissileV") == 0) return Weapon_createMissile(1);
    if (strcmp(name, "MissileH") == 0) return Weapon_createMissile(0);
    if (strcmp(name, "Bomb") == 0) return Weapon_createBomb();
    if (strcmp(name, "Basic") == 0) return Weapon_createBasic();
    return 0;
}

int Player_buyWeapon(Player *p, const char *weaponName) {
    Weapon *w = createWeaponByName(weaponName);
    if (!w) return 0;
    if (w->type == WEAPON_BASIC) { free(w); return 0; }
    if (p->budget < w->price) { free(w); return 0; }
    if (p->inventorySize >= MAX_INVENTORY) { free(w); return 0; }
    p->budget -= w->price;
    p->inventory[p->inventorySize++] = w;
    return 1;
}

static int findWeaponIndex(Player *p, const char *name) {
    for (int i = 0; i < p->inventorySize; i++)
        if (strcmp(p->inventory[i]->displayName, name) == 0) return i;
    return -1;
}

int Player_useWeapon(Player *p, const char *weaponName, Player *defender, int row, int col) {
    Weapon *w = 0;
    int index = findWeaponIndex(p, weaponName);
    if (index >= 0) w = p->inventory[index];
    else w = Weapon_createBasic();
    if (!w) return 0;

    w->use(w, p, defender, row, col);

    if (index >= 0) {
        free(w);
        for (int j = index; j < p->inventorySize - 1; j++) p->inventory[j] = p->inventory[j + 1];
        p->inventorySize--;
    } else {
        free(w);
    }
    return 1;
}

void Player_reduceShips(Player *p) {
    if (p->shipsCount > 0) p->shipsCount--;
}

int Player_isDefeated(const Player *p) {
    return p->shipsCount <= 0;
}

void Player_freeInventory(Player *p) {
    for (int i = 0; i < p->inventorySize; i++) free(p->inventory[i]);
    p->inventorySize = 0;
}
game.h.h
#ifndef GAME_H
#define GAME_H
#include "player.h"

typedef enum { PHASE_MENU, PHASE_PLACING_P1, PHASE_PLACING_P2, PHASE_ATTACK, PHASE_GAMEOVER } GamePhase;
typedef enum { SELECT_BASIC, SELECT_MISSILE_V, SELECT_MISSILE_H, SELECT_BOMB } SelectedWeapon;

typedef struct {
    Player player1;
    Player player2;
    Player *currentAttacker;
    Player *currentDefender;
    GamePhase phase;
    int vsAI;
    int shipsToPlace;
    SelectedWeapon selectedWeapon;
    char message[160];
} Game;

void Game_init(Game *g);
void Game_reset(Game *g, int vsAI);
void Game_updateClick(Game *g, int boardIndex, int row, int col);
const char* Game_selectedWeaponName(const Game *g);
void Game_setSelectedWeapon(Game *g, SelectedWeapon weapon);
#endif
game.c.c
#include <stdio.h>
#include <string.h>
#include "game.h"
#include "ai.h"

static void Game_buyDefaultWeapons(Game *g) {
    Player_buyWeapon(&g->player1, "MissileV");
    Player_buyWeapon(&g->player1, "MissileH");
    Player_buyWeapon(&g->player1, "Bomb");
    if (g->vsAI) AI_buyWeapons(&g->player2);
    else {
        Player_buyWeapon(&g->player2, "MissileV");
        Player_buyWeapon(&g->player2, "MissileH");
        Player_buyWeapon(&g->player2, "Bomb");
    }
}

void Game_init(Game *g) {
    Game_reset(g, 0);
}

void Game_reset(Game *g, int vsAI) {
    Player_init(&g->player1, "Player 1", 300, 0);
    Player_init(&g->player2, vsAI ? "AI Player" : "Player 2", 300, vsAI);
    g->currentAttacker = &g->player1;
    g->currentDefender = &g->player2;
    g->phase = PHASE_MENU;
    g->vsAI = vsAI;
    g->shipsToPlace = SHIPS_COUNT;
    g->selectedWeapon = SELECT_BASIC;
    strcpy(g->message, "Choose Two Players or Player vs AI.");
}

const char* Game_selectedWeaponName(const Game *g) {
    switch (g->selectedWeapon) {
        case SELECT_MISSILE_V: return "MissileV";
        case SELECT_MISSILE_H: return "MissileH";
        case SELECT_BOMB: return "Bomb";
        default: return "Basic";
    }
}

void Game_setSelectedWeapon(Game *g, SelectedWeapon weapon) {
    g->selectedWeapon = weapon;
    snprintf(g->message, sizeof(g->message), "Selected weapon: %s", Game_selectedWeaponName(g));
}

static void Game_nextTurn(Game *g) {
    Player *tmp = g->currentAttacker;
    g->currentAttacker = g->currentDefender;
    g->currentDefender = tmp;
    if (g->vsAI && g->currentAttacker == &g->player2) {
        AI_attack(&g->player2, &g->player1);
        if (Player_isDefeated(&g->player1)) {
            g->phase = PHASE_GAMEOVER;
            strcpy(g->message, "AI Player wins! Press R to restart.");
            return;
        }
        g->currentAttacker = &g->player1;
        g->currentDefender = &g->player2;
    }
    snprintf(g->message, sizeof(g->message), "%s turn. Select weapon and click enemy board.", g->currentAttacker->name);
}

void Game_updateClick(Game *g, int boardIndex, int row, int col) {
    if (g->phase == PHASE_PLACING_P1) {
        if (boardIndex != 0) { strcpy(g->message, "Player 1: click your own board."); return; }
        if (Player_placeShip(&g->player1, row, col)) {
            g->shipsToPlace--;
            snprintf(g->message, sizeof(g->message), "Player 1 ships left to place: %d", g->shipsToPlace);
            if (g->shipsToPlace == 0) {
                if (g->vsAI) {
                    AI_placeShips(&g->player2);
                    Game_buyDefaultWeapons(g);
                    g->phase = PHASE_ATTACK;
                    g->currentAttacker = &g->player1;
                    g->currentDefender = &g->player2;
                    strcpy(g->message, "Attack phase. Click enemy board.");
                } else {
                    g->phase = PHASE_PLACING_P2;
                    g->shipsToPlace = SHIPS_COUNT;
                    strcpy(g->message, "Player 2: place your ships.");
                }
            }
        } else strcpy(g->message, "Invalid placement. Ships cannot touch.");
        return;
    }

    if (g->phase == PHASE_PLACING_P2) {
        if (boardIndex != 1) { strcpy(g->message, "Player 2: click your own board."); return; }
        if (Player_placeShip(&g->player2, row, col)) {
            g->shipsToPlace--;
            snprintf(g->message, sizeof(g->message), "Player 2 ships left to place: %d", g->shipsToPlace);
            if (g->shipsToPlace == 0) {
                Game_buyDefaultWeapons(g);
                g->phase = PHASE_ATTACK;
                g->currentAttacker = &g->player1;
                g->currentDefender = &g->player2;
                strcpy(g->message, "Attack phase. Player 1 starts.");
            }
        } else strcpy(g->message, "Invalid placement. Ships cannot touch.");
        return;
    }

    if (g->phase == PHASE_ATTACK) {
        int enemyBoard = (g->currentDefender == &g->player1) ? 0 : 1;
        if (boardIndex != enemyBoard) { strcpy(g->message, "Click the enemy board to attack."); return; }
        Player_useWeapon(g->currentAttacker, Game_selectedWeaponName(g), g->currentDefender, row, col);
        if (Player_isDefeated(g->currentDefender)) {
            g->phase = PHASE_GAMEOVER;
            snprintf(g->message, sizeof(g->message), "%s wins! Press R to restart.", g->currentAttacker->name);
            return;
        }
        g->selectedWeapon = SELECT_BASIC;
        Game_nextTurn(g);
    }
}
ai.h.h
#ifndef AI_H
#define AI_H
#include "player.h"
void AI_placeShips(Player *ai);
void AI_buyWeapons(Player *ai);
void AI_attack(Player *ai, Player *opponent);
#endif
ai.c.c
#include <stdlib.h>
#include "ai.h"

void AI_placeShips(Player *ai) {
    while (ai->shipsCount < SHIPS_COUNT) {
        int r = rand() % BOARD_SIZE;
        int c = rand() % BOARD_SIZE;
        Player_placeShip(ai, r, c);
    }
}

void AI_buyWeapons(Player *ai) {
    Player_buyWeapon(ai, "MissileV");
    Player_buyWeapon(ai, "MissileH");
    Player_buyWeapon(ai, "Bomb");
}

void AI_attack(Player *ai, Player *opponent) {
    int row, col, safety = 0;
    do {
        row = rand() % BOARD_SIZE;
        col = rand() % BOARD_SIZE;
        safety++;
    } while (Board_getCell(&opponent->board, row, col)->isHit && safety < 1000);
    const char *weapon = "Basic";
    if (ai->inventorySize > 0) weapon = ai->inventory[rand() % ai->inventorySize]->displayName;
    Player_useWeapon(ai, weapon, opponent, row, col);
}
gui.h.h
#ifndef GUI_H
#define GUI_H
#include "game.h"
void Gui_run(Game *game);
#endif
gui.c.c
#include "gui.h"
#include "raylib.h"
#include <stdio.h>

#define SCREEN_W 950
#define SCREEN_H 620
#define CELL_SIZE 35
#define P1_X 70
#define P2_X 560
#define BOARD_Y 150

static int pointInRect(int x, int y, Rectangle r) {
    return x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height;
}

static void drawButton(Rectangle r, const char *text, int active) {
    DrawRectangleRec(r, active ? SKYBLUE : LIGHTGRAY);
    DrawRectangleLinesEx(r, 2, DARKGRAY);
    DrawText(text, (int)r.x + 10, (int)r.y + 10, 18, BLACK);
}

static void drawBoard(Board *board, int x0, int y0, int reveal, const char *title) {
    DrawText(title, x0, y0 - 30, 20, BLACK);
    for (int r = 0; r < BOARD_SIZE; r++) {
        for (int c = 0; c < BOARD_SIZE; c++) {
            Cell *cell = Board_getCell(board, r, c);
            Color color = BLUE;
            if (cell->isHit && cell->hasShip) color = RED;
            else if (cell->isHit) color = GRAY;
            else if (reveal && cell->hasShip) color = GREEN;
            Rectangle rec = { (float)(x0 + c * CELL_SIZE), (float)(y0 + r * CELL_SIZE), CELL_SIZE, CELL_SIZE };
            DrawRectangleRec(rec, color);
            DrawRectangleLinesEx(rec, 1, BLACK);
        }
    }
}

static int getClickedBoardCell(int mouseX, int mouseY, int *boardIndex, int *row, int *col) {
    int starts[2] = {P1_X, P2_X};
    for (int b = 0; b < 2; b++) {
        int x0 = starts[b];
        if (mouseX >= x0 && mouseX < x0 + BOARD_SIZE * CELL_SIZE &&
            mouseY >= BOARD_Y && mouseY < BOARD_Y + BOARD_SIZE * CELL_SIZE) {
            *boardIndex = b;
            *row = (mouseY - BOARD_Y) / CELL_SIZE;
            *col = (mouseX - x0) / CELL_SIZE;
            return 1;
        }
    }
    return 0;
}

void Gui_run(Game *game) {
    InitWindow(SCREEN_W, SCREEN_H, "Sea Battle C - Raylib");
    SetTargetFPS(60);

    Rectangle twoPlayerBtn = {220, 80, 190, 45};
    Rectangle aiBtn = {450, 80, 190, 45};
    Rectangle basicBtn = {80, 540, 120, 40};
    Rectangle missileVBtn = {230, 540, 130, 40};
    Rectangle missileHBtn = {390, 540, 130, 40};
    Rectangle bombBtn = {550, 540, 120, 40};
    Rectangle restartBtn = {700, 540, 130, 40};

    while (!WindowShouldClose()) {
        if (IsKeyPressed(KEY_R)) Game_reset(game, game->vsAI);

        if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
            Vector2 m = GetMousePosition();
            int mx = (int)m.x, my = (int)m.y;

            if (game->phase == PHASE_MENU) {
                if (pointInRect(mx, my, twoPlayerBtn)) {
                    Game_reset(game, 0);
                    game->phase = PHASE_PLACING_P1;
                    game->shipsToPlace = SHIPS_COUNT;
                    snprintf(game->message, sizeof(game->message), "Player 1: place %d ships.", SHIPS_COUNT);
                } else if (pointInRect(mx, my, aiBtn)) {
                    Game_reset(game, 1);
                    game->phase = PHASE_PLACING_P1;
                    game->shipsToPlace = SHIPS_COUNT;
                    snprintf(game->message, sizeof(game->message), "Player 1: place %d ships. AI will place automatically.", SHIPS_COUNT);
                }
            } else {
                if (pointInRect(mx, my, basicBtn)) Game_setSelectedWeapon(game, SELECT_BASIC);
                else if (pointInRect(mx, my, missileVBtn)) Game_setSelectedWeapon(game, SELECT_MISSILE_V);
                else if (pointInRect(mx, my, missileHBtn)) Game_setSelectedWeapon(game, SELECT_MISSILE_H);
                else if (pointInRect(mx, my, bombBtn)) Game_setSelectedWeapon(game, SELECT_BOMB);
                else if (pointInRect(mx, my, restartBtn)) Game_reset(game, game->vsAI);
                else {
                    int boardIndex, row, col;
                    if (getClickedBoardCell(mx, my, &boardIndex, &row, &col)) {
                        Game_updateClick(game, boardIndex, row, col);
                    }
                }
            }
        }

        BeginDrawing();
        ClearBackground(RAYWHITE);
        DrawText("SEA BATTLE", 385, 20, 30, DARKBLUE);
        DrawText(game->message, 55, 55, 20, MAROON);

        if (game->phase == PHASE_MENU) {
            drawButton(twoPlayerBtn, "Two Players", 0);
            drawButton(aiBtn, "Player vs AI", 0);
            DrawText("Choose game mode.", 370, 155, 22, DARKGRAY);
        } else {
            int revealP1 = (game->phase == PHASE_PLACING_P1) || (game->phase == PHASE_GAMEOVER) || (game->currentAttacker == &game->player1);
            int revealP2 = (game->phase == PHASE_PLACING_P2) || (game->phase == PHASE_GAMEOVER) || (!game->vsAI && game->currentAttacker == &game->player2);
            drawBoard(&game->player1.board, P1_X, BOARD_Y, revealP1, game->player1.name);
            drawBoard(&game->player2.board, P2_X, BOARD_Y, revealP2, game->player2.name);
            drawButton(basicBtn, "Basic", game->selectedWeapon == SELECT_BASIC);
            drawButton(missileVBtn, "MissileV", game->selectedWeapon == SELECT_MISSILE_V);
            drawButton(missileHBtn, "MissileH", game->selectedWeapon == SELECT_MISSILE_H);
            drawButton(bombBtn, "Bomb", game->selectedWeapon == SELECT_BOMB);
            drawButton(restartBtn, "Restart", 0);

            char info[120];
            snprintf(info, sizeof(info), "%s attacking | Weapon: %s", game->currentAttacker->name, Game_selectedWeaponName(game));
            DrawText(info, 70, 505, 20, BLACK);
        }
        EndDrawing();
    }
    CloseWindow();
}
file_logger.h.h
#ifndef FILE_LOGGER_H
#define FILE_LOGGER_H
void Log_message(const char *msg);
#endif
file_logger.c.c
#include "file_logger.h"
#include <stdio.h>
#ifdef _WIN32
#include <direct.h>
#else
#include <sys/stat.h>
#endif

void Log_message(const char *msg) {
#ifdef _WIN32
    _mkdir("logs");
#else
    mkdir("logs", 0755);
#endif
    FILE *f = fopen("logs/log.txt", "a");
    if (!f) return;
    fprintf(f, "%s\n", msg);
    fclose(f);
}

// documentation

download documentation.pdf watch presentation technical design doc · 13 sections · C internals

// readme ↓ download README.md

README.md.md
# SeaBattle_C_Raylib

# SeaBattle — C + raylib

A Battleships game written in C using [raylib](https://www.raylib.com/) for graphics.

---

## Requirements

- Linux (Ubuntu/Debian or any distro with GCC and make)
- GCC
- make
- raylib

---

## Setup

Install the required Linux dependencies:

```bash
sudo apt update

sudo apt install build-essential git make gcc \
libgl1-mesa-dev libx11-dev \
libxrandr-dev libxinerama-dev \
libxcursor-dev libxi-dev
```

Then build and run the game:

```bash
make run
```

If raylib is not installed on the system, the Makefile automatically:
1. Clones the official raylib repository
2. Builds raylib locally
3. Links the game against the local build

No additional manual setup is required.

---

## Build commands

| Command      | Description                        |
|--------------|------------------------------------|
| `make`       | Compile the project                |
| `make run`   | Compile and launch the game        |
| `make clean` | Remove compiled objects and binary |

---

## Project structure

```
SeaBattle_C_Raylib/
├── src/          # C source and header files
├── Makefile
└── README.md
```

---

## Libraries used

| Library    | Purpose                        |
|------------|--------------------------------|
| raylib     | Window, input, and rendering   |
| libGL      | OpenGL (via Mesa on Linux)     |
| libm       | Math                           |
| libpthread | Threads                        |
| libdl      | Dynamic linking                |
| librt      | POSIX real-time extensions     |
| libX11     | X Window System                |

ENGS110 · Spring 2026 · AUA