{"id":21843323,"url":"https://github.com/yiransheng/rust-snake-wasm","last_synced_at":"2025-07-17T10:39:12.959Z","repository":{"id":50617322,"uuid":"126283297","full_name":"yiransheng/rust-snake-wasm","owner":"yiransheng","description":"A snake game in rust and webassembly","archived":false,"fork":false,"pushed_at":"2018-12-08T21:34:43.000Z","size":1388,"stargazers_count":58,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-14T12:06:25.866Z","etag":null,"topics":["blockade","rust","snake-game","wasm","wasm-bindgen","web-assymbly"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yiransheng.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-03-22T05:17:16.000Z","updated_at":"2025-03-02T18:01:48.000Z","dependencies_parsed_at":"2022-09-24T16:00:41.441Z","dependency_job_id":null,"html_url":"https://github.com/yiransheng/rust-snake-wasm","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/yiransheng/rust-snake-wasm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yiransheng%2Frust-snake-wasm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yiransheng%2Frust-snake-wasm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yiransheng%2Frust-snake-wasm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yiransheng%2Frust-snake-wasm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yiransheng","download_url":"https://codeload.github.com/yiransheng/rust-snake-wasm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yiransheng%2Frust-snake-wasm/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265596194,"owners_count":23794852,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blockade","rust","snake-game","wasm","wasm-bindgen","web-assymbly"],"created_at":"2024-11-27T22:14:50.216Z","updated_at":"2025-07-17T10:39:12.941Z","avatar_url":"https://github.com/yiransheng.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rust WASM Snake Game\n\nPlay Link: [http://yiransheng.github.io/rust-snake-wasm/](http://yiransheng.github.io/rust-snake-wasm/)\n\n\n\n![](./screenshots/games.png)\n\n\n\nA weirdly designed snake game in rust \u0026 WebAssembley, primarily aimed for retained mode rendering targeting canvas. Two non-browser targets are also included in [./non_browser](./non_browser).\n\n## Features\n\n* Snake movements are animated and smoothed over\n* Variable render speed, hold down direction keys to accelerate (opposite direction key to deaccelerate)\n\n![](./screenshots/acceleration.gif)\n\n* VIM key bindings supported (h,j,k,l)\n* `no_std`\n* Retained rendering; a game of life like appoach to game states\n* Perhaps too over-engineered, trying to explore various fun and unique abstractions afforded by rust\n* Minimal world model completely decoupled from drawing code, easily ported over to other environments and rendering targets (although in trying to run it with piston, retained rendering doesn't quite work..)\n  * ..and enabled writing unit tests nicely like so:\n\n```rust\n#[test]\nfn test_death() {\n    let snake_string = indoc!(\n        \"\n        ..........\n        \u003e\u003e\u003e\u003e\u003e\u003ev...\n        ......v...\n        ..^\u003c\u003c\u003c\u003c...\n        .........*\"\n    );\n    let afterwards = indoc!(\n        \"\n        ..........\n        .oooooo...\n        ..o...o...\n        ..ooooo...\n        .........*\"\n    );\n\n    let mut world: World\u003cSmallRng, Bounding\u003e = World::from_ascii(snake_string);\n\n    while let Ok(_) = world.step(None) {}\n\n    assert_matches!(world.step(None), Err(UpdateError::CollideBody));\n\n    assert_eq!(\u0026afterwards, \u0026world.grid.to_string());\n}\n```\n\n## Similar Projects\n\nOther snakesssssss (with rust and wasm):\n\n* https://github.com/alamminsalo/snakewasm\n* https://github.com/dmacewen/snake_wasm\n\n* https://github.com/tensor-programming/wasm_snake_example\n\n* https://github.com/mardiros/wasm-snake\n* https://github.com/BurNiinTRee/Snake\n* https://github.com/DarrenTsung/wrg-snake\n* https://github.com/kirjavascript/snake-rs\n\n## Usage\n\n### Stuff to Install\n\n1. `wasm32-unknown-unknown` target and `wasm-bindgen-cli`\n\n```\nrustup target add wasm32-unknown-unknown --toolchain nightly\ncargo +nightly install wasm-bindgen-cli\n```\n\n2. `node` and `yarn`\n3. (optional) `wasm-opt` install from: [https://github.com/WebAssembly/binaryen](https://github.com/WebAssembly/binaryen)\n\n### Build\n\nDevelopment\n\n```\nmake dev\n```\n\nBuild (cargo release build, `no_std`, `wee_alloc`, `webpack --mode=production` and `wasm-opt`)\n\n```\nmake\n# serve ./docs\n```\n\nTest\n\n```\ncargo test\n```\n\n## Brief Overview\n\nThe core structure of this game is `World` (mod: `world`), its side-effects/outputs are:\n\n```rust\nenum WorldUpdate {\n    SetBlock { block: Block, at: Coordinate },\n    Clear { prev_block: Block, at: Coordinate },\n    SetWorldSize(u16, u16),\n}\n\nenum UpdateError {\n    HeadDetached,  // panics, game bug, should not happen\n    TailDetached,  // panics, game bug, should not happen\n    OutOfBound,\n    CollideBody,\n}\n\ntype Result\u003cT\u003e = ::std::result\u003cT, UpdateError\u003e;\n```\n\n`World` itself is just:\n\n```rust\nstruct World\u003cR: Rng, BB: BoundingBehavior = Wrapping\u003e {\n    fn initialize(\u0026'a mut self) -\u003e impl Iterator\u003cItem=WorldUpdate\u003e {\n        // ...\n    }\n\n    fn tear_down(\u0026mut self) {\n        // ...\n    }\n\n    fn step(\u0026mut self, cmd: Option\u003cDirection\u003e) -\u003e Result\u003cOption\u003cWorldUpdate\u003e\u003e {\n        // ...\n    }\n}\n```\n\nIt's complete devoid of drawing code and does not concern itself with game start/stop and other game loop level controls. This allowed itself to be reused for very different runtimes (browser, terminal etc., Although when trying to adapt it to a `piston_window`, not exposing its internal state really drove things into a corner).\n\n`initialize` returns an `Iterator` of `WorldUpdate`, this simplifies things for renderers, as they only need to deal with `WorldUpdate` data type alone, and do not need to worry about behaving differently during initialization vs. normal game play.\n\nThis formulation of game world is abstracted out as a `trait` (generic lifetime ``m` allows `initialize` to return a iterator that borrows the struct itself).\n\n```rust\ntrait Stateful\u003c'm\u003e {\n    type Cmd;\n    type Init: IntoIterator\u003cItem = Self::Update\u003e + 'm;\n    type Update;\n    type Error: Into\u003cGameOver\u003e;\n\n    fn initialize(\u0026'm mut self) -\u003e Self::Init;\n\n    fn step(\n        \u0026mut self,\n        cmd: Option\u003cSelf::Cmd\u003e,\n    ) -\u003e Result\u003cOption\u003cSelf::Update\u003e, Self::Error\u003e;\n\n    fn tear_down(\u0026mut self);\n}\n```\n\nTwo other addons that implement this `trait` are `RenderSpeed` and `Dead`, responsible for controlling acceleration and gameover/restart respectively. In the end all things are glued together with provided combinator methods on `Stateful` `trait` :\n\n```rust\n    let world: World\u003cSmallRng, Wrapping\u003e = WorldBuilder::new()\n        .width(64)\n        .height(32)\n        .set_snake(1, 1)\n        .extend(Direction::East)\n        .extend(Direction::East)\n        .extend(Direction::East)\n        .extend(Direction::East)\n        .build_with_seed([123; 16]);\n\n    let game = world\n        .zip_with(RenderSpeed::new(Direction::East), VariableFrame::pack)\n        .alternating::\u003cKey, _\u003e(Dead::new())\n        .make_game(CanvasEnv::new());\n```\n\nThis same code is pretty much reused for `piston_snake` and `terminal_snake` as well, just using different `Env` (than `CanvasEnv`) implementations there. \n\nGame loop is provided by `js` side:\n\n```rust\n#[wasm_bindgen(module = \"./game-loop\")]\nextern \"C\" {\n    type GameLoop;\n\n    #[wasm_bindgen(constructor)]\n    fn new(run: \u0026Closure\u003cFnMut(u8)\u003e) -\u003e GameLoop;\n\n    #[wasm_bindgen(method)]\n    fn start(this: \u0026GameLoop) -\u003e bool;\n\n    #[wasm_bindgen(method)]\n    fn stop(this: \u0026GameLoop) -\u003e bool;\n}\n```\n\n`js` class `GameLoop` takes a closure from `wasm`, and run it on a `requestAnimationFrame` loop, supplying a `u8` for pressed key code in each tick.\n\nFinally game update code is packaged as a `Generator`. Since rust does not allow sending data into generator (unlike `javaScript` and `python`), a channel-like \"`Sender`\" is also returned to feed in data/commands (it's just a `Rc\u003cRefCell\u003cInputBuffer\u003c_\u003e\u003e\u003e` under the hood), but I imagine in a multi-threaded context by using `std::sync::mpsc::channel` , this pattern would still work.\n\n```rust\nlet (tx, mut generator) = game.new_game();\n\nlet each_tick = Closure::wrap(Box::new(move |key: u8| {\n    let key = Key::from(key);\n\n    tx.send(key);\n\n    unsafe {\n        generator.resume();\n    }\n}) as Box\u003cFnMut(_)\u003e);\n\nlet game_loop = GameLoop::new(\u0026each_tick);\n\ngame_loop.start();\neach_tick.forget();\n```\n\nThe returned `generator` uses `Generator` `yield` syntax to encode a simple state machine that alternates between rendering ticks and game logic tick. Getting [this piece](./src/system/state.rs#L252) to compile (and not leak memory) took me a long time to figure out, but it was a pretty good exercise to understand `rust` ownership model on a deeper level.\n\nWhile this architecture is largely unnecessary for such a simple game (and probably does not scale to real world games at all) - going about in in a very generic and modular way and having everything tied together in the end was still very satisfying.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyiransheng%2Frust-snake-wasm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyiransheng%2Frust-snake-wasm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyiransheng%2Frust-snake-wasm/lists"}