A 2D drone simulation built as a set of independent Linux processes that talk to each other only through POSIX anonymous pipes, coordinated by a central blackboard server. The drone is not moved directly by the keyboard: key presses become force inputs to a mass–damper model, and targets and obstacles exert attractive and repulsive forces on the drone through an artificial potential field.
Written for the Advanced and Robot Programming course at the University of Genoa (UniGe), winter semester 2023/2024.
Amir Mahdi Matin — s5884715
- What this project actually does
- Tech stack
- Architecture
- Message protocol
- Drone dynamics
- Game rules and scoring
- Build and run
- Controls
- Repository layout
- Known limitations
- Other branches
- License
main forks seven child processes, each wrapped in its own Konsole terminal window so
that its stdout is visible while the simulation runs. The processes exchange fixed-size
tagged text messages over eleven anonymous pipes created by main before the fork()s;
each child receives its own pipe file descriptors as a space-separated string in argv[1].
The interface draws an ncurses window; the drone integrates its own equations of motion; targets and obstacles are generated by separate processes; and the server routes every message to the processes that need it.
| Area | What is used |
|---|---|
| Language | C (C17, GCC) |
| Concurrency model | Multi-process — fork() + execvp() |
| IPC | POSIX anonymous pipes (pipe()), non-blocking multiplexing with select() |
| Shared state | POSIX shared memory (shm_open / mmap) + named semaphores (sem_open) for watchdog PID handover |
| Signals | sigaction with SA_SIGINFO (SIGINT, SIGUSR1, SIGUSR2) |
| UI | ncurses |
| Process display | Konsole (one terminal window per process) |
| Build | GNU Make |
┌──────────┐
│ main │ creates 11 pipes, forks 7 children
└──────────┘
│
┌──────────┬─────────┼──────────┬───────────┬──────────┐
▼ ▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌────────┐ ┌─────┐ ┌──────────┐ ┌─────────┐ ┌────────┐
│interface│ │key_mgr │ │drone│ │ targets │ │obstacles│ │watchdog│
└─────────┘ └────────┘ └─────┘ └──────────┘ └─────────┘ └────────┘
│ ▲ │ │ ▲ │ ▲ │ ▲
│ │ │ │ │ │ │ │ │
│ └────────┼────────┴─┴────────┴─┴─────────┴─┘
│ │ ┌────────┐
└───────────┴─────────────▶│ server │ (blackboard: routes all traffic)
└────────┘
── two "serverless" pipes bypass the server ──
interface ──raw keypress──▶ key_manager
interface ──lowest target──▶ drone
main (src/main.c) — Creates all pipes with pipe(), then forks each child
and execvp()s konsole -e ./build/<process> "<fds>". It installs a SIGINT handler that
kills every child and closes every pipe, then wait()s for all children to exit.
server (src/server.c) — The blackboard. It holds the read ends of the five
inbound pipes (key manager, interface, drone, obstacles, targets) and polls each with
select() and a zero timeout in one loop. Routing is decided by the leading tag of each
message: keyboard actions go to the drone, interface messages go to the drone (and, for
resize events, to targets and obstacles), drone positions go to the interface, and obstacle
data goes to both the interface and the drone. The server also creates the shared memory
segments and semaphores used for watchdog PID registration.
interface (src/interface.c) — The ncurses front end and the game-logic
owner. It reports the initial drone position and terminal size, redraws the window each
iteration (drone + in blue, obstacles * in red, targets as digits in green, score in the
title bar), captures key presses with non-blocking getch(), tracks the score, detects
target capture and obstacle collision, and re-scales target coordinates when the terminal is
resized. It also sends the currently active target straight to the drone over a dedicated pipe.
key_manager (src/key_manager.c) — Blocks on select() waiting for a
key character from the interface, maps it to a force pair through determine_action(), and
forwards non-empty actions to the server.
drone (src/drone.c) — Owns the physics. It integrates position and velocity
with an explicit Euler step, sums the keyboard force with the potential-field force from the
active target and all known obstacles, clamps the drone to the window bounds, and publishes
its rounded integer position back to the server every D_T (0.1 s).
targets (src/targets.c) — Waits for the screen dimensions, then generates
MAX_TARGETS (8) targets once. Placement combines randomness with structure: a shuffled
order distributes targets across a 3×2 sector grid, so no region of the screen is left empty.
obstacles (src/obstacles.c) — Waits for the screen dimensions, then adds
one obstacle per second up to MAX_OBSTACLES (7). Each obstacle is given a lifetime between
MIN_SPAWN_TIME (4 s) and MAX_SPAWN_TIME (10 s); when it expires the obstacle is
regenerated at a new random location, so the obstacle field keeps changing. The full obstacle
list is re-sent to the server on every cycle.
watchdog (src/watchdog.c) — Collects the PIDs of the server, interface,
key manager and drone. Because every process actually runs inside a Konsole wrapper, the PID
returned by fork() in main is not the PID of the simulation process, so each process
registers its own PID by writing it into the /wd_pid_1 shared memory segment under a
three-semaphore handshake (publish_pid_to_wd() in src/util.c). See
Known limitations for what the watchdog does not do.
logger (src/logger.c) — A shared-memory log consumer that writes timestamped
entries to a file. It is not launched — the call in main is commented out. See
Known limitations.
All messages are fixed-length (MSG_LEN = 240 bytes) ASCII strings, dispatched on their
first characters.
| Message | From → To | Meaning |
|---|---|---|
I1:x,y,sx,sy |
interface → server → drone | Initial drone position and screen size |
I2:sx,sy |
interface → server → drone, targets, obstacles | Screen size changed (resize) |
K:x,y |
key_manager → server → drone | Keyboard force input |
D:x,y |
drone → server → interface | Current drone position |
T[n]x,y|x,y|… |
targets → server → interface | The full target list |
O[n]x,y|x,y|… |
obstacles → server → interface, drone | The full obstacle list |
<char> |
interface → key_manager | Raw key press (bypasses the server) |
x,y |
interface → drone | Coordinates of the lowest-ID target (bypasses the server) |
The drone is a point mass with viscous damping, integrated with an explicit Euler step
(differential_equations() in src/drone.c):
a = (F_input + F_external − DAMPING · v) / MASS
v ← v + a · Δt
p ← p + v · Δt
with MASS = 1.0, DAMPING = 1, Δt = 0.1 s, input force capped at F_MAX = 30.0 and
external force capped at EXT_FORCE_MAX = 40.0. Position is clamped to the window, so the
window borders act as walls.
F_external is an artificial potential field in the style of Khatib's model
(calculate_extenal_force()): targets attract, obstacles repel, and both use the gradient form
F = Coefficient · (1/d − 1/ρ) · (1/d²)
with Coefficient = 400.0 and an influence radius start_distance = 5.0; distances below
min_distance = 1.0 are clamped to avoid a singularity. Only obstacles and the single active
(lowest-ID) target contribute.
Because forces accumulate rather than setting velocity directly, the drone has inertia — it keeps drifting after the keys are released.
Targets must be reached in ID order; the interface always sends only the lowest remaining target ID to the drone, and only that one attracts it. Scoring is implemented in src/interface.c:
| Time to reach the target (loop ticks of ~10 ms) | Points |
|---|---|
| under ~5 s | +10 |
| ~5–10 s | +8 |
| ~10–20 s | +6 |
| over ~20 s | +2 |
Overlapping an obstacle costs −5. When the last target is collected the interface draws a final window with the score and closes after a 5-second countdown.
sudo apt-get install build-essential libncurses-dev konsolekonsole is required: main launches every process inside a Konsole window. ncurses headers
are required to build the interface.
make # builds all executables into ./buildmake run # equivalent to ./build/mainSeven Konsole windows open — one per process. Play in the interface window. Ctrl+C in the
main terminal shuts everything down.
make cleanThe nine keys around s map to the eight compass directions plus stop:
q w e
a s d
z x c
| Key | Action | Key | Action |
|---|---|---|---|
w |
up | q |
up-left |
x |
down | e |
up-right |
a |
left | z |
down-left |
d |
right | c |
down-right |
s cancels all input force. Because of inertia and damping the drone coasts to a stop rather
than halting instantly. Any other key is ignored.
src/ Process sources — main, server, interface, key_manager,
drone, targets, obstacles, watchdog, logger, util
include/ Headers; constants.h holds the IPC names and shared structs,
drone.h the physics constants, targets.h / obstacles.h the
generation parameters
Backup/ Earlier revisions of server.c and targets.c (not built)
Test/ Scratch file (empty, not built)
Makefile One target per executable
These are honest notes about the state of the code, not planned features:
- The watchdog does not monitor anything. It successfully collects the four PIDs at
startup, but its main loop is an empty
usleep()— noSIGUSR1heartbeat is ever sent, and the per-process counters are never incremented. TheSIGUSR1→SIGUSR2reply handlers are implemented in every monitored process, so only the polling half is missing. The watchdog also does not track the targets and obstacles processes, which never register a PID. - The logger is not wired up. Its launch in
mainis commented out,write_message_to_logger()is never called from any running code path, and thebuild/logsdirectory the logger writes into is not created by the Makefile. - Leftover shared-memory design.
constants.hstill defines shared memory and semaphore names for keyboard, position and action state from an earlier shared-memory-based version. They are unused: the current build communicates through pipes. make cleandoes not remove thetargetsandobstaclesbinaries.- The drone must run in a terminal large enough for the ncurses window; there is no minimum size check.
The main branch holds the pipe-based version described above. Two further branches exist and
have not been merged:
Improve-Assignment-2— a refactored version of this same design.Assignment-3— a substantially larger version that adds TCP socket communication, splitting the system so that targets and obstacles can run as clients against a remote server, selected through aconfiguration.txtfile. Its own README marks it as incomplete.
MIT — see LICENSE.