Overview
ElCity is a small, turn-based city builder that runs entirely inside Emacs. The UI is ASCII-based and optimized for terminal Emacs sessions. The core simulation is deterministic and pure, while the UI handles rendering and input.
Screenshots for those interested in fancy graphics:
This is an excercise in implementing the “functional core / imperative shell” architecture in a moderately sized project that with a developed UI. Every tile type is defined through a DSL, with a strong separation between state and effects. Most functions in the core are either pure or pure-ish.
Benefits to this approach:
- easy to debug
- scalable in terms of code: reduced cognitive load on both people and LLMs
- easy UX/UI as state is always localized
- easy to extend (with some discipline)
- easy to autotest
Requirements
- Emacs 30.1+
- Optional: Eask for dependency management
Installation
Clone the repository and add it to your Emacs load path. Example with
use-package:
(use-package elcity :load-path "/path/to/elcity" :commands (elcity-start))
If you are not using use-package, add the directory to load-path
and require the entry point:
(add-to-list 'load-path "/path/to/elcity") (require 'elcity) (elcity-start)
Quick Start
From the project root:
Or from Emacs: M-x elcity-start
Game Logic
- The game is turn-based. Press
nto advance one turn. - Funds increase each turn by: (Population / 2) + (Commercial level + Industrial level).
- City Hall is the source of road connectivity and is unique and non-demolishable.
- Roads are only connected if they trace through other roads to City Hall.
- Power plants provide a Manhattan-radius of 6 tiles.
- Zones grow by 1 level per turn if powered and road-adjacent.
- Zones decay by 1 level per turn if they lose power or road adjacency.
- Maximum zone level is 3.
- Residential (R) supplies Workers in a radius and dislikes Pollution.
- Industrial (I) supplies Goods and Pollution and requires Workers.
- Commercial (C) requires Workers and Goods.
Controls
Rselect Residential and place onceCselect Commercial and place onceIselect Industrial and place oncerselect Road and place oncepselect Power plant and place oncehselect City Hall and place onceSPCorRETplace selected tool at cursorddemolish at cursoruundo last actionnadvance one turn- Arrow keys move the cursor
ocycle overlays (goods, polution, connectivity, etc)
Basic Configuration
- Start with a custom map by calling
elcity-startwith a list of row strings. - Example call:
(elcity-start '("H=R0" "....")) - Map tokens are defined in tile definitions.
- Canonical tokens include
..,~~,==,PP,HH,R0-=R3=,C0-=C3=,I0-=I3=. - Short aliases are also accepted:
.,~,=,P,H,R,C,I. - Default map rows live in
elcity-maps.el(elcity-map-default-rows). - Default map size is set by
elcity-core-map-widthandelcity-core-map-height.
Development
make testruns ERT tests.make lintruns package lint, checkdoc, and byte compilation.make compilebyte-compiles non-test files.
Project Layout
elcity.elentry point that wires core and UIelcity-core.elpure simulation and state transitionselcity-tiles.eltile definitions and effect metadataelcity-ui.elUI shell and input handlingelcity-maps.elmap presetstest/ERT tests

