Skip to content

pioner92/cropbot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

15 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Cropbot

Program a farming drone in real C++. Watch it work.

πŸš€ Play now β†’ cropbot.fly.dev

Cropbot preview

A browser-based farming game where you write C++ code to automate a drone. Your code is compiled to WebAssembly via Emscripten and runs live in the browser β€” no plugins, no downloads, just a text editor and a field to farm.


How it works

You write C++ in the browser. Click Run. The server compiles your code with emcc, sends back a .wasm binary, and the drone starts executing your main() β€” tick by tick, on a real grid, with real crops.

int main() {
    while (true) {
        if (get_cell_state() == CellState::EMPTY)   till();
        if (get_cell_state() == CellState::TILLED)  plant(CropType::PUMPKIN);
        if (get_cell_state() == CellState::READY)   harvest();

        if (get_water_level() < 30 && get_tank() >= 15)
            water();

        move_east();
    }
}

Quick start

npm install
node server.js
# open http://localhost:3000

Requirements: Node.js, Emscripten (emcc in PATH)


Drone API

All functions are available without any #include. The header is automatically prepended to your code before compilation.

Movement β€” 1 tick each

void move(Direction dir);
void move_north();
void move_south();
void move_east();
void move_west();

Farming β€” 1 tick each

void till();                  // EMPTY β†’ TILLED
void plant(CropType crop);    // TILLED β†’ PLANTED
void harvest();               // READY  β†’ EMPTY, earn gold
void water();                 // costs 15 tank units, gives cell +50 water
void wait(int ticks);

State queries β€” 0 ticks

CellState get_cell_state();
CellState get_cell_state(int x, int y);
CropType  get_crop();
CropType  get_crop(int x, int y);
int get_water_level();
int get_water_level(int x, int y);
int get_tank();
int get_max_tank();
int get_energy();
int get_max_energy();
int is_at_base();             // 1 if drone is at (0,0)
int get_x();
int get_y();
int get_ticks();
int get_score();

Economy β€” 0 ticks

int  get_gold();
int  get_seeds(CropType crop);
void buy_seeds(CropType crop, int count);
void buy_water(int packs);    // 50 tank units per pack, 10 gold each
void print(int val);

Types

enum class Direction  { NORTH, EAST, SOUTH, WEST };
enum class CellState  { EMPTY, TILLED, PLANTED, GROWING, READY, BASE };
enum class CropType   { WHEAT, POTATO, PUMPKIN, CORN, MUSHROOM, COFFEE };

Crops

Crop Growth Sell Seed Profit Gold/tick Unlock
Wheat 8+12 ticks 2 πŸͺ™ 1 πŸͺ™ 1 0.050 free
Potato 14+20 ticks 6 πŸͺ™ 2 πŸͺ™ 4 0.118 free
Pumpkin 25+40 ticks 10 πŸͺ™ 5 πŸͺ™ 5 0.077 80 πŸͺ™
Corn 18+27 ticks 9 πŸͺ™ 3 πŸͺ™ 6 0.133 280 πŸͺ™
Mushroom 15+25 ticks 10 πŸͺ™ 4 πŸͺ™ 6 0.150 650 πŸͺ™
Coffee 40+80 ticks 35 πŸͺ™ 20 πŸͺ™ 15 0.125 1500 πŸͺ™

Mushroom: grows only if water_level > 40 β€” requires preventive watering strategy. Water: every planted/growing cell loses 2 water/tick. Growth pauses at 0. Tank: holds water for water() calls. Refills at base (+8/tick). Battery: every action costs 1 energy. Recharges at base (+10/tick).


Base (0, 0)

  • Farming actions (till, plant, water, harvest) are no-ops at base
  • Auto-refills tank and battery each tick while the drone is here
  • is_at_base() returns 1 when drone is at (0, 0)

Upgrades

Purchased from the Extra panel in-game. Permanent until reset.

Upgrade Per level Max level Max value Start cost
Tank Capacity +10 10 150 60 πŸͺ™
Battery Capacity +24 10 360 100 πŸͺ™

Cost multiplier: Γ—1.65 per level.


Architecture

Browser
β”œβ”€β”€ index.html          UI, styles, API reference panel
β”œβ”€β”€ game.js             Game engine, renderer, WASM orchestration
β”‚   β”œβ”€β”€ growTick()      Crop growth + water drain per tick
β”‚   β”œβ”€β”€ droneActions{}  Bridge between WASM calls and game state
β”‚   β”œβ”€β”€ execWasmAction()Dispatches action codes from Worker
β”‚   └── wasmActionLoop()Listens to SharedArrayBuffer via Atomics.waitAsync
└── wasm-worker.js      Web Worker β€” runs compiled WASM
    └── callMain()      Blocks Worker via Atomics.wait, wakes main thread

Server (Node.js)
β”œβ”€β”€ POST /compile       Runs emcc, returns .wasm bytes
└── COOP/COEP headers   Required for SharedArrayBuffer to work

WASM ↔ Main thread communication

The Worker runs main() which blocks on Atomics.wait() for every drone API call. The main thread uses Atomics.waitAsync() to react, executes the action, writes the result into a SharedArrayBuffer, then wakes the Worker with Atomics.notify().

Worker              SharedArrayBuffer           Main thread
  β”‚                 [ACTION][ARG0][ARG1]           β”‚
  │── write action ──────────────────────────────> β”‚
  │── Atomics.wait(RESPONSE) ─────────────────>    β”‚
  β”‚                                   execWasmAction()
  β”‚                 [RESULT][RESPONSE=1]            β”‚
  β”‚<─────────────────────────────── Atomics.notify ─│
  │── read result ──────────────────────────────    β”‚

File structure

farm.cpp/
β”œβ”€β”€ index.html        Markup, styles, in-game API panel
β”œβ”€β”€ game.js           All game logic (~1700 lines)
β”œβ”€β”€ wasm-worker.js    Web Worker for WASM execution
β”œβ”€β”€ drone_api.h       Drone API header (auto-prepended on compile)
β”œβ”€β”€ server.js         Express server: /compile endpoint + static files
β”œβ”€β”€ package.json      Dependencies (express)
β”œβ”€β”€ ideas/            Design notes and feature ideas
β”‚   β”œβ”€β”€ crops.md
β”‚   └── field_expansion.md
└── README.md

Adding a new drone function

Three files must be updated in sync:

1. drone_api.h

extern "C" { int get_something(); }

2. game.js

// in const ACT:
GET_SOMETHING: 31,

// in QUERY_ACTIONS set (if it costs 0 ticks):
ACT.GET_SOMETHING,

// in execWasmAction() switch:
case ACT.GET_SOMETHING: result = droneActions.get_something(); break;

// in droneActions{}:
get_something() { return /* value */; },

3. wasm-worker.js

// in const ACT:
GET_SOMETHING: 31,

// in makeDroneEnv():
get_something: () => callMain(ACT.GET_SOMETHING),

Why the server is required

SharedArrayBuffer is blocked by browsers unless the page is served with:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Opening index.html directly via file:// will not work. Any static file server that can set these headers works β€” the Node.js server is just the simplest setup that also handles compilation.

About

Cropbot is a programming automation game where your C++ code controls a farming drone in real time

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors