forked from retoor/devplacepy
feat: add alt text extraction from URLs for images and improve CSS variable usage for progress bars
Add `_alt_from_url` helper in Python rendering and `altFromUrl` in JS ContentRenderer to generate descriptive alt attributes from image filenames, replacing empty alt strings. Update image embed templates and Avatar component to use derived alt text. Migrate inline `style.width` assignments to CSS custom properties (`--poll-pct`, `--hud-xp`, `--quest-pct`, `--bar-pct`, `--quota-pct`) across PollManager, GameFarm, and profile/quest CSS for better maintainability. Introduce new admin, docs, projects, services, and base CSS utility classes. Add "Code Farm" docs page entry.
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
{% set api_key = user.get('api_key') if user else 'YOUR_API_KEY' %}
|
||||
{% set uname = user.get('username') if user else 'YOUR_USERNAME' %}
|
||||
<div class="docs-content" data-render>
|
||||
# Code Farm
|
||||
|
||||
Code Farm is a cooperative idle game built into DevPlace. Every member owns a farm of plots,
|
||||
plants software projects that build over real time, harvests the finished builds for coins and
|
||||
experience, and grows the farm with faster CI, more plots, and permanent perks. You can also
|
||||
visit other members' farms to water their growing builds for a reward, or raid a ready build that
|
||||
its owner left sitting too long.
|
||||
|
||||
Open it from your user menu or go straight to `{{ base }}/game`. You need a member account to
|
||||
play. Visiting another farm at `{{ base }}/game/farm/{username}` is public.
|
||||
|
||||
Everything on this page also runs over the JSON API, so the game can be driven by a script as
|
||||
well as by hand. The full request and response reference is the
|
||||
[Code Farm API group](/docs/game.html); this page explains the rules and the numbers behind
|
||||
them, then shows a complete automated client at the end.
|
||||
|
||||
## The core loop
|
||||
|
||||
1. **Plant** a crop in an empty plot. Planting costs coins.
|
||||
2. The crop **builds** over real time. The build finishes at a fixed wall-clock moment.
|
||||
3. **Harvest** the finished build for coins and experience, which empties the plot again.
|
||||
4. Spend the coins on faster CI, more plots, and perks so the next loop pays more.
|
||||
|
||||
A farm starts with **50 coins** and **4 plots**. Nothing is lost while you are away: builds keep
|
||||
progressing and wait for you, so the game is safe to check once an hour or once a day.
|
||||
|
||||
## Crops
|
||||
|
||||
Each crop has a coin cost to plant, a base build time, a coin and experience reward on harvest,
|
||||
and a level at which it unlocks. Slower crops pay far more per build.
|
||||
|
||||
| Crop | Cost | Base build time | Coins | XP | Unlocks at level |
|
||||
|------|-----:|----------------:|------:|---:|:----------------:|
|
||||
| 🐚 Shell Script | 5 | 30s | 11 | 2 | 1 |
|
||||
| 🐍 Python Script | 15 | 2m | 36 | 5 | 1 |
|
||||
| 📜 Web App | 40 | 5m | 98 | 12 | 2 |
|
||||
| 🐹 Go Service | 90 | 10m | 224 | 25 | 3 |
|
||||
| 🦀 Rust Engine | 200 | 30m | 520 | 60 | 4 |
|
||||
| λ Compiler | 500 | 1h | 1380 | 150 | 6 |
|
||||
| ⚙️ Kernel | 1200 | 2h | 3600 | 400 | 8 |
|
||||
|
||||
Build time is the base time divided by the farm build speed (CI tier and the growth perk, below),
|
||||
so a higher tier finishes everything proportionally faster. Coin and experience rewards shown in
|
||||
the API already include your perks and refactor bonus, so read them from the live state rather
|
||||
than from this table.
|
||||
|
||||
## Plots
|
||||
|
||||
A farm starts with 4 plots and can grow to **12**. Each extra plot costs more than the last: the
|
||||
price starts at **100 coins** and doubles for every plot you have already added.
|
||||
|
||||
| Plot number | Cost |
|
||||
|:-----------:|-----:|
|
||||
| 5th | 100 |
|
||||
| 6th | 200 |
|
||||
| 7th | 400 |
|
||||
| 8th | 800 |
|
||||
| 9th | 1600 |
|
||||
| 10th | 3200 |
|
||||
| 11th | 6400 |
|
||||
| 12th | 12800 |
|
||||
|
||||
More plots is more builds in parallel, which is the main way to scale output.
|
||||
|
||||
## CI tier (build speed)
|
||||
|
||||
Upgrading your CI tier multiplies build speed for the whole farm. The upgrade is permanent.
|
||||
|
||||
| Tier | Name | Build speed | Upgrade cost |
|
||||
|:----:|------|:-----------:|-------------:|
|
||||
| 1 | Local Build | 1.0x | starting |
|
||||
| 2 | Shared Runner | 1.25x | 150 |
|
||||
| 3 | Fast Runner | 1.6x | 400 |
|
||||
| 4 | Parallel Matrix | 2.0x | 1000 |
|
||||
| 5 | Distributed Cache | 2.5x | 2600 |
|
||||
|
||||
## Levels and experience
|
||||
|
||||
Harvesting grants experience. Experience raises your level, and higher levels unlock the more
|
||||
valuable crops. The maximum level is **20**. Each level needs `50 * (level - 1)^2` total
|
||||
experience, so level 2 is at 50 XP, level 5 at 800, level 10 at 4050, and level 20 at 18050.
|
||||
|
||||
## Perks
|
||||
|
||||
Perks are permanent upgrades bought with coins. Each level of a perk costs more than the last.
|
||||
There are four:
|
||||
|
||||
| Perk | Effect per level | Max level |
|
||||
|------|------------------|:---------:|
|
||||
| 📈 Optimizer | +5% harvest coins | 10 |
|
||||
| ⚡ Build Cache | +4% build speed | 10 |
|
||||
| 🏷️ Bulk Licenses | -3% planting cost | 8 |
|
||||
| 🎓 Mentorship | +5% harvest experience | 10 |
|
||||
|
||||
The live state reports each perk's current level and the exact cost of its next level.
|
||||
|
||||
## Daily bonus and streak
|
||||
|
||||
Once per day you can claim a coin bonus. Claiming on consecutive days builds a streak that
|
||||
increases the bonus, from **20 coins** on day one up to **92 coins** at a seven-day streak.
|
||||
Missing a day resets the streak. The state field `daily_available` tells you when a claim is
|
||||
ready, and `daily_reward` is the amount you would receive.
|
||||
|
||||
## Daily quests
|
||||
|
||||
Each day you are given three small goals, such as planting a number of crops, harvesting a number
|
||||
of builds, watering neighbour builds, or earning a number of coins from harvests. Progress is
|
||||
tracked automatically as you play. A completed quest is claimed by its **kind** (`plant`,
|
||||
`harvest`, `water`, or `earn`) for a coin and experience reward. The state field `can_claim` on
|
||||
each quest marks the ones that are ready.
|
||||
|
||||
## Fertilizer
|
||||
|
||||
Fertilizing a growing build spends coins to **halve its remaining time**. The cost scales with how
|
||||
much time is left (about 0.3 coins per remaining second, minimum 5 coins), so it is cheapest near
|
||||
the end of a build. Each plot in the state reports its current `fertilize_cost`.
|
||||
|
||||
## Watering a neighbour (cooperation)
|
||||
|
||||
Visit another member's farm and water one of their **growing** builds. Watering reduces that
|
||||
build's remaining time by 8% of its total build time and rewards **you**, the visitor, with
|
||||
**6 coins and 3 experience**. A single plot can be watered up to **3 times** total across all
|
||||
visitors, so popular farms fill up. Each plot reports `can_water`, `watered_count`, and
|
||||
`max_waters`.
|
||||
|
||||
## Raiding a ready build (competition)
|
||||
|
||||
If an owner leaves a build **ready** without harvesting it, another member can raid it, but only
|
||||
after a **60 second protection window** has passed since the build became ready. The raider
|
||||
receives **half** the build's coin value and the owner loses the build entirely. The owner is told
|
||||
their farm was raided but the raider's identity is never revealed. Each plot reports `can_steal`
|
||||
and `steal_coins` (the payout a raid would give), and `ready_at` lets you compute when the
|
||||
protection window ends. Harvest your own ready builds promptly to keep them safe.
|
||||
|
||||
## Refactor (prestige)
|
||||
|
||||
At level 10 or above you can **refactor**: the farm resets to its starting coins, level, CI tier,
|
||||
perks, and plots, but you gain a permanent **+25% coin bonus** that stacks with every refactor and
|
||||
multiplies with the Optimizer perk. Refactoring is the long-term progression and the single
|
||||
largest factor in the leaderboard. The state reports `prestige`, `prestige_multiplier`, and
|
||||
`prestige_available`.
|
||||
|
||||
## Leaderboard and scoring
|
||||
|
||||
The leaderboard ranks the top farms by a composite score. Refactors dominate the formula, then
|
||||
lifetime harvests, level, and everything else:
|
||||
|
||||
```
|
||||
score = xp
|
||||
+ prestige * 5000
|
||||
+ total_harvests * 10
|
||||
+ coins / 20
|
||||
+ (ci_tier - 1) * 250
|
||||
+ (plots - 4) * 200
|
||||
+ total_perk_levels * 120
|
||||
+ min(streak, 30) * 15
|
||||
```
|
||||
|
||||
Read the top farms with `GET {{ base }}/game/leaderboard` (public, no account needed).
|
||||
|
||||
## Live updates
|
||||
|
||||
A farm page subscribes to the pub/sub topic `public.game.farm.{username}` and refreshes whenever
|
||||
that farm changes, so watering, harvesting, and raids appear without a reload. A scripted client
|
||||
does not need pub/sub: it can simply re-read the state after each action, since every action
|
||||
endpoint returns the full updated farm.
|
||||
|
||||
## Playing through the API
|
||||
|
||||
Every action is a normal DevPlace endpoint that negotiates HTML or JSON. Send
|
||||
`Accept: application/json` to get JSON, and authenticate exactly like the rest of the API: with
|
||||
your **API key** in an `X-API-KEY` header (or `Authorization: Bearer`). Your key is on your
|
||||
[profile page]({% if user %}/profile/{{ uname }}{% else %}/profile/YOUR_USERNAME{% endif %}).
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `GET` | `/game/state` | Your full farm state (always JSON). |
|
||||
| `GET` | `/game/leaderboard` | Top farms (public). |
|
||||
| `GET` | `/game/farm/{username}` | Another member's farm. |
|
||||
| `POST` | `/game/plant` | Plant `crop` in `slot`. |
|
||||
| `POST` | `/game/harvest` | Harvest the build in `slot`. |
|
||||
| `POST` | `/game/fertilize` | Halve the remaining time of `slot`. |
|
||||
| `POST` | `/game/buy-plot` | Unlock one more plot. |
|
||||
| `POST` | `/game/upgrade` | Raise the CI tier. |
|
||||
| `POST` | `/game/perk` | Upgrade `perk` (`yield`, `growth`, `discount`, `xp`). |
|
||||
| `POST` | `/game/daily` | Claim the daily bonus. |
|
||||
| `POST` | `/game/quests/claim` | Claim a completed quest by `quest` kind. |
|
||||
| `POST` | `/game/prestige` | Refactor at level 10 or above. |
|
||||
| `POST` | `/game/farm/{username}/water` | Water a neighbour's build in `slot`. |
|
||||
| `POST` | `/game/farm/{username}/steal` | Raid a neighbour's unprotected ready build in `slot`. |
|
||||
|
||||
POST bodies are form encoded (`application/x-www-form-urlencoded`). Form fields are `slot` (an
|
||||
integer plot index), `crop`, `perk`, and `quest` where the table notes them. Every POST returns
|
||||
the full farm under `farm`, so one call both performs the action and gives you the new state.
|
||||
|
||||
### Reading the state
|
||||
|
||||
```python
|
||||
import json, urllib.request
|
||||
|
||||
request = urllib.request.Request(
|
||||
"{{ base }}/game/state",
|
||||
headers={"X-API-KEY": "{{ api_key }}", "Accept": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(request) as response:
|
||||
farm = json.loads(response.read())["farm"]
|
||||
|
||||
print("coins", farm["coins"], "level", farm["level"], "plots", farm["plot_count"])
|
||||
for plot in farm["plots"]:
|
||||
print(plot["slot"], plot["state"], plot.get("crop_name"), plot.get("remaining_seconds"))
|
||||
```
|
||||
|
||||
### The farm state shape
|
||||
|
||||
`GET /game/state` returns `{"ok": true, "farm": {...}}`. The farm carries your totals plus three
|
||||
lists you act on: `plots`, `crops`, and `quests`. A plot looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"slot": 0,
|
||||
"state": "growing",
|
||||
"crop_key": "python",
|
||||
"crop_name": "Python Script",
|
||||
"reward_coins": 45,
|
||||
"reward_xp": 5,
|
||||
"ready_at": "2026-06-23T12:34:56+00:00",
|
||||
"remaining_seconds": 73,
|
||||
"watered_count": 1,
|
||||
"max_waters": 3,
|
||||
"can_water": false,
|
||||
"can_steal": false,
|
||||
"steal_coins": 0,
|
||||
"fertilize_cost": 22
|
||||
}
|
||||
```
|
||||
|
||||
A plot's `state` is `empty`, `growing`, or `ready`. An entry in `crops` carries the live `cost`,
|
||||
`reward_coins`, `grow_seconds`, `min_level`, and a `locked` flag, so a client can decide what is
|
||||
both unlocked and affordable without hard-coding the table above.
|
||||
|
||||
### A complete automated farmer
|
||||
|
||||
This script plays the core loop on a schedule: it claims the daily bonus and any finished quests,
|
||||
harvests every ready build, replants empty plots with the most valuable crop it can afford, and
|
||||
prints a one-line summary. It uses only the Python standard library. Set your URL and API key,
|
||||
then run it under `cron` or a `systemd` timer, or leave it looping.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""Plays the DevPlace Code Farm automatically over the JSON API."""
|
||||
|
||||
import json
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
BASE = "{{ base }}"
|
||||
API_KEY = "{{ api_key }}"
|
||||
POLL_SECONDS = 60
|
||||
|
||||
|
||||
def call(method, path, fields=None):
|
||||
headers = {"X-API-KEY": API_KEY, "Accept": "application/json"}
|
||||
data = None
|
||||
if fields is not None:
|
||||
data = urllib.parse.urlencode(fields).encode()
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
request = urllib.request.Request(BASE + path, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(request) as response:
|
||||
return json.loads(response.read())
|
||||
|
||||
|
||||
def state():
|
||||
return call("GET", "/game/state")["farm"]
|
||||
|
||||
|
||||
def best_affordable_crop(farm):
|
||||
options = [c for c in farm["crops"] if not c["locked"] and c["cost"] <= farm["coins"]]
|
||||
if not options:
|
||||
return None
|
||||
return max(options, key=lambda c: c["reward_coins"])
|
||||
|
||||
|
||||
def claim_free_rewards(farm):
|
||||
if farm.get("daily_available"):
|
||||
call("POST", "/game/daily")
|
||||
for quest in farm.get("quests", []):
|
||||
if quest.get("can_claim"):
|
||||
call("POST", "/game/quests/claim", {"quest": quest["kind"]})
|
||||
|
||||
|
||||
def harvest_and_replant(farm):
|
||||
for plot in farm["plots"]:
|
||||
if plot["state"] == "ready":
|
||||
call("POST", "/game/harvest", {"slot": plot["slot"]})
|
||||
farm = state()
|
||||
for plot in farm["plots"]:
|
||||
if plot["state"] != "empty":
|
||||
continue
|
||||
crop = best_affordable_crop(farm)
|
||||
if not crop:
|
||||
break
|
||||
call("POST", "/game/plant", {"slot": plot["slot"], "crop": crop["key"]})
|
||||
farm = state()
|
||||
return farm
|
||||
|
||||
|
||||
def run():
|
||||
print("Code Farm automation started.")
|
||||
while True:
|
||||
try:
|
||||
farm = state()
|
||||
claim_free_rewards(farm)
|
||||
farm = harvest_and_replant(state())
|
||||
print(f"coins={farm['coins']} level={farm['level']} harvests={farm['total_harvests']}")
|
||||
except urllib.error.HTTPError as error:
|
||||
print("api error:", error.code, error.read().decode()[:200])
|
||||
except OSError as error:
|
||||
print("network error, retrying:", error)
|
||||
time.sleep(POLL_SECONDS)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
```
|
||||
|
||||
From here the natural extensions are easy: spend surplus coins with `POST /game/buy-plot` and
|
||||
`POST /game/upgrade` when you can afford them, raise perks with `POST /game/perk`, water
|
||||
neighbours by walking `GET /game/farm/{username}` and posting to its `/water` path for plots where
|
||||
`can_water` is true, and refactor with `POST /game/prestige` once `prestige_available` is set. Be
|
||||
polite to the rate limiter: mutating calls are limited per account, so a poll interval of a minute
|
||||
or more with one action per finished build stays well within limits.
|
||||
|
||||
## Ask Devii
|
||||
|
||||
You can also play in plain language through the Devii assistant, which calls the same endpoints
|
||||
for you:
|
||||
|
||||
> Check my Code Farm, harvest anything that is ready, and replant with the best crop I can afford.
|
||||
|
||||
Devii reads your state, performs the actions, and reports your new coin and level totals.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="devii-doc-cta">
|
||||
<a href="/game" class="sidebar-link">Open Code Farm</a>
|
||||
</div>
|
||||
Reference in New Issue
Block a user