feat: add Code Farm cooperative idle game with plot-based crop system and pub/sub notifications

Implement a Farmville-style cooperative idle game mounted at `/game` with member-only play and public farm viewing. The data layer is pure and timestamp-driven with no background tick: plot states are derived from `ready_at` vs current time, never stored. Key features include plantable software projects (shell script through kernel) that build over real time, harvest for coins and XP, CI tier upgrades for faster builds, plot purchasing with doubling cost, watering mechanics for friends' builds, daily quests, and prestige system. All game endpoints negotiate HTML or JSON and return full farm state for single-request client refresh. Pub/sub notifications broadcast farm updates on `public.game.farm.{username}` topics. Database schema adds `game_farms`, `game_plots`, and `game_quests` tables with appropriate indexes.
This commit is contained in:
2026-06-22 16:41:53 +00:00
parent 743efbf61f
commit ebf6bf7f82
40 changed files with 3860 additions and 5 deletions
+52
View File
@@ -0,0 +1,52 @@
# retoor <retoor@molodetz.nl>
from playwright.sync_api import expect
from tests.conftest import BASE_URL
from tests.e2e.game.index import reset_farm
def _plant_growing(username, crop="python", slot=0):
from devplacepy.database import get_table
from devplacepy.services.game import store
user = get_table("users").find_one(username=username)
store.plant({"uid": user["uid"], "username": username}, slot, crop)
def test_view_other_farm(bob):
page, _ = bob
reset_farm("alice_test")
page.goto(f"{BASE_URL}/game/farm/alice_test", wait_until="domcontentloaded")
page.locator(".game-header h1").wait_for(state="visible")
assert "alice_test" in page.locator(".game-header h1").inner_text()
assert page.locator("[data-game-grid]").first.is_visible()
def test_water_neighbour_build(bob):
page, _ = bob
reset_farm("alice_test")
_plant_growing("alice_test")
page.goto(f"{BASE_URL}/game/farm/alice_test", wait_until="domcontentloaded")
page.locator("[data-game-grid]").first.wait_for(state="visible")
page.wait_for_timeout(800)
water = page.locator("form[data-game-action='water'] button").first
water.wait_for(state="visible")
water.click()
expect(page.locator(".plot-water-count").first).to_contain_text("1/3")
def test_guest_can_view_farm(page):
reset_farm("alice_test")
page.goto(f"{BASE_URL}/game/farm/alice_test", wait_until="domcontentloaded")
page.wait_for_timeout(500)
assert "/auth/login" not in page.url
assert "alice_test" in page.locator(".game-header h1").inner_text()
def test_unknown_farm_is_404(bob):
page, _ = bob
response = page.goto(
f"{BASE_URL}/game/farm/nope_nobody", wait_until="domcontentloaded"
)
assert response is not None and response.status == 404