Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
All notable changes to RFoG. Dates are UTC. While RFoG is in alpha, rules,
numbers and ratings can change (or be reset) between versions.

## 0.1.0-alpha: first public release (unreleased)
## 0.0.3 (unreleased)

The first version anyone else plays: the whole game, in a browser and in a
terminal.
terminal. (0.0.2 was the first tagged build: the launch code plus the first
two contributions.)

### The game
- WEGO squad tactics: both sides plan in secret, then the turn resolves at
once (instant abilities, delayed abilities, movement, overwatch, attacks,
scoring), with initiative, height, cover, line of sight and fog.
- An 8×8 board for 1v1 with a raised core and two side objectives; larger
maps for 2v2 to 5v5.
- An 8×8 board for 1v1 with a raised core and two side objectives.
Experimental team modes (2v2 to 5v5) in the terminal client, not yet
balanced.
- Net scoring: each turn the side holding more objectives scores the
difference; a commander kill is worth 2; first to 5, or the lead after 12
turns.
Expand Down Expand Up @@ -50,11 +52,17 @@ terminal.
### Terminal
- Plays everything by keyboard (mouse and touch also work), from 80×24
monochrome up to truecolor; the same pixel figures in half blocks on big
tiles, damage and healing numbers, fibre runs under the floor, the
`fibre` theme matching the web app, and five others.
tiles, damage and healing numbers, fibre runs under the floor.
- Ten boards, the same in the terminal and the web app (fibre, graphite,
daylight, abyss, nord, gruvbox, catppuccin, tokyo-night, amber, green;
tokyo-night from a contributor's pull request), each readable
at 24-bit and 256 colours; plus mono for terminals without colour. A
custom `~/.config/rfog/theme.toml` makes a board from `light`, `dark` and
`wall`.
- A home screen laid out like the web app's (quick pairing grid, rated or
casual, side column, your games, leaderboard) on big terminals; a list on
small ones.
casual, side column, your games, leaderboard, and the top bar: Watch,
Learn, Leaderboard) on big terminals; a list on small ones. Back from an
online screen returns there.
- Tutorial, hotseat, roster, replays, settings, a command line (`:`).

### Online
Expand All @@ -65,7 +73,7 @@ terminal.
120s+ (long-haul, with time banks), 24h (daily), each **rated or casual**.
Glicko-2 ratings per category. Casual games welcome guests and fill empty
seats with bots after a wait.
- Draft with bans; 1v1 to 5v5 (team modes in the terminal for now).
- Draft with bans (each player bans one commander, then picks).
- **Challenge a friend** by link (web) or code (terminal); the codes work in
both. Challenges are casual: ratings come from pairing only.
- **Resume anywhere, play in one place:** a match follows the account between
Expand All @@ -91,3 +99,7 @@ terminal.
- One static binary for Linux, macOS, FreeBSD and Windows; SQLite or
Postgres; TLS; the rules travel to clients when they differ from the
client's own.
- Counts for the operator, with nothing kept about any player: the server
logs a `stats` line every 10 minutes (online, peak, by client, matches),
and `rfog stats` prints accounts, guests, online matches and players per
day from the store.
48 changes: 21 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ Free and open source. Play in the browser or in a terminal, on one account.</p>
</p>

<p align="center">
<img src="docs/screenshots/web-phone-home.jpg" width="31%" alt="The web app on a phone: quick pairing by clock, bots, pass and play, challenges">
<img src="docs/screenshots/web-phone-match.jpg" width="31%" alt="A match on a phone: the squad holding a contested objective">
<img src="docs/screenshots/web-phone-replay.jpg" width="31%" alt="A replay on a phone: an ability's blast and the damage it does">
<img src="https://raw.githubusercontent.com/rfog-org/.github/main/assets/screenshots/web-phone-home.jpg" width="31%" alt="The web app on a phone: quick pairing by clock, bots, pass and play, challenges">
<img src="https://raw.githubusercontent.com/rfog-org/.github/main/assets/screenshots/web-phone-match.jpg" width="31%" alt="A match on a phone: the squad holding a contested objective">
<img src="https://raw.githubusercontent.com/rfog-org/.github/main/assets/screenshots/web-phone-replay.jpg" width="31%" alt="A replay on a phone: an ability's blast and the damage it does">
</p>

## About
Expand All @@ -28,15 +28,15 @@ over signal nodes. Both sides give their orders in secret, then the turn
resolves for everyone at once. A match takes 10 to 15 minutes.

There is nothing to buy and nothing to unlock. Every commander is available
to every player. The project is funded by donations, as Lichess is.
to every player. No ads and no paid features, on the Lichess model.

### Features

- **Simultaneous turns.** Orders are planned in secret and resolved together
in a fixed order: instant abilities, delayed abilities, movement,
overwatch, attacks, scoring.
- **Ten commanders,** each with four abilities, chosen in a draft with bans.
Modes from 1v1 to 5v5.
- **1v1, ten commanders.** Each commander has four abilities; both players
ban one, then pick.
- **Two clients, one game.** A graphical web app for desktop and phone, and a
terminal client that runs natively or over SSH with nothing to install.
A match started on one continues on the other.
Expand All @@ -58,12 +58,12 @@ to every player. The project is funded by donations, as Lichess is.
## The terminal client

<p align="center">
<img src="docs/screenshots/terminal-match.png" width="88%" alt="A match in the terminal client: the board with pixel figures, the unit panel and the squad">
<img src="https://raw.githubusercontent.com/rfog-org/.github/main/assets/screenshots/terminal-match.png" width="88%" alt="A match in the terminal client: the board with pixel figures, the unit panel and the squad">
</p>

<p align="center">
<img src="docs/screenshots/terminal-home.png" width="49%" alt="The terminal home screen: quick pairing, games and leaderboard">
<img src="docs/screenshots/terminal-roster.png" width="49%" alt="The commander roster in the terminal">
<img src="https://raw.githubusercontent.com/rfog-org/.github/main/assets/screenshots/terminal-home.png" width="49%" alt="The terminal home screen: quick pairing, games and leaderboard">
<img src="https://raw.githubusercontent.com/rfog-org/.github/main/assets/screenshots/terminal-roster.png" width="49%" alt="The commander roster in the terminal">
</p>

The terminal client draws the same pixel figures as the web app in half
Expand Down Expand Up @@ -119,6 +119,17 @@ connects.

## Build and test

To work on it, with Go 1.25 or newer:

```sh
git clone https://github.com/rfog-org/rfog.git && cd rfog
go test ./... # the tests
go build -o rfog ./cmd/rfog
./rfog serve # a local server; the web app at http://localhost:8080/
```

Then, from the repository:

```sh
make # vet, test, build
make web # rebuild web/engine.wasm after engine or data changes
Expand All @@ -135,7 +146,7 @@ to change balance.

## Status

**Alpha.** The game is complete and playable. Numbers are still being
**Alpha.** 1v1 is complete and playable. Numbers are still being
tuned, and ratings may be reset before the beta. The
[changelog](CHANGELOG.md) lists what each version contains.

Expand All @@ -155,20 +166,3 @@ and monochrome portraits are from [Tiny Dungeon](https://kenney.nl/assets/tiny-d
by Kenney (CC0). Built with [Bubble Tea](https://github.com/charmbracelet/bubbletea),
[lipgloss](https://github.com/charmbracelet/lipgloss) and
[wish](https://github.com/charmbracelet/wish).

## Development setup

To work on RFoG locally:

1. Install Go 1.25 or newer.
2. Clone the repository:
git clone https://github.com/rfog-org/rfog.git
cd rfog
3. Run the test suite:
go test ./...
4. Build the project:
go build -o rfog ./cmd/rfog
5. Start the local server:
./rfog serve

For changes to the web client or game engine, see the contributing guide for the project layout and development workflow.
1 change: 1 addition & 0 deletions client/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func (o *onlineScreen) challengeKey(a *App, k tea.KeyMsg) (screen, tea.Cmd) {
switch {
case isKey(k, "esc"):
o.state = "menu"
return o.homeOr(a, o), nil
case isKey(k, "up", "down", "tab", "shift+tab"):
c.sel = 1 - c.sel
case isKey(k, "enter"):
Expand Down
29 changes: 20 additions & 9 deletions client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,13 @@ func (s Settings) Save() error {
}

// themeFile is the on-disk custom theme format (~/.config/rfog/theme.toml).
// light and dark (and optionally wall) make a board the way the built-in
// ones are made (render.BoardTheme); any other key overrides one colour.
type themeFile struct {
Name string `toml:"name"`
Mono bool `toml:"mono"`
Light string `toml:"light"`
Dark string `toml:"dark"`
Fg string `toml:"fg"`
Dim string `toml:"dim"`
Accent string `toml:"accent"`
Expand Down Expand Up @@ -139,15 +143,22 @@ func LoadUserTheme() string {
if _, err := toml.Decode(string(b), &tf); err != nil || tf.Name == "" {
return ""
}
base := render.GetTheme("gruvbox")
t := render.Theme{Name: tf.Name, Mono: tf.Mono, Fg: or(tf.Fg, base.Fg), Dim: or(tf.Dim, base.Dim),
Accent: or(tf.Accent, base.Accent), TeamA: or(tf.TeamA, base.TeamA), TeamB: or(tf.TeamB, base.TeamB),
Danger: or(tf.Danger, base.Danger), Warn: or(tf.Warn, base.Warn), Good: or(tf.Good, base.Good),
Objective: or(tf.Objective, base.Objective), Smoke: or(tf.Smoke, base.Smoke), Wall: or(tf.Wall, base.Wall),
Cover: or(tf.Cover, base.Cover), Fog: or(tf.Fog, base.Fog), Cursor: or(tf.Cursor, base.Cursor),
Highlight: or(tf.Highlight, base.Highlight), Border: or(tf.Border, base.Border)}
for i := range t.Ground {
t.Ground[i] = or(tf.Ground[i], base.Ground[i])
t := render.GetTheme("fibre")
if tf.Light != "" && tf.Dark != "" {
t = render.BoardTheme(render.Board{Name: tf.Name, Light: tf.Light, Dark: tf.Dark, Wall: or(tf.Wall, t.Wall)})
}
t.Name, t.Mono = tf.Name, tf.Mono
for _, f := range []struct {
to *string
v string
}{
{&t.Fg, tf.Fg}, {&t.Dim, tf.Dim}, {&t.Accent, tf.Accent}, {&t.TeamA, tf.TeamA}, {&t.TeamB, tf.TeamB},
{&t.Danger, tf.Danger}, {&t.Warn, tf.Warn}, {&t.Good, tf.Good}, {&t.Objective, tf.Objective},
{&t.Smoke, tf.Smoke}, {&t.Wall, tf.Wall}, {&t.Cover, tf.Cover}, {&t.Fog, tf.Fog}, {&t.Cursor, tf.Cursor},
{&t.Highlight, tf.Highlight}, {&t.Border, tf.Border},
{&t.Ground[0], tf.Ground[0]}, {&t.Ground[1], tf.Ground[1]}, {&t.Ground[2], tf.Ground[2]}, {&t.Ground[3], tf.Ground[3]},
} {
*f.to = or(f.v, *f.to)
}
render.RegisterTheme(t)
return t.Name
Expand Down
4 changes: 2 additions & 2 deletions client/draft.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (d *draftScreen) update(a *App, msg tea.Msg) (screen, tea.Cmd) {
m := newMatchScreen(a, d.rm)
return m.beginOrders(a, &t)
case matchEndMsg:
return newOnlineScreen(a), nil
return onlineBack(a), nil
case tea.KeyMsg:
heroes := d.st.Heroes
if len(heroes) == 0 {
Expand All @@ -90,7 +90,7 @@ func (d *draftScreen) update(a *App, msg tea.Msg) (screen, tea.Cmd) {
case isKey(t, "Q"):
d.rm.Leave()
a.rm = nil
return newOnlineScreen(a), nil
return onlineBack(a), nil
}
}
return d, nil
Expand Down
Loading
Loading