Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🎮 Multiplayer Real-Time Tic-Tac-Toe Server

A concurrent, multi-process tic-tac-toe game server written in C++ that communicates with player processes over Unix domain sockets using a custom binary protocol. Supports configurable grid sizes, variable streak lengths, and any number of players — all running as independent processes.

Built to explore Unix systems programming concepts: process management, IPC via socketpair, non-blocking I/O with poll(), and binary protocol design.


Architecture Overview

┌────────────────────────────────────────────────┐
│                   SERVER PROCESS                │
│                                                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────────┐  │
│  │  Config   │  │  Game    │  │  Event Loop  │  │
│  │  Parser   │→ │  State   │← │  (poll)      │  │
│  └──────────┘  └──────────┘  └──────┬───────┘  │
│                                     │           │
│         ┌──────────┬────────────────┤           │
│         │          │                │           │
│    ┌────┴────┐┌────┴────┐     ┌────┴────┐      │
│    │SocketPair││SocketPair│     │SocketPair│     │
│    │  (fd)   ││  (fd)   │     │  (fd)   │      │
│    └────┬────┘└────┬────┘     └────┬────┘      │
└─────────┼──────────┼───────────────┼───────────┘
          │          │               │
     ┌────┴────┐┌────┴────┐    ┌────┴────┐
     │ Player  ││ Player  │    │ Player  │
     │ Process ││ Process │    │ Process │
     │  (A)    ││  (B)    │    │  (N)    │
     └─────────┘└─────────┘    └─────────┘

The server forks a child process for each player, connecting them through bidirectional Unix domain sockets (socketpair). The main event loop uses poll() to concurrently monitor all player sockets without blocking.


Key Concepts

🔀 Inter-Process Communication (IPC)

Each player runs as a separate process. Communication happens through socketpair(AF_UNIX, SOCK_STREAM, PF_UNIX, fd), which creates a pair of connected Unix domain sockets — one end for the server, one for the player.

// Create bidirectional pipe using socketpair
int pipe_fd[2];
socketpair(AF_UNIX, SOCK_STREAM, PF_UNIX, pipe_fd);
// pipe_fd[0] → server end
// pipe_fd[1] → player end (redirected to stdin/stdout)

After fork(), the child process redirects its stdin/stdout to the socket and calls execvp() to replace itself with the player executable:

// Child process setup
dup2(pipe_fd[1], STDIN_FILENO);   // Player reads from socket
dup2(pipe_fd[1], STDOUT_FILENO);  // Player writes to socket
execvp(executable, args);          // Replace with player binary

⚡ Non-Blocking I/O with poll()

The server must handle messages from multiple players concurrently without blocking on any single player. This is achieved using poll():

std::vector<pollfd> poll_fds(player_count);
for (int i = 0; i < player_count; i++) {
    poll_fds[i].fd = players[i].pipe_fd[0];
    poll_fds[i].events = POLLIN;  // Monitor for incoming data
}

// Main game loop
while (game_running) {
    int ret = poll(poll_fds.data(), poll_fds.size(), 1); // 1ms timeout
    for (int i = 0; i < player_count; i++) {
        if (poll_fds[i].revents & POLLIN) {
            // Handle message from player i
        }
    }
}

The 1ms timeout prevents CPU hogging while keeping the server responsive. This pattern is the foundation of most event-driven server architectures.

📨 Binary Protocol Design

The server and players communicate using fixed-size binary structs sent over the socket. This avoids the overhead of text parsing and ensures consistent message boundaries.

Client → Server Messages:

Message Type Fields Description
START type Player signals it's ready
MARK type, position {x, y} Player makes a move

Server → Client Messages:

Message Type Fields Description
RESULT type, success, filled_count + grid data Response to START/MARK
END type, success Game over notification
┌─────────────┐      START        ┌─────────────┐
│   Player    │  ──────────────►  │   Server    │
│   Process   │  ◄──────────────  │   Process   │
│             │     RESULT +      │             │
│             │     grid_data[]   │             │
│             │                   │             │
│             │    MARK(x,y)      │             │
│             │  ──────────────►  │             │
│             │  ◄──────────────  │             │
│             │     RESULT +      │             │
│             │     grid_data[]   │             │
│             │                   │             │
│             │  ◄──────────────  │             │
│             │       END         │             │
└─────────────┘                   └─────────────┘

🏆 Win Detection Algorithm

The server checks four directions after every valid move to detect a winning streak:

  1. Horizontal → (x, y), (x+1, y), (x+2, y), ...
  2. Vertical → (x, y), (x, y+1), (x, y+2), ...
  3. Diagonal ↘ → (x, y), (x+1, y+1), (x+2, y+2), ...
  4. Diagonal ↙ → (x, y), (x-1, y+1), (x-2, y+2), ...

For each direction, the algorithm scans every possible starting position and checks if streak_size consecutive cells contain the same character. This runs in O(rows × cols × streak_size) time per check.


Building & Running

Prerequisites

  • GCC/G++ with C++11 support
  • Unix-like system (Linux / macOS) — requires POSIX APIs

Build

make        # Compiles and links → produces 'server' executable
make clean  # Removes build artifacts

Run

The server reads its configuration from stdin. You can pipe a config file or type it in manually:

echo "3 3 3 2 A 2 ./player_a arg1 B 2 ./player_b arg1" | ./server

Configuration Format

<grid_width> <grid_height> <streak_size> <player_count>
<char_1> <arg_count_1> <executable_1> [args...]
<char_2> <arg_count_2> <executable_2> [args...]
...
Field Description
grid_width Number of columns in the game grid
grid_height Number of rows in the game grid
streak_size Consecutive marks needed to win
player_count Number of players
char Single letter (A–Z, a–z) representing the player
arg_count Total number of arguments for the player executable
executable Path to the player process binary
args Command-line arguments for the player

Example — Classic 3×3 Tic-Tac-Toe with 2 players:

3 3 3 2
X 1 ./player_x
O 1 ./player_o

Example — 5×5 grid, streak of 4, 3 players:

5 5 4 3
A 1 ./smart_player
B 1 ./random_player
C 1 ./random_player

Project Structure

.
├── server.cpp          # Main server: config parsing, process management,
│                       #   event loop, game logic, protocol handling
├── game_structs.h      # Shared data structures (coordinate, messages, grid_data)
├── print_output.h      # Logging function declarations
├── print_output.c      # Logging implementation (prints protocol messages)
├── Makefile            # Build configuration
└── .gitignore

How It Works — Step by Step

  1. Parse Configuration — Read grid dimensions, streak size, player info from stdin
  2. Fork Players — For each player: create a socketpair, fork(), redirect child's stdin/stdout to the socket, exec() the player binary
  3. Enter Event Loop — Use poll() to monitor all player sockets with a 1ms timeout
  4. Handle Messages:
    • On START → respond with current game state (RESULT + filled positions)
    • On MARK(x, y) → validate move, update grid if valid, check for winner, respond with RESULT
  5. End Game — When a player wins or the grid is full, send END to all players, print the result, and reap child processes with wait()

Implementation Notes

  • The game is not turn-based: any player can send a MARK at any time, making concurrency handling critical
  • Each grid cell can only be marked once — subsequent marks on the same cell are rejected
  • The server maintains a running list of filled positions to efficiently respond to RESULT queries without rescanning the entire grid
  • Child processes are properly reaped using wait() to prevent zombie processes
  • The server processes messages in the order they are received by poll(), ensuring fairness

License

This project is open source and available under the MIT License.

About

A concurrent, multi-process real-time tic-tac-toe server in C++ using Unix IPC (socketpair, fork/exec, poll)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages