An SDL2 front-end in Common Lisp (SBCL, ocicl
for dependencies) for a Civilization-like 4X game — explore a wrapping world
dotted with tribal huts, found cities, climb the advance tree, govern, wage war,
do diplomacy / espionage / trade, manage pollution, and win by conquest or the
space race. It
renders a live civ-model game using sprites extracted from
the DOS game Sid Meier's Civilization (the sibling
civ-extract project), with the
torch graphic (sliced from the same sprite sheet) as the mouse cursor.
A developed realm: the player's cities Rome (walled) and Ostia, linked by a railroad and ringed with irrigated farmland and mined hills; a fort garrisons the southern approach, and the rival Veii (red borders) looms east with cavalry. Edge-blended terrain (TER257), rivers and resource specials (SP257); blue/red borders mark each civilization.
Cities rendered Civ1-style: a size box (population, in the owner's colour), the
city graphic, optional walls, a name label, and a black border when a
military unit garrisons it. Rome (size 7) and Nineveh (size 2) are both
defended; text uses a font extracted from the game's FONTS.CV.
Fog of war from the human's view: black = unexplored, dim = explored but out of sight, bright = currently visible. A scout's trail is dimmed behind it.
Each game opens on a setup screen: pick your civilization (which sets your city-name roster and a free starting advance), the difficulty (Chieftain … Emperor — higher levels let the AI research faster and turn warlike), the number of rivals, and the map size. ↑/↓ choose a row, ←/→ change its value, Enter starts, Esc quits.
Unlike Civilization (1991), where every civ begins knowing nothing, here each nation starts with one thematic root advance — Rome and Germany with Bronze Working, the Mongols with Horseback Riding, China and Egypt with Masonry, Greece and France with the Alphabet, and so on. (A deliberate deviation; all are prerequisite-free advances, so the tech tree stays consistent.)
A Civ1-style status pane sits in the top-left, reading out the state of your civilization at a glance: the date and turn, your total population (the classic 10k / 30k / 60k… per-city formula, summed and comma-grouped), your gold reserves and net income per turn, your government and the tax / luxury / science split, and your current research target with its percent progress and turns remaining to the next advance. A spaceship line appears once one is in flight. Press Ctrl+M to flip the whole pane (text and minimap) between the left and right edges of the screen.
Despotism Rome in 3700 BC: population 60,000, 144 gold (+2/turn), a 50/0/50 tax split, and Writing 56% researched — six turns out.
Below the readout sits an overview minimap: one block per tile coloured by terrain, cities as dots in their owner's colour, and a white rectangle marking the part of the world currently on screen. Under fog of war, tiles you have never seen stay black and explored-but-unseen tiles are dimmed, so the minimap doubles as your exploration record. Click anywhere on the minimap to recentre the main view there.
The whole world at a glance — green land, blue sea, rival cities as coloured dots, and the viewport box showing where the main view is looking.
Selecting a unit opens a Civ1-style info box in the bottom-left: the owner and unit type, attack/defense, moves left and HP, the city it garrisons (if any), and the terrain under it with its yields and defensive bonus. Below that is a row of every unit sharing the square — the selected one outlined in cyan, the rest in their owner's colour.
A wounded Legion (HP 6) selected inside Rome, stacked with a Warriors and a Phalanx; the info box shows its stats, the city and grassland terrain, and the three-unit garrison with the Legion highlighted.
Selecting a friendly city opens its build menu: every unit, improvement and
wonder the city's owner has unlocked through the advance tree,
priced in shields. One-per-game wonders are marked with *.
Rome's build menu set to the Pyramids (300 shields). The roster is gated by tech, so only what's been researched appears.
The menu doubles as a Civ1-style city screen: alongside the buildable roster it reads out the city's food balance and granary store, the current production and its progress, the trade split into tax/luxury/science, the workforce, the garrison, and the improvements already built. A work-radius map in the bottom-left shows the 21-tile fat cross around the city as real terrain, the centre tile holding the city and each worked tile framed in yellow and annotated with its yield icons (food, shields, trade — the original SP257 symbols).
Like Civ1, you manage the tiles by clicking them: click a worked tile to free it (its citizen leaves the land and becomes a specialist), click an idle tile in range to put a citizen to work it, and click the centre to hand the arrangement back to the governor's auto-optimiser. This lets you force a city to work its high-food tiles to grow, or its high-shield tiles to rush a wonder.
Across the top runs a City Resources pane: one row of icons per resource — food (wheat), production (shields), trade (arrows), gold, science and happiness (citizen faces) — each split by a small separator into what the city consumes (food eaten, gold upkeep, unhappy citizens) on the left and its surplus (food toward growth, net profit, happy citizens) on the right.
On the left, a Food Store pane is the granary box: a grid of food icons for the food banked toward the next citizen (bright = stored, dim = still needed), with a yellow line at the half level a granary keeps when the city grows.
Down the right edge, a Units pane lists the units the city supports (its home units), each as its sprite on the nation's colour with its per-turn shield maintenance (a shield icon and the cost; diplomats, caravans, and the units within a despotism/anarchy city's free allowance show 0).
Unit support follows Civ1: every unit a city builds is homed there and costs 1 shield per turn, deducted from the city's production. Under despotism and anarchy the first size units are free; other governments grant a small flat allowance (so revolting away from despotism doesn't bankrupt the army at once). Diplomats and caravans are always free. Settlers are a departing citizen — a size-2+ city loses a population point when it builds one (a lone size-1 city sends a homeless, upkeep-free settler so it can still expand) — and they also eat food (1 under despotism/anarchy, 2 otherwise) from their home city. If a city can no longer pay for its army, the unit farthest from home is disbanded. The AI is support-aware — it won't build a unit its home city can't sustain — so its production goes to growth instead of churning out units that just get disbanded.
Along the bottom of the panel runs a population bar, one little citizen
sprite per inhabitant (the original SP257 figures): happy, content and unhappy
faces for the tile-workers, then the specialists. Citizens who aren't working
a tile are specialists — press + / - to hire one (pulling a citizen off a
tile) or send one back to work, and click a specialist's figure to cycle its job
(its coloured underline shows it's clickable): an entertainer (Elvis, magenta)
makes 2 luxury — handy to break a city out of disorder — a taxman (gold)
makes 2 gold, and a scientist (Einstein, cyan) makes 2 beakers. Specialists
are always content, and a big city whose work radius is full of other cities'
tiles puts its surplus citizens to work as specialists automatically. The AI
manages its own: taxmen when its treasury is thin, scientists otherwise, and
entertainers to head off riots.
Size-7 Rome with Workers 5 specialists: 1 elvis 1 sci: a Temple at 8/40
shields, a Warriors garrison, Barracks/Granary/Marketplace/Palace built, a
population bar of one happy and four content workers followed by the Elvis and
scientist specialists, and the work-radius map showing the five worked tiles
(framed in yellow with their food/trade yield icons) around the city centre.
Production accumulates shields each turn until the item completes. Below, after Rome has grown and worked its tiles for ~150 turns, the Pyramids are done: they drop out of the buildable list (one per game) and appear in the Built: section with their effect.
After completion — Pyramids — boosts production now shows under Built:,
and AI cities (Akkad, Uruk) have grown alongside.
Every wonder now carries a live effect. Empire-wide: the Pyramids boost production, Hoover Dam acts as a power plant in every city (+50% shields), Hanging Gardens add content, the SETI Program adds +50% science, the Lighthouse trains veteran ships, Magellan's Expedition gives ships +1 move, Women's Suffrage halves military unhappiness, and the United Nations stays the AI's hand against war. In their own city: the Great Library, Copernicus' Observatory and Isaac Newton's College pile on science, the Colossus boosts trade, and Darwin's Voyage grants two free advances the moment it completes.
| key | action |
|---|---|
| left-click | select a unit (wakes a fortified one), open a friendly city's build menu, or — on empty ground — recenter the view there |
| click minimap | recenter the main view on that spot of the world |
| Ctrl+M | dock the status pane + minimap on the opposite (left/right) edge |
| O | open the Civilopedia (←/→ section, ↑/↓ scroll, Esc close) |
| Q | open the replay — score line-graph + city foundings (Esc closes) |
| 1–9 / click | in the build menu, choose a unit, improvement, or wonder (*) to build; Esc closes |
| arrow keys / numpad | move the selected unit — the numpad moves diagonally too (1/3/7/9) |
| Tab | cycle to the next active unit (skips fortified / out-of-moves) |
| W | wait — send the unit to the end of this turn's cycle |
| B | found a city (with a settlers unit) — prompts for a name, prefilled from your nation's roster (Enter confirms, Esc cancels) |
| F | fortify the selected unit (defense + faster healing) |
| Shift+D | disband the selected unit (recovers shields if in a city) |
| U | upgrade the selected obsolete unit to its successor (for gold, in a city) |
| N | detonate the selected nuclear missile (3×3 blast centred on its tile) |
| R / I / M | settlers: build road (then railroad once known) / irrigation / mine |
| T / A / C | settlers: build a fort / airbase / clear forest→plains, jungle/swamp→grassland |
| P | settlers: clean pollution on the tile |
| Z / X / D | diplomat: steal tech / sabotage / open the full spy menu (embassy, investigate, incite revolt, bribe…) |
| H / J | caravan: help build a wonder / establish a trade route |
| G, then left-click | send the selected unit to a tile (auto-paths each turn); the cursor becomes the Go arrow |
| X | auto-explore: the unit heads for the nearest unmapped tile each turn, stopping when there's nothing left, it's blocked, or it sights another civ (a diplomat sabotages instead) |
| V | start a revolution — pick a new government from the menu |
| Y / E | open the diplomacy (war / peace / alliance / gift) / trade (tech & gold) menu |
| research chooser | opens automatically when your research is idle — pick the next advance (1–9 / click; Esc takes the default) |
| , / . | shift the luxury rate down / up (trades against science) |
| [ / ] | shift the tax rate down / up — gold vs science (capped by your government) |
| Enter | end turn |
| S / L | save / load the game (single quicksave slot; also autosaves each turn) |
| ~ / K | open the Lisp console / start–stop a Slynk server (connect from Emacs) |
| ? | toggle the help overlay (a keybinding cheat-sheet) |
| Esc | close a menu / cancel a pending Go; otherwise quit |
Pressing ? brings up an in-window cheat-sheet of every key:
The map is covered by fog of war: unexplored tiles are black, tiles you've seen but can't currently see are dimmed, and enemy units/cities show only while in sight of one of your units or cities. Fog clears the moment a unit moves, not at end of turn. The selected unit shows an info box in the bottom-left (owner, type, attack/defense, moves, HP, city, terrain, and the units sharing its square) — see Selecting a unit above.
Scattered across the map are tribal huts. Move a unit onto one and it springs a surprise: a windfall of gold, a free advance, friendly mercenaries or wandering settlers joining your cause, an advanced tribe handing you a new city — or a barbarian ambush. Huts beside your own cities are tame (just friendly scouts bearing gold), so there's no farming them next to your capital.
Settlers can terraform the tile they stand on, over several turns during
which the unit holds position; the improvement then feeds straight into that
tile's yield: R builds a road (+1 trade) and, once Railroad is
researched, upgrades it to a railroad (+1 shield); I irrigates (+1
food); M mines (+1 shield); C clears vegetation/wetland to the land
beneath (forest→plains, jungle and swamp→grassland); T builds a
fort (+100% defense in the field, like city walls); and P cleans
pollution. Industrial cities throw off pollution once a civ reaches
Industrialization — a blight that halves a tile's output until a settler clears
it — and if it piles up across the map it triggers global warming, which
degrades random land terrain (grassland → plains → desert). City improvements
carry a gold upkeep charged every turn; a player who can't pay sells off
improvements (priciest first) until solvent.
Press S to save and L to load — because the whole civ-model state
is flat, serializable data (the RNG included), a loaded game continues rolling
identically, so save/load is fully deterministic. The game also autosaves
every turn, and the setup screen offers R to resume that autosaved game —
so quitting (or a crash) never loses more than the current turn.
Founding a city (B with a settler) prompts for its name, prefilled with
the next unused name from your nation's roster — Rome, Caesarea, Carthage… for
the Romans; Thebes, Memphis, Heliopolis… for the Egyptians — drawn from the
original Civilization name lists (per CivOne, CC0). Type to edit, Enter to found,
Esc to cancel. The AI names its cities the same way.
Each city's citizens are happy / content / unhappy: a few are content for
free and the rest start unhappy, quieted by temples, colosseums and cathedrals,
by luxuries (./, adjust the rate), by military garrisons under
martial-law governments, and by a handful of wonders. If a city has more unhappy
than happy citizens it falls into civil disorder (no production, trade or
growth); get at least half its citizens happy and it celebrates — under a
republic or democracy that triggers "We Love the King" rapture growth, +1
population every turn (up to the size-8 cap, raised to 12 by an aqueduct and
lifted entirely by a sewer system). The city
menu shows the mood and flags both states. Your government (V to start a
revolution — one turn of anarchy, then the new regime) sets the rules: despotism
docks busy tiles and bridles the economy; a republic or democracy adds trade but
bans martial law and suffers war weariness — every military unit out in the
field makes a citizen back home unhappy (one under a republic, two under a
democracy). Under a republic or democracy the Senate also constrains foreign
policy, as in Civ1: it forbids declaring war on a civ you're at peace with,
and forces you to accept an enemy's cease-fire — so the trade-rich free
governments come with a real diplomatic leash. Corruption, rate caps and
martial-law limits all vary by government, and the advance tree unlocks Monarchy,
Communism, The Republic and Democracy.
Research. You choose what to research: at the start of the game and each time you complete an advance, a chooser lists every advance whose prerequisites you now meet, and you pick the next one to pursue (Esc takes the first as a default, so research never stalls). The status pane tracks the percent progress toward it. The AI civilizations beeline their own priorities automatically.
Choosing the next advance — only those whose prerequisites are met are offered.
Civilopedia. Press O for a browsable in-game reference, read straight from
the rule tables: every advance with its prerequisites, every unit with
its attack/defense/move and cost, and every building and wonder with its
cost, required advance, and effect. Entries you can't have yet — an unresearched
advance, or a unit/building/wonder whose prerequisite advance you lack — are
dimmed, so the bright entries are what's available to you now. ←/→ switch
sections, ↑/↓ scroll, Esc closes.
The wonders section: cost, the advance that unlocks each, and its effect. The four whose advance this civ already has are bright; the rest are dimmed.
City improvements build up the economy, each with a live effect: a marketplace then a bank and stock exchange each add +50% gold (the first two boost luxuries too); a library, university and (wonder) Great Library each add +50% science; a courthouse halves corruption; a temple, colosseum and cathedral keep citizens content; and on the production side a factory adds +50% shields, a power plant (or hydro/nuclear plant, or the Hoover Dam) adds +50% more to a factory city, and a manufacturing plant adds +50% again — with mass transit and recycling centers to scrub the pollution that heavy industry brings. The AI builds these too: it beelines the economy advances and raises infrastructure in its cities even between wars.
Terrain uses the original edge-blending scheme (CivOne's algorithm): a land tile is a generic base plus a TER257 overlay chosen by a bitmask of which cardinal neighbours share its terrain (N=1 E=2 S=4 W=8), so mismatched edges feather into their neighbours; ocean tiles add coastline sub-tiles from the eight surrounding land directions. Rivers (SP257 connection variants, +1 trade) and per-terrain resource specials (SP257; e.g. grassland shields, ocean fish, mountain gold, jungle gems, arctic seals) are drawn as overlays and feed into tile yields. Units and cities use SP257 sprites. The world is generated as continents grown out of open ocean with a latitude climate — arctic and tundra at the poles, temperate grassland/forest/hills between, jungle and swamp at the equator — across all eleven terrain types.
Two freshly-generated 80×50 worlds (whole map, fog off — mountain ranges thread the continents, ice hugs the poles, fish dot the seas):
It is an 80×50 horizontal
cylinder (it wraps
east–west, with poles top and bottom); the window is a scrolling 32×20-tile
viewport — a golden-rectangle 8:5 shape (16 px tiles drawn at 2× → a 1024×640
window) — whose camera follows the selected
unit, and tiles are stitched seamlessly across the seam. Keyboard input is turned
into civ-model commands — the view never mutates the model directly. Several
rival civilizations (the default game has four) plus roving barbarians
are each run by a simple AI that issues the same commands — founding and
spacing out cities, setting production, exploring, researching, even swapping
advances — and take their turns automatically when you end yours. Each unit
spends its whole movement each turn; cities always keep a garrison
(rebuilding a defender whenever one is left empty); and the AI only declares
war on a rival it is at least as strong as, suing for peace once it is down
to half a rival's cities.
All of this requires first contact: two civilizations are strangers — no diplomacy, no war, not even listed in each other's diplomacy menu — until one's unit or city comes within sight of the other's. Only then can they trade, ally, or fight.
Each AI is dealt a personality at the start — aggressive, expansionist,
builder, or scientific — that biases how readily it goes to war, how far
it expands, how eagerly it raises wonders and infrastructure, how willing it is
to ally, and which advances it chases first, so a six-civ game has real variety.
A game-wide difficulty (:chieftain … :emperor, default Prince) sets the
AI handicap: at higher levels the rival civilizations research markedly faster
and go to war more readily. Surplus troops march on the nearest enemy city,
and rather than feed a defended city its attackers one at a time (the garrison
heals each turn, so that never breaks through), the AI holds and masses a stack
adjacent, then storms the city together once it can win the odds or has
enough force to overwhelm the garrison — so its wars actually take cities. At war
a coastal AI builds a transport, loads an invasion force, ferries it across
the sea, and lands the troops on your shore. It also fields its
whole toolbox — defending aircraft that fly home to refuel, diplomats
that spy on adjacent cities, caravans that open trade routes, and nuclear
missiles it flies at your cities and detonates. The AI also runs an
economy, not just an army: it beelines the advances that matter
(Monarchy, Trade, the Republic, the wonder techs), leads a revolution toward
the best government it can adopt (Monarchy while at war, Republic or Democracy in
peace), and invests its largest city in world wonders — whose effects are
all live (see below). For growth it raises the food/happiness buildings
(granary, aqueduct, colosseum) on its cities, sends surplus settlers out as
engineers to irrigate the land once it has expanded, and raises the luxury
rate when a city riots — so its cities actually climb past the early size
ceiling. examples/economy-demo.lisp plays a full
six-AI game and reports the governments adopted and wonders raised.
Combat is war-gated: you can't enter a tile held by a civ you're at
peace with, so you declare war first (the diplomacy menu, Y). Moving into
an enemy-occupied tile then triggers a Civ1-style fight to the death with
terrain, fortification, fort and city-wall bonuses; win and your unit advances
onto the cleared tile — and a unit that survives a battle may be promoted to
veteran (+50% combat), as in Civ1. A land unit that enters an enemy city
with no defenders left captures it — ownership flips, it shrinks by one, you
loot gold from the loser's treasury, its buildings and wonders carry over,
and trade routes break; a size-1 city is razed instead. Take a civ's last
city and you win by conquest. Capturing a civ's capital (the city that
holds the Palace) can spark a civil war: if the empire is still large, half
its cities break away as a new rebel civilization, and the survivors crown a new
capital. Damage carries between fights, so units heal between turns when they
stay put — fully in a city, faster when fortified (F).
Adjacent enemy units exert a zone of control.
Naval transport. Land units can't swim, but they can ride ships. Move a land unit onto an adjacent sea tile holding one of your transport-capable ships (trireme 2, sail 3, frigate 4, transport 8) to board it; the ship then carries its cargo wherever it sails. Move a passenger back onto land to disembark, or straight onto an adjacent enemy coastal tile for an amphibious assault. A ship only takes on cargo up to its capacity, and a ship sunk at sea drowns everyone aboard. (Carriers carry air units, which matters once air units gain range.)
Obsolescence. Advances retire old units: once you research the superseding
tech (Gunpowder retires Warriors/Phalanx, Conscription retires Legions and
Musketeers, Combustion retires Frigates and Ironclads, and so on) the unit drops
out of the build menu. Existing ones keep fighting, but you can upgrade one
to its successor while it sits in a city (U) for gold proportional to the cost
difference.
Air power. Fighters, bombers and the nuclear missile fly anywhere, but burn
fuel: each carries a limited number of turns of flight (2 / 5 / 8) and must
end a turn on a friendly city, airbase (a settler improvement, A) or
carrier to refuel — run dry in the air and the unit crashes. Carriers are
mobile airbases: parked planes refuel on them and ride along when the carrier
sails. The selected unit's info box shows its remaining Fuel.
Nuclear weapons. Once you research Rocketry you can build the nuclear
missile. Fly it next to a target and detonate (N): everything on the centre
tile and the eight around it is vaporised, any city in the blast is
devastated (population halved), and the ground is left with fallout
(pollution, which feeds global warming). The strike is indiscriminate — it kills
your own units too — and dropping it on a civ you were at peace with is an act of
war. Detonation plays a 40-frame mushroom-cloud animation
(assets/nuke.png) over the blast.
Diplomacy & trade. Relations are war, peace, or alliance per
pair. The Y diplomacy menu lists, for each rival, the moves open to you:
make peace or propose a cease-fire with an enemy (a timed truce the AI won't
break for a while), declare war on a neighbour, propose an alliance (an AI
weighs it — a civ welcomes an ally at least as strong as itself), break one,
demand tribute (a weaker, fearful AI pays gold; a confident one refuses), or
gift 50 gold as a goodwill gesture. Allies can't attack each other, and the
AI won't pounce on an ally (you can still betray one by declaring war). The AIs
also conduct their own diplomacy — forming alliances and agreeing cease-fires
among themselves as the game unfolds — and they make offers to you: a friendly
civ proposes an alliance, and one losing a war to you sues for a cease-fire. Such
an offer pops up a prompt you answer with Y (accept) or N (decline); it
isn't applied unless you agree. E opens a trade menu to swap advances or
buy/sell them for gold.
Per-rival diplomacy: at war with Egypt (make peace / cease-fire), allied with Zulu (break / declare war), at peace with Greece (war / alliance / tribute / gift).
An AI's offer is yours to accept or decline.
Diplomats (Z/X/D) run
the full espionage suite — steal tech, sabotage, establish an embassy,
investigate, incite a city to revolt, or bribe a unit — where a defended city
may catch the spy (lost, plus war). Caravans (H/J) help build a wonder
or open a trade route (a gold windfall plus recurring trade).
Winning. The game ends by conquest — last civilization with a city or unit standing — or the space race: build the Apollo Program (which, as in Civ1, reveals the entire map to every civilization), then with Space Flight (the structurals) and Fusion Power (the ship's fuel) assemble ten spaceship parts; the ship launches and, after its flight, arrives for the win.
Score. As in Civilization (1991), every civ earns a running score — +2 per happy citizen and +1 per content one, +3 per advance, +5 per wonder, +1 for each turn spent at war with nobody, and −1 per polluted tile around its cities (never below zero). When the game ends, the VICTORY / DEFEAT verdict is followed by the final standings, every civilization ranked by score in its own colour.
Greece wins by conquest with 90 points; the also-rans are ranked beneath.
Each finished game is also recorded to a Hall of Fame (civ-halloffame.lisp),
which keeps the ten best games by score — civ, score, difficulty, and year —
and lists the top five right under the final standings, beneath a heading set in
Civilization's big serif title font (FONTS.CV font 4) and framed by its
laurel-wreath glyphs, so a hard-won Emperor game earns a lasting place on the board.
Below this game's standings, the laurel-framed Hall of Fame ranks the best games played so far.
Replay. Press Q any time for a Civ1-style replay graph: each
civilization's Civilization-score plotted over the turns as a coloured line, with
a tick per city founding along the time axis (in the founding civ's colour),
so you can watch empires rise, overtake one another, and expand.
Six civilizations' scores climbing across 140 turns; the marks along the bottom are city foundings.
macOS / arm64 note: cl-sdl2's high-level event accessors (
scancode-value, the:x/:ydestructuring) read the wrongSDL_Eventstruct offsets against SDL2 2.x on Apple Silicon, so keyboard/mouse came through as garbage. The front end instead runs its ownSDL_PollEventloop and reads fields at the documented byte offsets (scancode @16, mouse x/y @20/24). Seesrc/main.lisp.
| system | what it is | depends on |
|---|---|---|
civ-model |
the pure game model — state + rules, no SDL, no I/O | (nothing) |
civ-lisp |
the SDL2 front-end (window, cursor, scaling, menus) | civ-model, sdl2, sdl2-image, slynk |
The model is deliberately independent of rendering so it can be tested headless
and reused by tools / AI / a future networked client. See
docs/MODEL.md for the design and
examples/model-demo.lisp for a runnable tour:
sbcl --non-interactive --load examples/model-demo.lisp- SBCL
- ocicl
- Native SDL2 + SDL2_image libraries (macOS:
brew install sdl2 sdl2_image)
cl-sdl2's FFI bindings need a larger heap to compile, so pass
--dynamic-space-size:
ocicl install # fetch sdl2 / sdl2-image (first time)
sbcl --dynamic-space-size 4096 --non-interactive --load run.lispA 1024×640 window opens with the torch image as the cursor (drawn centred so you can see it). Close the window or press Escape to quit.
Press ~ in the window for an in-game Lisp console (it binds *state* to
the live game), or attach a full REPL from Emacs: press K to start (or
stop) a Slynk server — equivalently (civ-lisp:start-slynk) / (stop-slynk),
from the ~ console or before (civ-lisp:main) — then M-x sly-connect
to localhost port 4005. The server runs in its own thread, so you can
inspect and reshape the running game while it plays.
*scale* is a global integer zoom for the whole app (default 2, so the 32×20
tile viewport fills a 1024×640 window with 32 px tiles). It multiplies two
separate things:
- Renderer drawing —
SDL_RenderSetScalemultiplies every render coordinate, so the app draws in logical space and SDL scales it up. - The mouse cursor — OS cursors are not touched by the renderer, so the
cursor surface is upscaled (nearest-neighbour via
SDL_UpperBlitScaled) before the colour cursor is created, to match.
Change the factor at runtime:
(setf civ-lisp:*scale* 3)
(civ-lisp:run) ; or (civ-lisp:run :scale 3)Dump a self-contained binary with save-lisp-and-die:
make # or: sbcl --dynamic-space-size 4096 --non-interactive --load build.lisp
./civ-lispThis produces a ~15 MB ./civ-lisp (a compressed SBCL core with the app and
all Lisp dependencies baked in). The native SDL2 / SDL2_image shared
libraries are not embedded — CFFI reloads them at startup, so they must be
installed on whatever machine runs the binary.
make targets: make (build), make run (run from source), make deps
(ocicl install), make clean.
The mouse cursors are sliced straight out of the sprite sheet: the torch is
SP257 cell (7,2) and the goto "GO" cursor is (2,2). cell-surface blits a
16×16 cell into a fresh SDL surface, which becomes a colour cursor via the raw
FFI call SDL_CreateColorCursor, then made active with SDL_SetCursor (cl-sdl2
doesn't wrap these high-level cursor calls, so we use the autowrap
sdl2-ffi.functions layer directly). See src/main.lisp.
(asdf:load-system :civ-lisp)
(civ-lisp:main) ; runs on the macOS main thread
;; or, with options:
(civ-lisp:run :width 800 :height 600)The cursor hotspot defaults to the top-left (0,0); the torch image is 13×14.




















