[
  {
    "path": ".github/workflows/build.yml",
    "content": "on: [push]\n\nname: build\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions-rs/toolchain@v1\n        with:\n          toolchain: 1.57.0\n          target: wasm32-unknown-unknown\n          override: true\n          components: clippy\n      - uses: actions-rs/install@v0.1\n        with:\n          crate: wasm-pack\n          version: 0.9.1\n          use-tool-cache: true\n      - name: Annotate commit with clippy warnings\n        uses: actions-rs/clippy-check@v1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n      - uses: actions/setup-node@v2\n        with:\n          node-version: '16.13.0'\n      - run: npm install\n      - run: npm test\n      - run: npm run build\n      - name: Deploy to Netlify\n        uses: nwtgck/actions-netlify@v1.2\n        with:\n          publish-dir: './dist'\n          production-branch: main\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          deploy-message: \"Deploy from GitHub Actions\"\n          enable-pull-request-comment: true\n          enable-commit-comment: true\n          overwrites-pull-request-comment: true\n        env:\n          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}\n          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}\n        timeout-minutes: 1\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n/dist\n/target\n/pkg\n/wasm-pack.log\n\n# Local Netlify folder\n.netlify\n\n.DS_Store"
  },
  {
    "path": ".rustfmt",
    "content": "edition = \"2018\"\n"
  },
  {
    "path": ".tool-versions",
    "content": "nodejs 16.13.0\n"
  },
  {
    "path": "Cargo.toml",
    "content": "# You must change these to your own details.\n[package]\nname = \"rust-webpack-template\"\ndescription = \"Walk the Dog - the game for the Rust Games with WebAssembly book\"\nversion = \"0.1.0\"\nauthors = [\"Eric Smith <paytonrules@gmail.com>\"]\ncategories = [\"wasm\"]\nreadme = \"README.md\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[profile.release]\n# This makes the compiled code faster and smaller, but it makes compiling slower,\n# so it's only enabled in release mode.\nlto = true\n\n[features]\n# If you uncomment this line, it will enable `wee_alloc`:\n#default = [\"wee_alloc\"]\n\n[dependencies]\n# The `wasm-bindgen` crate provides the bare minimum functionality needed\n# to interact with JavaScript.\nwasm-bindgen = { version = \"0.2.78\", features = [\"serde-serialize\"] }\n\nconsole_error_panic_hook = \"0.1.7\"\nrand = \"0.8.4\"\ngetrandom = { version = \"0.2.3\", features = [\"js\"] }\nfutures = \"0.3.17\"\nwasm-bindgen-futures = \"0.4.28\"\nserde = {version = \"1.0.131\", features = [\"derive\"] }\nanyhow = \"1.0.51\"\nasync-trait = \"0.1.52\"\njs-sys = \"0.3.55\"\n\n# The `web-sys` crate allows you to interact with the various browser APIs,\n# like the DOM.\n[dependencies.web-sys]\nversion = \"0.3.55\"\nfeatures = [\"console\",\n           \"Window\",\n           \"Document\",\n           \"HtmlCanvasElement\",\n           \"CanvasRenderingContext2d\",\n           \"Element\",\n           \"HtmlImageElement\",\n           \"Response\",\n           \"Performance\",\n           \"KeyboardEvent\",\n           \"AudioContext\",\n           \"AudioBuffer\",\n           \"AudioBufferSourceNode\",\n           \"AudioDestinationNode\",\n           \"AudioBufferOptions\",\n           ]\n\n# These crates are used for running unit tests.\n[dev-dependencies]\nwasm-bindgen-test = \"0.3.28\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Packt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "\n\n\n# Game Development with Rust and WebAssembly\nGame Development with Rust and WebAssembly, published by Packt\n\n<a href=\"https://www.packtpub.com/product/game-development-with-rust-and-webassembly/9781801070973\"><img src=\"https://static.packt-cdn.com/products/9781801070973/cover/smaller\" alt=\"Game Development with Rust and WebAssembly\" height=\"256px\" align=\"right\"></a>\n\nThis is the code repository for [Game Development with Rust and WebAssembly](https://www.packtpub.com/product/game-development-with-rust-and-webassembly/9781801070973), published by Packt.\n\n**Learn how to run Rust on the web while building a game**\n\n## What is this book about?\nThe Rust programming language has held the most-loved technology ranking on Stack Overflow for 6 years running, while JavaScript has been the most-used programming language for 9 years straight as it runs on every web browser. Now, thanks to WebAssembly (or Wasm), you can use the language you love on the platform that's everywhere.\n\nThis book covers the following exciting features:\n\n* Build and deploy a Rust application to the web using WebAssembly\n* Use wasm-bindgen and the Canvas API to draw real-time graphics\n* Write a game loop and take keyboard input for dynamic action\n* Explore collision detection and create a dynamic character that can jump on and off platforms and fall down holes\n* Manage animations using state machines\n* Generate levels procedurally for an endless runner\n* Load and display sprites and sprite sheets for animations\n\nIf you feel this book is for you, get your [copy](https://www.amazon.com/dp/1801070970) today!\n\n<a href=\"https://www.packtpub.com/?utm_source=github&utm_medium=banner&utm_campaign=GitHubBanner\"><img src=\"https://raw.githubusercontent.com/PacktPublishing/GitHub/master/GitHub.png\" \nalt=\"https://www.packtpub.com/\" border=\"5\" /></a>\n\n## Instructions and Navigation\n\nYou're currently looking the main branch of this repository, which represents the \"completed\" state of this book. I say completed because development on this branch is ongoing - specifically the challenges cited in the book are being implemented here. If you want to see the end state of any chapter those are stored as tags, such as https://github.com/PacktPublishing/Game-Development-with-Rust-and-WebAssembly/tree/chapter_1.\n\n**Following is what you need for this book:**\n\nThis game development book is for developers interested in Rust who want to create and deploy 2D games to the web. Game developers looking to build a game on the web platform using WebAssembly without C++ programming or web developers who want to explore WebAssembly along with JavaScript web will also find this book useful. The book will also help Rust developers who want to move from the server side to the client side by familiarizing them with the WebAssembly toolchain. Basic knowledge of Rust programming is assumed.\n\nWith the following software and hardware list you can run all code files present in the book (Chapter 1-11).\n\n### Software and Hardware List\n\n| Chapter  | Software required                          | version | OS required |\n|----------|--------------------------------------------|---------|-------------|\n| (1 - 11) | Rust Toolchains via Rustup                 | 1.57.0  | Any OS      |\n| (1 - 11) | NodeJS                                     | 16.13.0 | Any OS      |\n| (1 - 11) | Rust Compile target wasm32-unknown-unknown | NA      | NA          |\n\nI use https://asdf-vm.com to install Node and a .tool-versions file is present but you don't have to. Instructions for creating a new project are found in the book (chapter 1) but the project can also be setup by cloning this repository and running the commands for building and running. Speaking of that:\n\n### Running this App\n\n#### Installation\n\n`npm install` Will install the Node dependencies (primarily WebPack). Don't worry you don't have to think about those much.\n\n#### Running in debug\n\n`npm start` Will compile the application to Wasm and start a server, running it at localhost:8080 by default. This will also ensure `wasm-pack` is setup and running and run `cargo build`.\n\n#### Building for release\n\n`npm run build` Creates a release build and puts it in the `dist` directory.\n\n#### Running Tests\n\n`npm run test`\n\nYou can use a lot of the `cargo` commands as well - but those do not go through the process of bundling up the built assembly for distribution. \n\n#### Deployment\n\nThis branch is setup for continuous deployment with GitHub Actions, as is the tag for chapter_10. Something to keep in mind when forking the repository. The current production version of this game can be found at:\n\nhttps://rust-games-webassembly.netlify.app\n\n## Challenges\n\nAt the end of the book (Further Resources and What's Next?) there are six challenges for you, the reader. I'll be completing them on and off [my stream](www.twitch.tv/paytonrules), and making a note here when they are complete.\n\nChallenge #6:\n- Challenge #6 was completed two ways. I displayed the score via the canvas's render text function, as well as via the DOM. There are two branches with the solutions: [add-score](https://github.com/PacktPublishing/Game-Development-with-Rust-and-WebAssembly/tree/add-score) and [add-score-html](https://github.com/PacktPublishing/Game-Development-with-Rust-and-WebAssembly/tree/add-score-via-html). The HTML version looks a lot better, and is also implemented in the main branch.\n\n## More Information \nWe also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://static.packt-cdn.com/downloads/9781801070973_ColorImages.pdf).\n\nThe Code in Action videos for this book can be viewed at https://bit.ly/3uxXl4W.\n\n### Related products <Other books you may enjoy>\n* Creative Projects for Rust Programmers  [[Packt]](https://www.packtpub.com/product/creative-projects-for-rust-programmers/9781789346220) [[Amazon]](https://www.amazon.com/Creative-Projects-Rust-Programmers-WebAssembly/dp/1789346223)\n\n* Rust Web Programming [[Packt]](https://www.packtpub.com/product/rust-web-programming/9781800560819) [[Amazon]](https://www.amazon.com/Rust-Web-Programming-hands-programming-dp-1800560818/dp/1800560818/ref=mt_other?_encoding=UTF8&me=&qid=)\n\n## Get to Know the Author\n**Eric Smith** is a software crafter with over 20 years of software development experience. Since 2005, he's worked at 8th Light, where he consults for companies big and small by delivering software, mentoring developers, and coaching teams. He's a frequent speaker at conferences speaking on topics such as educating developers and test-driven development, and holds a master's degree in video game development from DePaul University. Eric wrote much of the code for this book live on his Twitch stream. When he's not at the computer, you can find Eric running obstacle races and traveling with his family.\n\n### Download a free PDF\n\n <i>If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.<br>Simply click on the link to claim your free PDF.</i>\n<p align=\"center\"> <a href=\"https://packt.link/free-ebook/9781801070973\">https://packt.link/free-ebook/9781801070973 </a> </p>\n"
  },
  {
    "path": "js/index.js",
    "content": "import(\"../pkg/index.js\").catch(console.error);\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"author\": \"You <you@example.com>\",\n  \"name\": \"rust-webpack-template\",\n  \"version\": \"0.1.0\",\n  \"scripts\": {\n    \"build\": \"rimraf dist pkg && webpack\",\n    \"start\": \"rimraf dist pkg && webpack-dev-server --open -d --host 0.0.0.0\",\n    \"test\": \"cargo test && wasm-pack test --headless --chrome\"\n  },\n  \"devDependencies\": {\n    \"@wasm-tool/wasm-pack-plugin\": \"^1.1.0\",\n    \"copy-webpack-plugin\": \"^5.0.3\",\n    \"rimraf\": \"^3.0.0\",\n    \"webpack\": \"^4.42.0\",\n    \"webpack-cli\": \"^3.3.3\",\n    \"webpack-dev-server\": \"^3.7.1\"\n  },\n  \"dependencies\": {\n    \"netlify-cli\": \"^8.0.1\"\n  }\n}\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"1.57.0\"\ntargets = [\"wasm32-unknown-unknown\"]\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "edition = \"2018\"\n"
  },
  {
    "path": "src/browser.rs",
    "content": "use anyhow::{anyhow, Result};\nuse js_sys::ArrayBuffer;\nuse std::future::Future;\n\nuse wasm_bindgen::{\n    closure::WasmClosure, closure::WasmClosureFnOnce, prelude::Closure, JsCast, JsValue,\n};\nuse wasm_bindgen_futures::JsFuture;\nuse web_sys::{\n    CanvasRenderingContext2d, Document, Element, HtmlCanvasElement, HtmlElement, HtmlImageElement,\n    Response, Window,\n};\n\n// Straight taken from https://rustwasm.github.io/book/game-of-life/debugging.html\nmacro_rules! log {\n    ( $( $t:tt )* ) => {\n        web_sys::console::log_1(&format!( $( $t )* ).into());\n    }\n}\n\nmacro_rules! error {\n    ( $( $t:tt )* ) => {\n        web_sys::console::error_1(&format!( $( $t )* ).into());\n    }\n}\n\npub fn window() -> Result<Window> {\n    web_sys::window().ok_or_else(|| anyhow!(\"No Window Found\"))\n}\n\npub fn document() -> Result<Document> {\n    window()?\n        .document()\n        .ok_or_else(|| anyhow!(\"No Document Found\"))\n}\n\npub fn canvas() -> Result<HtmlCanvasElement> {\n    document()?\n        .get_element_by_id(\"canvas\")\n        .ok_or_else(|| anyhow!(\"No Canvas Element found with ID 'canvas'\"))?\n        .dyn_into::<web_sys::HtmlCanvasElement>()\n        .map_err(|element| anyhow!(\"Error converting {:#?} to HtmlCanvasElement\", element))\n}\n\npub fn context() -> Result<CanvasRenderingContext2d> {\n    canvas()?\n        .get_context(\"2d\")\n        .map_err(|js_value| anyhow!(\"Error getting 2d context {:#?}\", js_value))?\n        .ok_or_else(|| anyhow!(\"No 2d context found\"))?\n        .dyn_into::<web_sys::CanvasRenderingContext2d>()\n        .map_err(|element| {\n            anyhow!(\n                \"Error converting {:#?} to CanvasRenderingContext2d\",\n                element\n            )\n        })\n}\n\npub fn spawn_local<F>(future: F)\nwhere\n    F: Future<Output = ()> + 'static,\n{\n    wasm_bindgen_futures::spawn_local(future);\n}\n\npub async fn fetch_with_str(resource: &str) -> Result<JsValue> {\n    JsFuture::from(window()?.fetch_with_str(resource))\n        .await\n        .map_err(|err| anyhow!(\"error fetching {:#?}\", err))\n}\n\npub async fn fetch_response(resource: &str) -> Result<Response> {\n    fetch_with_str(resource)\n        .await?\n        .dyn_into()\n        .map_err(|err| anyhow!(\"error converting fetch to Response {:#?}\", err))\n}\n\npub async fn fetch_json(json_path: &str) -> Result<JsValue> {\n    let resp = fetch_response(json_path).await?;\n\n    JsFuture::from(\n        resp.json()\n            .map_err(|err| anyhow!(\"Could not get JSON from response {:#?}\", err))?,\n    )\n    .await\n    .map_err(|err| anyhow!(\"error fetching JSON {:#?}\", err))\n}\n\npub async fn fetch_array_buffer(resource: &str) -> Result<ArrayBuffer> {\n    let array_buffer = fetch_response(resource)\n        .await?\n        .array_buffer()\n        .map_err(|err| anyhow!(\"Error loading array buffer {:#?}\", err))?;\n\n    JsFuture::from(array_buffer)\n        .await\n        .map_err(|err| anyhow!(\"Error converting array buffer into a future {:#?}\", err))?\n        .dyn_into()\n        .map_err(|err| anyhow!(\"Error converting raw JSValue to ArrayBuffer {:#?}\", err))\n}\n\npub fn new_image() -> Result<HtmlImageElement> {\n    HtmlImageElement::new().map_err(|err| anyhow!(\"Could not create HtmlImageElement: {:#?}\", err))\n}\n\npub type LoopClosure = Closure<dyn FnMut(f64)>;\npub fn create_raf_closure(f: impl FnMut(f64) + 'static) -> LoopClosure {\n    closure_wrap(Box::new(f))\n}\n\npub fn request_animation_frame(callback: &LoopClosure) -> Result<i32> {\n    window()?\n        .request_animation_frame(callback.as_ref().unchecked_ref())\n        .map_err(|err| anyhow!(\"Cannot request animation frame {:#?}\", err))\n}\n\npub fn closure_once<F, A, R>(fn_once: F) -> Closure<F::FnMut>\nwhere\n    F: 'static + WasmClosureFnOnce<A, R>,\n{\n    Closure::once(fn_once)\n}\n\npub fn closure_wrap<T: WasmClosure + ?Sized>(data: Box<T>) -> Closure<T> {\n    Closure::wrap(data)\n}\n\npub fn now() -> Result<f64> {\n    Ok(window()?\n        .performance()\n        .ok_or_else(|| anyhow!(\"Performance object not found\"))?\n        .now())\n}\n\npub fn draw_ui(html: &str) -> Result<()> {\n    find_ui()?\n        .insert_adjacent_html(\"afterbegin\", html)\n        .map_err(|err| anyhow!(\"Could not insert html {:#?}\", err))\n}\n\npub fn hide_ui() -> Result<()> {\n    let ui = find_ui()?;\n\n    if let Some(child) = ui.first_child() {\n        ui.remove_child(&child)\n            .map(|_removed_child| ())\n            .map_err(|err| anyhow!(\"Failed to remove child {:#?}\", err))\n            .and_then(|_unit| {\n                canvas()?\n                    .focus()\n                    .map_err(|err| anyhow!(\"Could not set focus to canvas! {:#?}\", err))\n            })\n    } else {\n        Ok(())\n    }\n}\n\nfn find_ui() -> Result<Element> {\n    document().and_then(|doc| {\n        doc.get_element_by_id(\"ui\")\n            .ok_or_else(|| anyhow!(\"UI element not found\"))\n    })\n}\n\npub fn find_html_element_by_id(id: &str) -> Result<HtmlElement> {\n    document()\n        .and_then(|doc| {\n            doc.get_element_by_id(id)\n                .ok_or_else(|| anyhow!(\"Element with id {} not found\", id))\n        })\n        .and_then(|element| {\n            element\n                .dyn_into::<HtmlElement>()\n                .map_err(|err| anyhow!(\"Could not cast into HtmlElement {:#?}\", err))\n        })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use wasm_bindgen_test::wasm_bindgen_test;\n\n    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);\n\n    #[wasm_bindgen_test]\n    async fn test_error_loading_json() {\n        let json = fetch_json(\"not_there.json\").await;\n\n        assert_eq!(json.is_err(), true);\n    }\n}\n"
  },
  {
    "path": "src/engine.rs",
    "content": "use crate::browser::{self, LoopClosure};\nuse crate::sound;\nuse anyhow::{anyhow, Result};\nuse async_trait::async_trait;\nuse futures::channel::{\n    mpsc::{unbounded, UnboundedReceiver},\n    oneshot::channel,\n};\nuse serde::Deserialize;\nuse std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Mutex};\nuse wasm_bindgen::{prelude::Closure, JsCast, JsValue};\nuse web_sys::AudioContext;\nuse web_sys::{AudioBuffer, HtmlElement};\nuse web_sys::{CanvasRenderingContext2d, HtmlImageElement};\n\n#[derive(Deserialize, Clone)]\npub struct SheetRect {\n    pub x: i16,\n    pub y: i16,\n    pub w: i16,\n    pub h: i16,\n}\n\n#[derive(Deserialize, Clone)]\n#[serde(rename_all = \"camelCase\")]\npub struct Cell {\n    pub frame: SheetRect,\n    pub sprite_source_size: SheetRect,\n}\n\n#[derive(Deserialize, Clone)]\npub struct Sheet {\n    pub frames: HashMap<String, Cell>,\n}\n\n#[derive(Clone, Copy, Default)]\npub struct Point {\n    pub x: i16,\n    pub y: i16,\n}\n\n#[derive(Default)]\npub struct Rect {\n    pub position: Point,\n    pub width: i16,\n    pub height: i16,\n}\n\nimpl Rect {\n    pub const fn new(position: Point, width: i16, height: i16) -> Self {\n        Rect {\n            position,\n            width,\n            height,\n        }\n    }\n\n    pub const fn new_from_x_y(x: i16, y: i16, width: i16, height: i16) -> Self {\n        Rect::new(Point { x, y }, width, height)\n    }\n\n    pub fn intersects(&self, rect: &Rect) -> bool {\n        self.x() < rect.right()\n            && self.right() > rect.x()\n            && self.y() < rect.bottom()\n            && self.bottom() > rect.y()\n    }\n\n    pub fn right(&self) -> i16 {\n        self.x() + self.width\n    }\n\n    pub fn bottom(&self) -> i16 {\n        self.y() + self.height\n    }\n\n    pub fn set_x(&mut self, x: i16) {\n        self.position.x = x\n    }\n\n    pub fn x(&self) -> i16 {\n        self.position.x\n    }\n\n    pub fn y(&self) -> i16 {\n        self.position.y\n    }\n}\n\npub struct Renderer {\n    context: CanvasRenderingContext2d,\n}\n\nimpl Renderer {\n    pub fn clear(&self, rect: &Rect) {\n        self.context.clear_rect(\n            rect.x().into(),\n            rect.y().into(),\n            rect.width.into(),\n            rect.height.into(),\n        );\n    }\n\n    pub fn draw_image(&self, image: &HtmlImageElement, frame: &Rect, destination: &Rect) {\n        self.context\n            .draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(\n                image,\n                frame.x().into(),\n                frame.y().into(),\n                frame.width.into(),\n                frame.height.into(),\n                destination.x().into(),\n                destination.y().into(),\n                destination.width.into(),\n                destination.height.into(),\n            )\n            .expect(\"Drawing is throwing exceptions! Unrecoverable error.\");\n    }\n\n    pub fn draw_entire_image(&self, image: &HtmlImageElement, position: &Point) {\n        self.context\n            .draw_image_with_html_image_element(image, position.x.into(), position.y.into())\n            .expect(\"Drawing is throwing exceptions! Unrecoverable error.\");\n    }\n\n    #[allow(dead_code)]\n    pub fn draw_rect(&self, bounding_box: &Rect) {\n        self.context.set_stroke_style(&JsValue::from_str(\"#FF0000\"));\n        self.context.begin_path();\n        self.context.rect(\n            bounding_box.x().into(),\n            bounding_box.y().into(),\n            bounding_box.width.into(),\n            bounding_box.height.into(),\n        );\n        self.context.stroke();\n    }\n\n    #[allow(dead_code)]\n    pub fn draw_text(&self, text: &str, location: &Point) -> Result<()> {\n        self.context.set_font(\"16pt Ken Future\");\n        self.context\n            .fill_text(text, location.x.into(), location.y.into())\n            .map_err(|err| anyhow!(\"Error filling text {:#?}\", err))?;\n        Ok(())\n    }\n}\n\npub async fn load_image(source: &str) -> Result<HtmlImageElement> {\n    let image = browser::new_image()?;\n\n    let (complete_tx, complete_rx) = channel::<Result<()>>();\n    let success_tx = Rc::new(Mutex::new(Some(complete_tx)));\n    let error_tx = Rc::clone(&success_tx);\n    let success_callback = browser::closure_once(move || {\n        if let Some(success_tx) = success_tx.lock().ok().and_then(|mut opt| opt.take()) {\n            if let Err(err) = success_tx.send(Ok(())) {\n                error!(\"Could not send successful image loaded message! {:#?}\", err);\n            }\n        }\n    });\n\n    let error_callback: Closure<dyn FnMut(JsValue)> = browser::closure_once(move |err| {\n        if let Some(error_tx) = error_tx.lock().ok().and_then(|mut opt| opt.take()) {\n            if let Err(err) = error_tx.send(Err(anyhow!(\"Error Loading Image: {:#?}\", err))) {\n                error!(\"Could not send error message on loading image! {:#?}\", err);\n            }\n        }\n    });\n\n    image.set_onload(Some(success_callback.as_ref().unchecked_ref()));\n    image.set_onerror(Some(error_callback.as_ref().unchecked_ref()));\n    image.set_src(source);\n\n    complete_rx.await??;\n\n    Ok(image)\n}\n\n#[async_trait(?Send)]\npub trait Game {\n    async fn initialize(&self) -> Result<Box<dyn Game>>;\n    fn update(&mut self, keystate: &KeyState);\n    fn draw(&self, renderer: &Renderer);\n}\n\n// Sixty Frames per second, converted to a frame length in milliseconds\nconst FRAME_SIZE: f32 = 1.0 / 60.0 * 1000.0;\npub struct GameLoop {\n    last_frame: f64,\n    accumulated_delta: f32,\n}\ntype SharedLoopClosure = Rc<RefCell<Option<LoopClosure>>>;\n\nimpl GameLoop {\n    pub async fn start(game: impl Game) -> Result<()> {\n        let mut keyevent_receiver = prepare_input()?;\n        let mut game = game.initialize().await?;\n\n        let mut game_loop = GameLoop {\n            last_frame: browser::now()?,\n            accumulated_delta: 0.0,\n        };\n\n        let renderer = Renderer {\n            context: browser::context()?,\n        };\n\n        let f: SharedLoopClosure = Rc::new(RefCell::new(None));\n        let g = f.clone();\n\n        let mut keystate = KeyState::new();\n\n        *g.borrow_mut() = Some(browser::create_raf_closure(move |perf: f64| {\n            process_input(&mut keystate, &mut keyevent_receiver);\n\n            let frame_time = perf - game_loop.last_frame;\n            game_loop.accumulated_delta += frame_time as f32;\n            while game_loop.accumulated_delta > FRAME_SIZE {\n                game.update(&keystate);\n                game_loop.accumulated_delta -= FRAME_SIZE;\n            }\n            game_loop.last_frame = perf;\n            game.draw(&renderer);\n\n            if cfg!(debug_assertions) {\n                unsafe {\n                    draw_frame_rate(&renderer, frame_time);\n                }\n            }\n\n            browser::request_animation_frame(f.borrow().as_ref().unwrap()).unwrap();\n        }));\n\n        browser::request_animation_frame(\n            g.borrow()\n                .as_ref()\n                .ok_or_else(|| anyhow!(\"GameLoop: Loop is None\"))?,\n        )?;\n        Ok(())\n    }\n}\n\nunsafe fn draw_frame_rate(renderer: &Renderer, frame_time: f64) {\n    static mut FRAMES_COUNTED: i32 = 0;\n    static mut TOTAL_FRAME_TIME: f64 = 0.0;\n    static mut FRAME_RATE: i32 = 0;\n\n    FRAMES_COUNTED += 1;\n    TOTAL_FRAME_TIME += frame_time;\n\n    if TOTAL_FRAME_TIME > 1000.0 {\n        FRAME_RATE = FRAMES_COUNTED;\n        TOTAL_FRAME_TIME = 0.0;\n        FRAMES_COUNTED = 0;\n    }\n\n    if let Err(err) = renderer.draw_text(\n        &format!(\"Frame Rate {}\", FRAME_RATE),\n        &Point { x: 400, y: 100 },\n    ) {\n        error!(\"Could not draw text {:#?}\", err);\n    };\n}\n\n#[derive(Debug)]\npub struct KeyState {\n    pressed_keys: HashMap<String, web_sys::KeyboardEvent>,\n}\n\nimpl KeyState {\n    fn new() -> Self {\n        KeyState {\n            pressed_keys: HashMap::new(),\n        }\n    }\n\n    pub fn is_pressed(&self, code: &str) -> bool {\n        self.pressed_keys.contains_key(code)\n    }\n\n    fn set_pressed(&mut self, code: &str, event: web_sys::KeyboardEvent) {\n        self.pressed_keys.insert(code.into(), event);\n    }\n\n    fn set_released(&mut self, code: &str) {\n        self.pressed_keys.remove(code);\n    }\n}\n\nenum KeyPress {\n    KeyUp(web_sys::KeyboardEvent),\n    KeyDown(web_sys::KeyboardEvent),\n}\n\nfn process_input(state: &mut KeyState, keyevent_receiver: &mut UnboundedReceiver<KeyPress>) {\n    loop {\n        match keyevent_receiver.try_next() {\n            Ok(None) => break,\n            Err(_err) => break,\n            Ok(Some(evt)) => match evt {\n                KeyPress::KeyUp(evt) => state.set_released(&evt.code()),\n                KeyPress::KeyDown(evt) => state.set_pressed(&evt.code(), evt),\n            },\n        };\n    }\n}\n\nfn prepare_input() -> Result<UnboundedReceiver<KeyPress>> {\n    let (keydown_sender, keyevent_receiver) = unbounded();\n    let keydown_sender = Rc::new(RefCell::new(keydown_sender));\n    let keyup_sender = Rc::clone(&keydown_sender);\n    let onkeydown = browser::closure_wrap(Box::new(move |keycode: web_sys::KeyboardEvent| {\n        if let Err(err) = keydown_sender\n            .borrow_mut()\n            .start_send(KeyPress::KeyDown(keycode))\n        {\n            error!(\"Could not send keyDown message {:#?}\", err);\n        }\n    }) as Box<dyn FnMut(web_sys::KeyboardEvent)>);\n\n    let onkeyup = browser::closure_wrap(Box::new(move |keycode: web_sys::KeyboardEvent| {\n        if let Err(err) = keyup_sender\n            .borrow_mut()\n            .start_send(KeyPress::KeyUp(keycode))\n        {\n            error!(\"Could not send keyUp message {:#?}\", err);\n        }\n    }) as Box<dyn FnMut(web_sys::KeyboardEvent)>);\n\n    browser::canvas()?.set_onkeydown(Some(onkeydown.as_ref().unchecked_ref()));\n    browser::canvas()?.set_onkeyup(Some(onkeyup.as_ref().unchecked_ref()));\n    onkeydown.forget();\n    onkeyup.forget();\n\n    Ok(keyevent_receiver)\n}\n\npub fn add_click_handler(elem: HtmlElement) -> UnboundedReceiver<()> {\n    let (mut click_sender, click_receiver) = unbounded();\n\n    let on_click = browser::closure_wrap(Box::new(move || {\n        if let Err(err) = click_sender.start_send(()) {\n            error!(\"Could not send click message {:#?}\", err);\n        }\n    }) as Box<dyn FnMut()>);\n\n    elem.set_onclick(Some(on_click.as_ref().unchecked_ref()));\n    on_click.forget();\n    click_receiver\n}\n\npub struct Image {\n    element: HtmlImageElement,\n    bounding_box: Rect,\n}\n\nimpl Image {\n    pub fn new(element: HtmlImageElement, position: Point) -> Self {\n        let bounding_box = Rect::new(position, element.width() as i16, element.height() as i16);\n\n        Self {\n            element,\n            bounding_box,\n        }\n    }\n\n    pub fn draw(&self, renderer: &Renderer) {\n        renderer.draw_entire_image(&self.element, &self.bounding_box.position)\n    }\n\n    pub fn bounding_box(&self) -> &Rect {\n        &self.bounding_box\n    }\n\n    pub fn move_horizontally(&mut self, distance: i16) {\n        self.set_x(self.bounding_box.x() + distance);\n    }\n\n    pub fn set_x(&mut self, x: i16) {\n        self.bounding_box.set_x(x);\n    }\n\n    pub fn right(&self) -> i16 {\n        self.bounding_box.right()\n    }\n}\n\npub struct SpriteSheet {\n    sheet: Sheet,\n    image: HtmlImageElement,\n}\n\nimpl SpriteSheet {\n    pub fn new(sheet: Sheet, image: HtmlImageElement) -> Self {\n        SpriteSheet { sheet, image }\n    }\n\n    pub fn cell(&self, name: &str) -> Option<&Cell> {\n        self.sheet.frames.get(name)\n    }\n\n    pub fn draw(&self, renderer: &Renderer, source: &Rect, destination: &Rect) {\n        renderer.draw_image(&self.image, source, destination);\n    }\n}\n\n#[derive(Clone)]\npub struct Audio {\n    context: AudioContext,\n}\n\n#[derive(Clone)]\npub struct Sound {\n    pub buffer: AudioBuffer,\n}\n\nimpl Audio {\n    pub fn new() -> Result<Self> {\n        Ok(Audio {\n            context: sound::create_audio_context()?,\n        })\n    }\n\n    pub async fn load_sound(&self, filename: &str) -> Result<Sound> {\n        let array_buffer = browser::fetch_array_buffer(filename).await?;\n\n        let audio_buffer = sound::decode_audio_data(&self.context, &array_buffer).await?;\n\n        Ok(Sound {\n            buffer: audio_buffer,\n        })\n    }\n\n    pub fn play_sound(&self, sound: &Sound) -> Result<()> {\n        sound::play_sound(&self.context, &sound.buffer, sound::LOOPING::No)\n    }\n\n    pub fn play_looping_sound(&self, sound: &Sound) -> Result<()> {\n        sound::play_sound(&self.context, &sound.buffer, sound::LOOPING::Yes)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn two_rects_that_intersect_on_the_left() {\n        let rect1 = Rect {\n            position: Point { x: 10, y: 10 },\n            height: 100,\n            width: 100,\n        };\n\n        let rect2 = Rect {\n            position: Point { x: 0, y: 10 },\n            height: 100,\n            width: 100,\n        };\n\n        assert_eq!(rect2.intersects(&rect1), true);\n    }\n}\n"
  },
  {
    "path": "src/game.rs",
    "content": "use std::rc::Rc;\n\nuse anyhow::{anyhow, Result};\nuse async_trait::async_trait;\nuse futures::channel::mpsc::UnboundedReceiver;\nuse rand::prelude::*;\nuse web_sys::HtmlImageElement;\n\nuse self::red_hat_boy_states::*;\nuse crate::{\n    browser,\n    engine::{\n        self, Audio, Cell, Game, Image, KeyState, Point, Rect, Renderer, Sheet, Sound, SpriteSheet,\n    },\n    segments::*,\n};\n\nconst HEIGHT: i16 = 600;\nconst TIMELINE_MINIMUM: i16 = 1000;\nconst OBSTACLE_BUFFER: i16 = 20;\n\npub struct WalkTheDog {\n    machine: Option<WalkTheDogStateMachine>,\n}\n\nimpl WalkTheDog {\n    pub fn new() -> Self {\n        WalkTheDog { machine: None }\n    }\n}\n\nenum WalkTheDogStateMachine {\n    Ready(WalkTheDogState<Ready>),\n    Walking(WalkTheDogState<Walking>),\n    GameOver(WalkTheDogState<GameOver>),\n}\n\nimpl WalkTheDogStateMachine {\n    fn new(walk: Walk) -> Self {\n        WalkTheDogStateMachine::Ready(WalkTheDogState::new(walk))\n    }\n\n    fn update(self, keystate: &KeyState) -> Self {\n        match self {\n            WalkTheDogStateMachine::Ready(state) => state.update(keystate).into(),\n            WalkTheDogStateMachine::Walking(state) => state.update(keystate).into(),\n            WalkTheDogStateMachine::GameOver(state) => state.update().into(),\n        }\n    }\n\n    fn draw(&self, renderer: &Renderer) {\n        match self {\n            WalkTheDogStateMachine::Ready(state) => state.draw(renderer),\n            WalkTheDogStateMachine::Walking(state) => state.draw(renderer),\n            WalkTheDogStateMachine::GameOver(state) => state.draw(renderer),\n        };\n    }\n}\n\nstruct WalkTheDogState<T> {\n    _state: T,\n    walk: Walk,\n}\n\nimpl<T> WalkTheDogState<T> {\n    fn draw(&self, renderer: &Renderer) {\n        self.walk.draw(renderer);\n    }\n}\n\nstruct Ready;\n\nimpl WalkTheDogState<Ready> {\n    fn new(walk: Walk) -> WalkTheDogState<Ready> {\n        browser::draw_ui(\"<p id='score'>0</>\").unwrap();\n        WalkTheDogState {\n            _state: Ready,\n            walk,\n        }\n    }\n\n    fn run_right(&mut self) {\n        self.walk.boy.run_right();\n    }\n\n    fn start_running(mut self) -> WalkTheDogState<Walking> {\n        self.run_right();\n\n        WalkTheDogState {\n            _state: Walking,\n            walk: self.walk,\n        }\n    }\n\n    fn update(mut self, keystate: &KeyState) -> ReadyEndState {\n        self.walk.boy.update();\n        if keystate.is_pressed(\"ArrowRight\") {\n            ReadyEndState::Complete(self.start_running())\n        } else {\n            ReadyEndState::Continue(self)\n        }\n    }\n}\n\nenum ReadyEndState {\n    Complete(WalkTheDogState<Walking>),\n    Continue(WalkTheDogState<Ready>),\n}\n\nimpl From<ReadyEndState> for WalkTheDogStateMachine {\n    fn from(state: ReadyEndState) -> Self {\n        match state {\n            ReadyEndState::Complete(walking) => walking.into(),\n            ReadyEndState::Continue(ready) => ready.into(),\n        }\n    }\n}\n\nstruct Walking;\n\nimpl WalkTheDogState<Walking> {\n    fn end_game(self) -> WalkTheDogState<GameOver> {\n        let receiver = browser::draw_ui(\"<button id='new_game'>New Game</button>\")\n            .and_then(|_unit| browser::find_html_element_by_id(\"new_game\"))\n            .map(engine::add_click_handler)\n            .unwrap();\n\n        WalkTheDogState {\n            _state: GameOver {\n                new_game_event: receiver,\n            },\n\n            walk: self.walk,\n        }\n    }\n\n    fn update(mut self, keystate: &KeyState) -> WalkingEndState {\n        if keystate.is_pressed(\"Space\") {\n            self.walk.boy.jump();\n        }\n\n        if keystate.is_pressed(\"ArrowDown\") {\n            self.walk.boy.slide();\n        }\n\n        self.walk.boy.update();\n\n        let walking_speed = self.walk.velocity();\n        let [first_background, second_background] = &mut self.walk.backgrounds;\n        first_background.move_horizontally(walking_speed);\n        second_background.move_horizontally(walking_speed);\n\n        if first_background.right() < 0 {\n            first_background.set_x(second_background.right());\n        }\n        if second_background.right() < 0 {\n            second_background.set_x(first_background.right());\n        }\n\n        self.walk.obstacles.retain(|obstacle| obstacle.right() > 0);\n\n        self.walk.obstacles.iter_mut().for_each(|obstacle| {\n            obstacle.move_horizontally(walking_speed);\n            obstacle.check_intersection(&mut self.walk.boy);\n        });\n\n        if self.walk.timeline < TIMELINE_MINIMUM {\n            self.walk.generate_next_segment();\n        } else {\n            self.walk.timeline += walking_speed;\n        }\n\n        self.walk.score += 1;\n\n        if self.walk.knocked_out() {\n            WalkingEndState::Complete(self.end_game())\n        } else {\n            WalkingEndState::Continue(self)\n        }\n    }\n}\n\nenum WalkingEndState {\n    Continue(WalkTheDogState<Walking>),\n    Complete(WalkTheDogState<GameOver>),\n}\n\nimpl From<WalkingEndState> for WalkTheDogStateMachine {\n    fn from(state: WalkingEndState) -> Self {\n        match state {\n            WalkingEndState::Continue(walking) => walking.into(),\n            WalkingEndState::Complete(game_over) => game_over.into(),\n        }\n    }\n}\n\nstruct GameOver {\n    new_game_event: UnboundedReceiver<()>,\n}\n\nimpl WalkTheDogState<GameOver> {\n    fn update(mut self) -> GameOverEndState {\n        if self._state.new_game_pressed() {\n            GameOverEndState::Complete(self.new_game())\n        } else {\n            GameOverEndState::Continue(self)\n        }\n    }\n\n    fn new_game(self) -> WalkTheDogState<Ready> {\n        if let Err(err) = browser::hide_ui() {\n            error!(\"Error hiding the browser {:#?}\", err);\n        }\n\n        WalkTheDogState {\n            _state: Ready,\n            walk: Walk::reset(self.walk),\n        }\n    }\n}\n\nenum GameOverEndState {\n    Continue(WalkTheDogState<GameOver>),\n    Complete(WalkTheDogState<Ready>),\n}\n\nimpl From<GameOverEndState> for WalkTheDogStateMachine {\n    fn from(state: GameOverEndState) -> Self {\n        match state {\n            GameOverEndState::Continue(game_over) => game_over.into(),\n            GameOverEndState::Complete(ready) => ready.into(),\n        }\n    }\n}\n\nimpl GameOver {\n    fn new_game_pressed(&mut self) -> bool {\n        matches!(self.new_game_event.try_next(), Ok(Some(())))\n    }\n}\n\nimpl From<WalkTheDogState<Walking>> for WalkTheDogStateMachine {\n    fn from(state: WalkTheDogState<Walking>) -> Self {\n        WalkTheDogStateMachine::Walking(state)\n    }\n}\n\nimpl From<WalkTheDogState<GameOver>> for WalkTheDogStateMachine {\n    fn from(state: WalkTheDogState<GameOver>) -> Self {\n        WalkTheDogStateMachine::GameOver(state)\n    }\n}\n\nimpl From<WalkTheDogState<Ready>> for WalkTheDogStateMachine {\n    fn from(state: WalkTheDogState<Ready>) -> Self {\n        WalkTheDogStateMachine::Ready(state)\n    }\n}\n\npub trait Obstacle {\n    fn check_intersection(&self, boy: &mut RedHatBoy);\n    fn draw(&self, renderer: &Renderer);\n    fn move_horizontally(&mut self, x: i16);\n    fn right(&self) -> i16;\n}\n\npub struct Platform {\n    sheet: Rc<SpriteSheet>,\n    sprites: Vec<Cell>,\n    bounding_boxes: Vec<Rect>,\n    position: Point,\n}\n\nimpl Platform {\n    pub fn new(\n        sheet: Rc<SpriteSheet>,\n        position: Point,\n        sprite_names: &[&str],\n        bounding_boxes: &[Rect],\n    ) -> Self {\n        let sprites = sprite_names\n            .iter()\n            .filter_map(|sprite_name| sheet.cell(sprite_name).cloned())\n            .collect();\n\n        let bounding_boxes = bounding_boxes\n            .iter()\n            .map(|bounding_box| {\n                Rect::new_from_x_y(\n                    bounding_box.x() + position.x,\n                    bounding_box.y() + position.y,\n                    bounding_box.width,\n                    bounding_box.height,\n                )\n            })\n            .collect();\n\n        Platform {\n            sheet,\n            position,\n            sprites,\n            bounding_boxes,\n        }\n    }\n\n    fn bounding_boxes(&self) -> &Vec<Rect> {\n        &self.bounding_boxes\n    }\n}\n\nimpl Obstacle for Platform {\n    fn check_intersection(&self, boy: &mut RedHatBoy) {\n        if let Some(box_to_land_on) = self\n            .bounding_boxes()\n            .iter()\n            .find(|&bounding_box| boy.bounding_box().intersects(bounding_box))\n        {\n            if boy.velocity_y() > 0 && boy.pos_y() < self.position.y {\n                boy.land_on(box_to_land_on.y());\n            } else {\n                boy.knock_out();\n            }\n        }\n    }\n\n    fn draw(&self, renderer: &Renderer) {\n        let mut x = 0;\n        self.sprites.iter().for_each(|sprite| {\n            self.sheet.draw(\n                renderer,\n                &Rect::new_from_x_y(\n                    sprite.frame.x,\n                    sprite.frame.y,\n                    sprite.frame.w,\n                    sprite.frame.h,\n                ),\n                // Just use position and the standard widths in the tileset\n                &Rect::new_from_x_y(\n                    self.position.x + x,\n                    self.position.y,\n                    sprite.frame.w,\n                    sprite.frame.h,\n                ),\n            );\n            x += sprite.frame.w;\n        });\n    }\n\n    fn move_horizontally(&mut self, x: i16) {\n        self.position.x += x;\n        self.bounding_boxes.iter_mut().for_each(|bounding_box| {\n            bounding_box.set_x(bounding_box.position.x + x);\n        })\n    }\n\n    fn right(&self) -> i16 {\n        self.bounding_boxes()\n            .last()\n            .unwrap_or(&Rect::default())\n            .right()\n    }\n}\n\npub struct RedHatBoy {\n    state_machine: RedHatBoyStateMachine,\n    sprite_sheet: Sheet,\n    image: HtmlImageElement,\n}\n\nimpl RedHatBoy {\n    fn new(sprite_sheet: Sheet, image: HtmlImageElement, audio: Audio, sound: Sound) -> Self {\n        RedHatBoy {\n            state_machine: RedHatBoyStateMachine::Idle(RedHatBoyState::new(audio, sound)),\n            sprite_sheet,\n            image,\n        }\n    }\n\n    fn reset(boy: Self) -> Self {\n        RedHatBoy::new(\n            boy.sprite_sheet,\n            boy.image,\n            boy.state_machine.context().audio.clone(),\n            boy.state_machine.context().jump_sound.clone(),\n        )\n    }\n\n    fn run_right(&mut self) {\n        self.state_machine = self.state_machine.clone().transition(Event::Run);\n    }\n\n    fn slide(&mut self) {\n        self.state_machine = self.state_machine.clone().transition(Event::Slide);\n    }\n\n    fn jump(&mut self) {\n        self.state_machine = self.state_machine.clone().transition(Event::Jump);\n    }\n\n    fn knock_out(&mut self) {\n        self.state_machine = self.state_machine.clone().transition(Event::KnockOut);\n    }\n\n    fn land_on(&mut self, position: i16) {\n        self.state_machine = self.state_machine.clone().transition(Event::Land(position));\n    }\n\n    fn update(&mut self) {\n        self.state_machine = self.state_machine.clone().update();\n    }\n\n    fn knocked_out(&self) -> bool {\n        self.state_machine.knocked_out()\n    }\n\n    fn pos_y(&self) -> i16 {\n        self.state_machine.context().position.y\n    }\n\n    fn velocity_y(&self) -> i16 {\n        self.state_machine.context().velocity.y\n    }\n\n    fn walking_speed(&self) -> i16 {\n        self.state_machine.context().velocity.x\n    }\n\n    fn frame_name(&self) -> String {\n        format!(\n            \"{} ({}).png\",\n            self.state_machine.frame_name(),\n            (self.state_machine.context().frame / 3) + 1\n        )\n    }\n\n    fn current_sprite(&self) -> Option<&Cell> {\n        self.sprite_sheet.frames.get(&self.frame_name())\n    }\n\n    fn bounding_box(&self) -> Rect {\n        const X_OFFSET: i16 = 18;\n        const Y_OFFSET: i16 = 14;\n        const WIDTH_OFFSET: i16 = 28;\n        Rect::new_from_x_y(\n            self.destination_box().x() + X_OFFSET,\n            self.destination_box().y() + Y_OFFSET,\n            self.destination_box().width - WIDTH_OFFSET,\n            self.destination_box().height - Y_OFFSET,\n        )\n    }\n\n    fn destination_box(&self) -> Rect {\n        let sprite = self.current_sprite().expect(\"Cell not found\");\n\n        Rect::new_from_x_y(\n            self.state_machine.context().position.x + sprite.sprite_source_size.x,\n            self.state_machine.context().position.y + sprite.sprite_source_size.y,\n            sprite.frame.w,\n            sprite.frame.h,\n        )\n    }\n\n    fn draw(&self, renderer: &Renderer) {\n        let sprite = self.current_sprite().expect(\"Cell not found\");\n\n        renderer.draw_image(\n            &self.image,\n            &Rect::new_from_x_y(\n                sprite.frame.x,\n                sprite.frame.y,\n                sprite.frame.w,\n                sprite.frame.h,\n            ),\n            &self.destination_box(),\n        );\n    }\n}\n\n#[derive(Clone)]\nenum RedHatBoyStateMachine {\n    Idle(RedHatBoyState<Idle>),\n    Running(RedHatBoyState<Running>),\n    Sliding(RedHatBoyState<Sliding>),\n    Jumping(RedHatBoyState<Jumping>),\n    Falling(RedHatBoyState<Falling>),\n    KnockedOut(RedHatBoyState<KnockedOut>),\n}\n\npub enum Event {\n    Run,\n    Jump,\n    Slide,\n    KnockOut,\n    Land(i16),\n    Update,\n}\n\nimpl RedHatBoyStateMachine {\n    fn transition(self, event: Event) -> Self {\n        match (self.clone(), event) {\n            (RedHatBoyStateMachine::Idle(state), Event::Run) => state.run().into(),\n            (RedHatBoyStateMachine::Running(state), Event::Jump) => state.jump().into(),\n            (RedHatBoyStateMachine::Running(state), Event::Slide) => state.slide().into(),\n            (RedHatBoyStateMachine::Running(state), Event::KnockOut) => state.knock_out().into(),\n            (RedHatBoyStateMachine::Running(state), Event::Land(position)) => {\n                state.land_on(position).into()\n            }\n            (RedHatBoyStateMachine::Jumping(state), Event::Land(position)) => {\n                state.land_on(position).into()\n            }\n            (RedHatBoyStateMachine::Jumping(state), Event::KnockOut) => state.knock_out().into(),\n            (RedHatBoyStateMachine::Sliding(state), Event::KnockOut) => state.knock_out().into(),\n            (RedHatBoyStateMachine::Sliding(state), Event::Land(position)) => {\n                state.land_on(position).into()\n            }\n            (RedHatBoyStateMachine::Idle(state), Event::Update) => state.update().into(),\n            (RedHatBoyStateMachine::Running(state), Event::Update) => state.update().into(),\n            (RedHatBoyStateMachine::Jumping(state), Event::Update) => state.update().into(),\n            (RedHatBoyStateMachine::Sliding(state), Event::Update) => state.update().into(),\n            (RedHatBoyStateMachine::Falling(state), Event::Update) => state.update().into(),\n            _ => self,\n        }\n    }\n\n    fn frame_name(&self) -> &str {\n        match self {\n            RedHatBoyStateMachine::Idle(state) => state.frame_name(),\n            RedHatBoyStateMachine::Running(state) => state.frame_name(),\n            RedHatBoyStateMachine::Jumping(state) => state.frame_name(),\n            RedHatBoyStateMachine::Sliding(state) => state.frame_name(),\n            RedHatBoyStateMachine::Falling(state) => state.frame_name(),\n            RedHatBoyStateMachine::KnockedOut(state) => state.frame_name(),\n        }\n    }\n\n    fn context(&self) -> &RedHatBoyContext {\n        match self {\n            RedHatBoyStateMachine::Idle(state) => state.context(),\n            RedHatBoyStateMachine::Running(state) => state.context(),\n            RedHatBoyStateMachine::Jumping(state) => state.context(),\n            RedHatBoyStateMachine::Sliding(state) => state.context(),\n            RedHatBoyStateMachine::Falling(state) => state.context(),\n            RedHatBoyStateMachine::KnockedOut(state) => state.context(),\n        }\n    }\n\n    fn knocked_out(&self) -> bool {\n        matches!(self, RedHatBoyStateMachine::KnockedOut(_))\n    }\n\n    fn update(self) -> Self {\n        self.transition(Event::Update)\n    }\n}\n\nimpl From<RedHatBoyState<Idle>> for RedHatBoyStateMachine {\n    fn from(state: RedHatBoyState<Idle>) -> Self {\n        RedHatBoyStateMachine::Idle(state)\n    }\n}\n\nimpl From<RedHatBoyState<Running>> for RedHatBoyStateMachine {\n    fn from(state: RedHatBoyState<Running>) -> Self {\n        RedHatBoyStateMachine::Running(state)\n    }\n}\n\nimpl From<RedHatBoyState<Sliding>> for RedHatBoyStateMachine {\n    fn from(state: RedHatBoyState<Sliding>) -> Self {\n        RedHatBoyStateMachine::Sliding(state)\n    }\n}\n\nimpl From<RedHatBoyState<Jumping>> for RedHatBoyStateMachine {\n    fn from(state: RedHatBoyState<Jumping>) -> Self {\n        RedHatBoyStateMachine::Jumping(state)\n    }\n}\n\nimpl From<RedHatBoyState<Falling>> for RedHatBoyStateMachine {\n    fn from(state: RedHatBoyState<Falling>) -> Self {\n        RedHatBoyStateMachine::Falling(state)\n    }\n}\n\nimpl From<RedHatBoyState<KnockedOut>> for RedHatBoyStateMachine {\n    fn from(state: RedHatBoyState<KnockedOut>) -> Self {\n        RedHatBoyStateMachine::KnockedOut(state)\n    }\n}\n\nimpl From<SlidingEndState> for RedHatBoyStateMachine {\n    fn from(state: SlidingEndState) -> Self {\n        match state {\n            SlidingEndState::Sliding(sliding) => sliding.into(),\n            SlidingEndState::Running(running) => running.into(),\n        }\n    }\n}\n\nimpl From<JumpingEndState> for RedHatBoyStateMachine {\n    fn from(state: JumpingEndState) -> Self {\n        match state {\n            JumpingEndState::Jumping(jumping) => jumping.into(),\n            JumpingEndState::Landing(landing) => landing.into(),\n        }\n    }\n}\n\nimpl From<FallingEndState> for RedHatBoyStateMachine {\n    fn from(state: FallingEndState) -> Self {\n        match state {\n            FallingEndState::Falling(falling) => falling.into(),\n            FallingEndState::KnockedOut(knocked_out) => knocked_out.into(),\n        }\n    }\n}\n\nmod red_hat_boy_states {\n    use super::{Audio, Sound, HEIGHT};\n    use crate::engine::Point;\n\n    const FLOOR: i16 = 479;\n    const PLAYER_HEIGHT: i16 = HEIGHT - FLOOR;\n    const RUNNING_SPEED: i16 = 4;\n    const STARTING_POINT: i16 = -20;\n    const IDLE_FRAMES: u8 = 29;\n    const RUNNING_FRAMES: u8 = 23;\n    const JUMPING_FRAMES: u8 = 35;\n    const SLIDING_FRAMES: u8 = 14;\n    const FALLING_FRAMES: u8 = 29;\n    const IDLE_FRAME_NAME: &str = \"Idle\";\n    const RUN_FRAME_NAME: &str = \"Run\";\n    const SLIDING_FRAME_NAME: &str = \"Slide\";\n    const JUMPING_FRAME_NAME: &str = \"Jump\";\n    const FALLING_FRAME_NAME: &str = \"Dead\";\n    const JUMP_SPEED: i16 = -25;\n    const GRAVITY: i16 = 1;\n    const TERMINAL_VELOCITY: i16 = 20;\n\n    #[derive(Clone)]\n    pub struct RedHatBoyState<S> {\n        context: RedHatBoyContext,\n        _state: S,\n    }\n\n    impl<S> RedHatBoyState<S> {\n        pub fn context(&self) -> &RedHatBoyContext {\n            &self.context\n        }\n\n        fn update_context(&mut self, frames: u8) {\n            self.context = self.context.clone().update(frames);\n        }\n    }\n\n    #[derive(Copy, Clone)]\n    pub struct Idle;\n\n    impl RedHatBoyState<Idle> {\n        pub fn new(audio: Audio, jump_sound: Sound) -> Self {\n            RedHatBoyState {\n                context: RedHatBoyContext {\n                    frame: 0,\n                    position: Point {\n                        x: STARTING_POINT,\n                        y: FLOOR,\n                    },\n                    velocity: Point { x: 0, y: 0 },\n                    audio,\n                    jump_sound,\n                },\n                _state: Idle {},\n            }\n        }\n\n        pub fn frame_name(&self) -> &str {\n            IDLE_FRAME_NAME\n        }\n\n        pub fn update(mut self) -> RedHatBoyState<Idle> {\n            self.update_context(IDLE_FRAMES);\n            self\n        }\n\n        pub fn run(self) -> RedHatBoyState<Running> {\n            RedHatBoyState {\n                context: self.context.reset_frame().run_right(),\n                _state: Running {},\n            }\n        }\n    }\n\n    #[derive(Copy, Clone)]\n    pub struct Running;\n\n    impl RedHatBoyState<Running> {\n        pub fn frame_name(&self) -> &str {\n            RUN_FRAME_NAME\n        }\n\n        pub fn update(mut self) -> RedHatBoyState<Running> {\n            self.update_context(RUNNING_FRAMES);\n            self\n        }\n\n        pub fn jump(self) -> RedHatBoyState<Jumping> {\n            RedHatBoyState {\n                context: self\n                    .context\n                    .reset_frame()\n                    .set_vertical_velocity(JUMP_SPEED)\n                    .play_jump_sound(),\n                _state: Jumping {},\n            }\n        }\n\n        pub fn slide(self) -> RedHatBoyState<Sliding> {\n            RedHatBoyState {\n                context: self.context.reset_frame(),\n                _state: Sliding {},\n            }\n        }\n\n        pub fn knock_out(self) -> RedHatBoyState<Falling> {\n            RedHatBoyState {\n                context: self.context.reset_frame().stop(),\n                _state: Falling {},\n            }\n        }\n\n        pub fn land_on(self, position: i16) -> RedHatBoyState<Running> {\n            RedHatBoyState {\n                context: self.context.set_on(position),\n                _state: Running {},\n            }\n        }\n    }\n\n    #[derive(Copy, Clone)]\n    pub struct Jumping;\n\n    pub enum JumpingEndState {\n        Jumping(RedHatBoyState<Jumping>),\n        Landing(RedHatBoyState<Running>),\n    }\n\n    impl RedHatBoyState<Jumping> {\n        pub fn frame_name(&self) -> &str {\n            JUMPING_FRAME_NAME\n        }\n\n        pub fn knock_out(self) -> RedHatBoyState<Falling> {\n            RedHatBoyState {\n                context: self.context.reset_frame().stop(),\n                _state: Falling {},\n            }\n        }\n\n        pub fn update(mut self) -> JumpingEndState {\n            self.update_context(JUMPING_FRAMES);\n\n            if self.context.position.y >= FLOOR {\n                JumpingEndState::Landing(self.land_on(HEIGHT))\n            } else {\n                JumpingEndState::Jumping(self)\n            }\n        }\n\n        pub fn land_on(self, position: i16) -> RedHatBoyState<Running> {\n            RedHatBoyState {\n                context: self.context.reset_frame().set_on(position),\n                _state: Running,\n            }\n        }\n    }\n\n    #[derive(Copy, Clone)]\n    pub struct Sliding;\n\n    pub enum SlidingEndState {\n        Sliding(RedHatBoyState<Sliding>),\n        Running(RedHatBoyState<Running>),\n    }\n\n    impl RedHatBoyState<Sliding> {\n        pub fn frame_name(&self) -> &str {\n            SLIDING_FRAME_NAME\n        }\n\n        pub fn stand(self) -> RedHatBoyState<Running> {\n            RedHatBoyState {\n                context: self.context.reset_frame(),\n                _state: Running {},\n            }\n        }\n\n        pub fn knock_out(self) -> RedHatBoyState<Falling> {\n            RedHatBoyState {\n                context: self.context.reset_frame().stop(),\n                _state: Falling {},\n            }\n        }\n\n        pub fn update(mut self) -> SlidingEndState {\n            self.update_context(SLIDING_FRAMES);\n\n            if self.context.frame >= SLIDING_FRAMES {\n                SlidingEndState::Running(self.stand())\n            } else {\n                SlidingEndState::Sliding(self)\n            }\n        }\n\n        pub fn land_on(self, position: i16) -> RedHatBoyState<Sliding> {\n            RedHatBoyState {\n                context: self.context.set_on(position),\n                _state: Sliding {},\n            }\n        }\n    }\n\n    #[derive(Copy, Clone)]\n    pub struct Falling;\n\n    impl RedHatBoyState<Falling> {\n        pub fn frame_name(&self) -> &str {\n            FALLING_FRAME_NAME\n        }\n\n        pub fn knock_out(self) -> RedHatBoyState<KnockedOut> {\n            RedHatBoyState {\n                context: self.context,\n                _state: KnockedOut {},\n            }\n        }\n\n        pub fn update(mut self) -> FallingEndState {\n            self.update_context(FALLING_FRAMES);\n            if self.context.frame >= FALLING_FRAMES {\n                FallingEndState::KnockedOut(self.knock_out())\n            } else {\n                FallingEndState::Falling(self)\n            }\n        }\n    }\n\n    pub enum FallingEndState {\n        KnockedOut(RedHatBoyState<KnockedOut>),\n        Falling(RedHatBoyState<Falling>),\n    }\n\n    #[derive(Copy, Clone)]\n    pub struct KnockedOut;\n\n    impl RedHatBoyState<KnockedOut> {\n        pub fn frame_name(&self) -> &str {\n            FALLING_FRAME_NAME\n        }\n    }\n\n    #[derive(Clone)]\n    pub struct RedHatBoyContext {\n        pub frame: u8,\n        pub position: Point,\n        pub velocity: Point,\n        pub audio: Audio,\n        pub jump_sound: Sound,\n    }\n\n    impl RedHatBoyContext {\n        pub fn update(mut self, frame_count: u8) -> Self {\n            if self.velocity.y < TERMINAL_VELOCITY {\n                self.velocity.y += GRAVITY;\n            }\n\n            if self.frame < frame_count {\n                self.frame += 1;\n            } else {\n                self.frame = 0;\n            }\n\n            self.position.y += self.velocity.y;\n\n            if self.position.y > FLOOR {\n                self.position.y = FLOOR;\n            }\n\n            self\n        }\n\n        fn reset_frame(mut self) -> Self {\n            self.frame = 0;\n            self\n        }\n\n        fn set_vertical_velocity(mut self, y: i16) -> Self {\n            self.velocity.y = y;\n            self\n        }\n\n        fn run_right(mut self) -> Self {\n            self.velocity.x += RUNNING_SPEED;\n            self\n        }\n\n        fn stop(mut self) -> Self {\n            self.velocity.x = 0;\n            self.velocity.y = 0;\n            self\n        }\n\n        fn set_on(mut self, position: i16) -> Self {\n            let position = position - PLAYER_HEIGHT;\n            self.position.y = position;\n            self\n        }\n\n        fn play_jump_sound(self) -> Self {\n            if let Err(err) = self.audio.play_sound(&self.jump_sound) {\n                log!(\"Error playing jump sound {:#?}\", err);\n            }\n            self\n        }\n    }\n}\n\npub struct Walk {\n    obstacle_sheet: Rc<SpriteSheet>,\n    stone: HtmlImageElement,\n    boy: RedHatBoy,\n    backgrounds: [Image; 2],\n    obstacles: Vec<Box<dyn Obstacle>>,\n    timeline: i16,\n    score: u16,\n}\n\nimpl Walk {\n    fn knocked_out(&self) -> bool {\n        self.boy.knocked_out()\n    }\n\n    fn reset(walk: Self) -> Self {\n        let starting_obstacles =\n            stone_and_platform(walk.stone.clone(), walk.obstacle_sheet.clone(), 0);\n        let timeline = rightmost(&starting_obstacles);\n\n        Walk {\n            boy: RedHatBoy::reset(walk.boy),\n            backgrounds: walk.backgrounds,\n            obstacles: starting_obstacles,\n            obstacle_sheet: walk.obstacle_sheet,\n            stone: walk.stone,\n            score: 0,\n            timeline,\n        }\n    }\n\n    fn draw(&self, renderer: &Renderer) {\n        self.backgrounds.iter().for_each(|background| {\n            background.draw(renderer);\n        });\n        self.boy.draw(renderer);\n\n        self.obstacles.iter().for_each(|obstacle| {\n            obstacle.draw(renderer);\n        });\n        browser::find_html_element_by_id(\"score\")\n            .map(|element| element.set_inner_html(&format!(\"Score: {}\", self.score)))\n            .unwrap();\n    }\n\n    fn velocity(&self) -> i16 {\n        -self.boy.walking_speed()\n    }\n\n    fn generate_next_segment(&mut self) {\n        let mut rng = thread_rng();\n        let next_segment = rng.gen_range(0..2);\n\n        let mut next_obstacles = match next_segment {\n            0 => stone_and_platform(\n                self.stone.clone(),\n                self.obstacle_sheet.clone(),\n                self.timeline + OBSTACLE_BUFFER,\n            ),\n            1 => platform_and_stone(\n                self.stone.clone(),\n                self.obstacle_sheet.clone(),\n                self.timeline + OBSTACLE_BUFFER,\n            ),\n            _ => vec![],\n        };\n        self.timeline = rightmost(&next_obstacles);\n        self.obstacles.append(&mut next_obstacles);\n    }\n}\n\npub struct Barrier {\n    image: Image,\n}\n\nimpl Barrier {\n    pub fn new(image: Image) -> Self {\n        Barrier { image }\n    }\n}\n\nimpl Obstacle for Barrier {\n    fn check_intersection(&self, boy: &mut RedHatBoy) {\n        if boy.bounding_box().intersects(self.image.bounding_box()) {\n            boy.knock_out()\n        }\n    }\n\n    fn draw(&self, renderer: &Renderer) {\n        self.image.draw(renderer);\n    }\n\n    fn move_horizontally(&mut self, x: i16) {\n        self.image.move_horizontally(x);\n    }\n\n    fn right(&self) -> i16 {\n        self.image.right()\n    }\n}\n\n#[async_trait(?Send)]\nimpl Game for WalkTheDog {\n    async fn initialize(&self) -> Result<Box<dyn Game>> {\n        match self.machine {\n            None => {\n                let sheet = browser::fetch_json(\"rhb.json\")\n                    .await?\n                    .into_serde::<Sheet>()?;\n                let background = engine::load_image(\"BG.png\").await?;\n                let stone = engine::load_image(\"Stone.png\").await?;\n\n                let tiles = browser::fetch_json(\"tiles.json\").await?;\n\n                let sprite_sheet = Rc::new(SpriteSheet::new(\n                    tiles.into_serde::<Sheet>()?,\n                    engine::load_image(\"tiles.png\").await?,\n                ));\n\n                let audio = Audio::new()?;\n                let sound = audio.load_sound(\"SFX_Jump_23.mp3\").await?;\n                let background_music = audio.load_sound(\"background_song.mp3\").await?;\n                audio.play_looping_sound(&background_music)?;\n\n                let rhb = RedHatBoy::new(sheet, engine::load_image(\"rhb.png\").await?, audio, sound);\n\n                let background_width = background.width() as i16;\n                let starting_obstacles = stone_and_platform(stone.clone(), sprite_sheet.clone(), 0);\n                let timeline = rightmost(&starting_obstacles);\n\n                let machine = WalkTheDogStateMachine::new(Walk {\n                    boy: rhb,\n                    backgrounds: [\n                        Image::new(background.clone(), Point { x: 0, y: 0 }),\n                        Image::new(\n                            background,\n                            Point {\n                                x: background_width,\n                                y: 0,\n                            },\n                        ),\n                    ],\n                    obstacles: starting_obstacles,\n                    obstacle_sheet: sprite_sheet,\n                    score: 0,\n                    stone,\n                    timeline,\n                });\n\n                Ok(Box::new(WalkTheDog {\n                    machine: Some(machine),\n                }))\n            }\n            Some(_) => Err(anyhow!(\"Error: Game is already initialized!\")),\n        }\n    }\n\n    fn update(&mut self, keystate: &KeyState) {\n        if let Some(machine) = self.machine.take() {\n            self.machine.replace(machine.update(keystate));\n        }\n        assert!(self.machine.is_some());\n    }\n\n    fn draw(&self, renderer: &Renderer) {\n        renderer.clear(&Rect::new(Point { x: 0, y: 0 }, 600, HEIGHT));\n\n        if let Some(machine) = &self.machine {\n            machine.draw(renderer);\n        }\n    }\n}\n\nfn rightmost(obstacle_list: &[Box<dyn Obstacle>]) -> i16 {\n    obstacle_list\n        .iter()\n        .map(|obstacle| obstacle.right())\n        .max_by(|x, y| x.cmp(y))\n        .unwrap_or(0)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use futures::channel::mpsc::unbounded;\n    use std::collections::HashMap;\n    use web_sys::{AudioBuffer, AudioBufferOptions};\n\n    use wasm_bindgen_test::wasm_bindgen_test;\n\n    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);\n\n    #[wasm_bindgen_test]\n    fn test_transition_from_game_over_to_new_game() {\n        let (_, receiver) = unbounded();\n        let image = HtmlImageElement::new().unwrap();\n        let audio = Audio::new().unwrap();\n        let options = AudioBufferOptions::new(1, 3000.0);\n        let sound = Sound {\n            buffer: AudioBuffer::new(&options).unwrap(),\n        };\n        let rhb = RedHatBoy::new(\n            Sheet {\n                frames: HashMap::new(),\n            },\n            image.clone(),\n            audio,\n            sound,\n        );\n        let sprite_sheet = SpriteSheet::new(\n            Sheet {\n                frames: HashMap::new(),\n            },\n            image.clone(),\n        );\n        let walk = Walk {\n            boy: rhb,\n            backgrounds: [\n                Image::new(image.clone(), Point { x: 0, y: 0 }),\n                Image::new(image.clone(), Point { x: 0, y: 0 }),\n            ],\n            obstacles: vec![],\n            obstacle_sheet: Rc::new(sprite_sheet),\n            stone: image.clone(),\n            score: 0,\n            timeline: 0,\n        };\n\n        let document = browser::document().unwrap();\n        document\n            .body()\n            .unwrap()\n            .insert_adjacent_html(\"afterbegin\", \"<div id='ui'></div>\")\n            .unwrap();\n        browser::draw_ui(\"<p>This is the UI</p>\").unwrap();\n        let state = WalkTheDogState {\n            _state: GameOver {\n                new_game_event: receiver,\n            },\n            walk: walk,\n        };\n\n        state.new_game();\n\n        let ui = browser::find_html_element_by_id(\"ui\").unwrap();\n        assert_eq!(ui.child_element_count(), 0);\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "#[macro_use]\nmod browser;\nmod engine;\nmod game;\nmod segments;\nmod sound;\n\nuse engine::GameLoop;\nuse game::WalkTheDog;\nuse wasm_bindgen::prelude::*;\n\n// This is like the `main` function, except for JavaScript.\n#[wasm_bindgen(start)]\npub fn main_js() -> Result<(), JsValue> {\n    console_error_panic_hook::set_once();\n\n    browser::spawn_local(async move {\n        let game = WalkTheDog::new();\n\n        GameLoop::start(game)\n            .await\n            .expect(\"Could not start game loop\");\n    });\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/segments.rs",
    "content": "use std::rc::Rc;\nuse web_sys::HtmlImageElement;\n\nuse crate::engine::{Image, Point, Rect, SpriteSheet};\nuse crate::game::{Barrier, Obstacle, Platform};\n\nconst LOW_PLATFORM: i16 = 420;\nconst HIGH_PLATFORM: i16 = 375;\nconst FIRST_PLATFORM: i16 = 370;\n\nconst STONE_ON_GROUND: i16 = 546;\n\nconst FLOATING_PLATFORM_SPRITES: [&str; 3] = [\"13.png\", \"14.png\", \"15.png\"];\nconst PLATFORM_WIDTH: i16 = 384;\nconst PLATFORM_HEIGHT: i16 = 93;\nconst PLATFORM_EDGE_WIDTH: i16 = 60;\nconst PLATFORM_EDGE_HEIGHT: i16 = 54;\nconst FLOATING_PLATFORM_BOUNDING_BOXES: [Rect; 3] = [\n    Rect::new_from_x_y(0, 0, PLATFORM_EDGE_WIDTH, PLATFORM_EDGE_HEIGHT),\n    Rect::new_from_x_y(\n        PLATFORM_EDGE_WIDTH,\n        0,\n        PLATFORM_WIDTH - (PLATFORM_EDGE_WIDTH * 2),\n        PLATFORM_HEIGHT,\n    ),\n    Rect::new_from_x_y(\n        PLATFORM_WIDTH - PLATFORM_EDGE_WIDTH,\n        0,\n        PLATFORM_EDGE_WIDTH,\n        PLATFORM_EDGE_HEIGHT,\n    ),\n];\n\nfn create_floating_platform(sprite_sheet: Rc<SpriteSheet>, position: Point) -> Platform {\n    Platform::new(\n        sprite_sheet,\n        position,\n        &FLOATING_PLATFORM_SPRITES,\n        &FLOATING_PLATFORM_BOUNDING_BOXES,\n    )\n}\n\npub fn stone_and_platform(\n    stone: HtmlImageElement,\n    sprite_sheet: Rc<SpriteSheet>,\n    offset_x: i16,\n) -> Vec<Box<dyn Obstacle>> {\n    const INITIAL_STONE_OFFSET: i16 = 150;\n\n    vec![\n        Box::new(Barrier::new(Image::new(\n            stone,\n            Point {\n                x: offset_x + INITIAL_STONE_OFFSET,\n                y: STONE_ON_GROUND,\n            },\n        ))),\n        Box::new(create_floating_platform(\n            sprite_sheet,\n            Point {\n                x: offset_x + FIRST_PLATFORM,\n                y: LOW_PLATFORM,\n            },\n        )),\n    ]\n}\n\npub fn platform_and_stone(\n    stone: HtmlImageElement,\n    sprite_sheet: Rc<SpriteSheet>,\n    offset_x: i16,\n) -> Vec<Box<dyn Obstacle>> {\n    const INITIAL_STONE_OFFSET: i16 = 400;\n    const INITIAL_PLATFORM_OFFSET: i16 = 200;\n\n    vec![\n        Box::new(Barrier::new(Image::new(\n            stone,\n            Point {\n                x: offset_x + INITIAL_STONE_OFFSET,\n                y: STONE_ON_GROUND,\n            },\n        ))),\n        Box::new(create_floating_platform(\n            sprite_sheet,\n            Point {\n                x: offset_x + INITIAL_PLATFORM_OFFSET,\n                y: HIGH_PLATFORM,\n            },\n        )),\n    ]\n}\n"
  },
  {
    "path": "src/sound.rs",
    "content": "use anyhow::{anyhow, Result};\nuse js_sys::ArrayBuffer;\nuse wasm_bindgen::JsCast;\nuse wasm_bindgen_futures::JsFuture;\nuse web_sys::{AudioBuffer, AudioBufferSourceNode, AudioContext, AudioDestinationNode, AudioNode};\n\npub fn create_audio_context() -> Result<AudioContext> {\n    AudioContext::new().map_err(|err| anyhow!(\"Could not create audio context: {:#?}\", err))\n}\n\nfn create_buffer_source(ctx: &AudioContext) -> Result<AudioBufferSourceNode> {\n    ctx.create_buffer_source()\n        .map_err(|err| anyhow!(\"Error creating buffer source {:#?}\", err))\n}\n\nfn connect_with_audio_node(\n    buffer_source: &AudioBufferSourceNode,\n    destination: &AudioDestinationNode,\n) -> Result<AudioNode> {\n    buffer_source\n        .connect_with_audio_node(destination)\n        .map_err(|err| anyhow!(\"Error connecting audio source to destination {:#?}\", err))\n}\n\nfn create_track_source(ctx: &AudioContext, buffer: &AudioBuffer) -> Result<AudioBufferSourceNode> {\n    let track_source = create_buffer_source(ctx)?;\n    track_source.set_buffer(Some(buffer));\n    connect_with_audio_node(&track_source, &ctx.destination())?;\n    Ok(track_source)\n}\n\npub enum LOOPING {\n    No,\n    Yes,\n}\n\npub fn play_sound(ctx: &AudioContext, buffer: &AudioBuffer, looping: LOOPING) -> Result<()> {\n    let track_source = create_track_source(ctx, buffer)?;\n    if matches!(looping, LOOPING::Yes) {\n        track_source.set_loop(true);\n    }\n\n    track_source\n        .start()\n        .map_err(|err| anyhow!(\"Could not start sound! {:#?}\", err))\n}\n\npub async fn decode_audio_data(\n    ctx: &AudioContext,\n    array_buffer: &ArrayBuffer,\n) -> Result<AudioBuffer> {\n    JsFuture::from(\n        ctx.decode_audio_data(array_buffer)\n            .map_err(|err| anyhow!(\"Could not decode audio from array buffer {:#?}\", err))?,\n    )\n    .await\n    .map_err(|err| anyhow!(\"Could not convert promise to future {:#?}\", err))?\n    .dyn_into()\n    .map_err(|err| anyhow!(\"Could not cast into AudioBuffer {:#?}\", err))\n}\n"
  },
  {
    "path": "src/test_browser.rs",
    "content": ""
  },
  {
    "path": "static/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <title>My Rust + Webpack project!</title>\n  <link rel=\"stylesheet\" href=\"styles.css\" type=\"text/css\" media=\n  \"screen\">\n  <link rel=\"preload\" as=\"image\" href=\"Button.svg\">\n  <link rel=\"preload\" as=\"font\" href=\n  \"kenney_future_narrow-webfont.woff2\">\n</head>\n<body>\n  <div id=\"ui\"></div>\n  <canvas id=\"canvas\" style=\"outline: none\" tabindex=\"0\" height=\n  \"600\" width=\"600\">\n    Your browser does not support the Canvas.\n  </canvas>\n  <script src=\"index.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "static/rhb.json",
    "content": "{\"frames\": {\n\n\"Dead (1).png\":\n{\n\t\"frame\": {\"x\":0,\"y\":0,\"w\":71,\"h\":115},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":58,\"y\":8,\"w\":71,\"h\":115},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Dead (2).png\":\n{\n\t\"frame\": {\"x\":117,\"y\":0,\"w\":87,\"h\":114},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":45,\"y\":9,\"w\":87,\"h\":114},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Dead (3).png\":\n{\n\t\"frame\": {\"x\":234,\"y\":0,\"w\":97,\"h\":106},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":35,\"y\":18,\"w\":97,\"h\":106},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Dead (4).png\":\n{\n\t\"frame\": {\"x\":351,\"y\":0,\"w\":105,\"h\":91},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":22,\"y\":32,\"w\":105,\"h\":91},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Dead (5).png\":\n{\n\t\"frame\": {\"x\":468,\"y\":0,\"w\":107,\"h\":83},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":19,\"y\":45,\"w\":107,\"h\":83},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Dead (6).png\":\n{\n\t\"frame\": {\"x\":585,\"y\":0,\"w\":107,\"h\":70},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":17,\"y\":58,\"w\":107,\"h\":70},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Dead (7).png\":\n{\n\t\"frame\": {\"x\":702,\"y\":0,\"w\":109,\"h\":67},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":15,\"y\":59,\"w\":109,\"h\":67},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Dead (8).png\":\n{\n\t\"frame\": {\"x\":819,\"y\":0,\"w\":110,\"h\":68},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":13,\"y\":61,\"w\":110,\"h\":68},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Dead (9).png\":\n{\n\t\"frame\": {\"x\":936,\"y\":0,\"w\":115,\"h\":68},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":13,\"y\":61,\"w\":115,\"h\":68},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Dead (10).png\":\n{\n\t\"frame\": {\"x\":1053,\"y\":0,\"w\":117,\"h\":68},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":13,\"y\":61,\"w\":117,\"h\":68},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Hurt (1).png\":\n{\n\t\"frame\": {\"x\":1170,\"y\":0,\"w\":71,\"h\":115},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":58,\"y\":8,\"w\":71,\"h\":115},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Hurt (2).png\":\n{\n\t\"frame\": {\"x\":1287,\"y\":0,\"w\":69,\"h\":112},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":67,\"y\":11,\"w\":69,\"h\":112},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Hurt (3).png\":\n{\n\t\"frame\": {\"x\":1404,\"y\":0,\"w\":64,\"h\":103},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":78,\"y\":17,\"w\":64,\"h\":103},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Hurt (4).png\":\n{\n\t\"frame\": {\"x\":1521,\"y\":0,\"w\":63,\"h\":102},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":79,\"y\":18,\"w\":63,\"h\":102},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Hurt (5).png\":\n{\n\t\"frame\": {\"x\":1638,\"y\":0,\"w\":64,\"h\":102},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":79,\"y\":18,\"w\":64,\"h\":102},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Hurt (6).png\":\n{\n\t\"frame\": {\"x\":1755,\"y\":0,\"w\":64,\"h\":101},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":79,\"y\":19,\"w\":64,\"h\":101},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Hurt (7).png\":\n{\n\t\"frame\": {\"x\":1872,\"y\":0,\"w\":65,\"h\":101},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":79,\"y\":19,\"w\":65,\"h\":101},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Hurt (8).png\":\n{\n\t\"frame\": {\"x\":0,\"y\":122,\"w\":68,\"h\":111},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":69,\"y\":12,\"w\":68,\"h\":111},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Idle (1).png\":\n{\n\t\"frame\": {\"x\":117,\"y\":122,\"w\":71,\"h\":115},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":58,\"y\":8,\"w\":71,\"h\":115},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Idle (2).png\":\n{\n\t\"frame\": {\"x\":234,\"y\":122,\"w\":71,\"h\":115},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":58,\"y\":8,\"w\":71,\"h\":115},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Idle (3).png\":\n{\n\t\"frame\": {\"x\":351,\"y\":122,\"w\":70,\"h\":114},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":59,\"y\":9,\"w\":70,\"h\":114},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Idle (4).png\":\n{\n\t\"frame\": {\"x\":468,\"y\":122,\"w\":70,\"h\":114},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":59,\"y\":9,\"w\":70,\"h\":114},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Idle (5).png\":\n{\n\t\"frame\": {\"x\":585,\"y\":122,\"w\":70,\"h\":113},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":59,\"y\":10,\"w\":70,\"h\":113},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Idle (6).png\":\n{\n\t\"frame\": {\"x\":702,\"y\":122,\"w\":71,\"h\":113},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":59,\"y\":10,\"w\":71,\"h\":113},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Idle (7).png\":\n{\n\t\"frame\": {\"x\":819,\"y\":122,\"w\":71,\"h\":113},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":59,\"y\":10,\"w\":71,\"h\":113},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Idle (8).png\":\n{\n\t\"frame\": {\"x\":936,\"y\":122,\"w\":70,\"h\":113},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":59,\"y\":10,\"w\":70,\"h\":113},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Idle (9).png\":\n{\n\t\"frame\": {\"x\":1053,\"y\":122,\"w\":70,\"h\":114},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":59,\"y\":9,\"w\":70,\"h\":114},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Idle (10).png\":\n{\n\t\"frame\": {\"x\":1170,\"y\":122,\"w\":70,\"h\":114},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":59,\"y\":9,\"w\":70,\"h\":114},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (1).png\":\n{\n\t\"frame\": {\"x\":1287,\"y\":122,\"w\":71,\"h\":115},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":58,\"y\":8,\"w\":71,\"h\":115},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (2).png\":\n{\n\t\"frame\": {\"x\":1404,\"y\":122,\"w\":70,\"h\":110},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":69,\"y\":13,\"w\":70,\"h\":110},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (3).png\":\n{\n\t\"frame\": {\"x\":1521,\"y\":122,\"w\":69,\"h\":109},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":72,\"y\":14,\"w\":69,\"h\":109},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (4).png\":\n{\n\t\"frame\": {\"x\":1638,\"y\":122,\"w\":70,\"h\":119},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":58,\"y\":3,\"w\":70,\"h\":119},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (5).png\":\n{\n\t\"frame\": {\"x\":1755,\"y\":122,\"w\":71,\"h\":119},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":58,\"y\":3,\"w\":71,\"h\":119},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (6).png\":\n{\n\t\"frame\": {\"x\":1872,\"y\":122,\"w\":70,\"h\":119},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":59,\"y\":3,\"w\":70,\"h\":119},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (7).png\":\n{\n\t\"frame\": {\"x\":0,\"y\":244,\"w\":70,\"h\":119},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":59,\"y\":3,\"w\":70,\"h\":119},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (8).png\":\n{\n\t\"frame\": {\"x\":117,\"y\":244,\"w\":71,\"h\":119},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":58,\"y\":3,\"w\":71,\"h\":119},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (9).png\":\n{\n\t\"frame\": {\"x\":234,\"y\":244,\"w\":70,\"h\":119},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":58,\"y\":3,\"w\":70,\"h\":119},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (10).png\":\n{\n\t\"frame\": {\"x\":351,\"y\":244,\"w\":69,\"h\":114},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":64,\"y\":6,\"w\":69,\"h\":114},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (11).png\":\n{\n\t\"frame\": {\"x\":468,\"y\":244,\"w\":73,\"h\":109},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":64,\"y\":11,\"w\":73,\"h\":109},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Jump (12).png\":\n{\n\t\"frame\": {\"x\":585,\"y\":244,\"w\":68,\"h\":111},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":67,\"y\":11,\"w\":68,\"h\":111},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Run (1).png\":\n{\n\t\"frame\": {\"x\":702,\"y\":244,\"w\":71,\"h\":115},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":58,\"y\":8,\"w\":71,\"h\":115},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Run (2).png\":\n{\n\t\"frame\": {\"x\":819,\"y\":244,\"w\":75,\"h\":122},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":55,\"y\":5,\"w\":75,\"h\":122},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Run (3).png\":\n{\n\t\"frame\": {\"x\":936,\"y\":244,\"w\":75,\"h\":117},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":56,\"y\":4,\"w\":75,\"h\":117},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Run (4).png\":\n{\n\t\"frame\": {\"x\":1053,\"y\":244,\"w\":71,\"h\":113},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":57,\"y\":7,\"w\":71,\"h\":113},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Run (5).png\":\n{\n\t\"frame\": {\"x\":1170,\"y\":244,\"w\":71,\"h\":115},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":58,\"y\":8,\"w\":71,\"h\":115},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Run (6).png\":\n{\n\t\"frame\": {\"x\":1287,\"y\":244,\"w\":70,\"h\":120},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":57,\"y\":6,\"w\":70,\"h\":120},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Run (7).png\":\n{\n\t\"frame\": {\"x\":1404,\"y\":244,\"w\":71,\"h\":115},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":55,\"y\":5,\"w\":71,\"h\":115},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Run (8).png\":\n{\n\t\"frame\": {\"x\":1521,\"y\":244,\"w\":70,\"h\":115},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":57,\"y\":6,\"w\":70,\"h\":115},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Slide (1).png\":\n{\n\t\"frame\": {\"x\":1638,\"y\":244,\"w\":85,\"h\":100},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":45,\"y\":28,\"w\":85,\"h\":100},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Slide (2).png\":\n{\n\t\"frame\": {\"x\":1755,\"y\":244,\"w\":86,\"h\":100},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":44,\"y\":27,\"w\":86,\"h\":100},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Slide (3).png\":\n{\n\t\"frame\": {\"x\":1872,\"y\":244,\"w\":87,\"h\":98},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":43,\"y\":27,\"w\":87,\"h\":98},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Slide (4).png\":\n{\n\t\"frame\": {\"x\":1872,\"y\":244,\"w\":87,\"h\":98},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":43,\"y\":27,\"w\":87,\"h\":98},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n},\n\"Slide (5).png\":\n{\n\t\"frame\": {\"x\":1755,\"y\":244,\"w\":86,\"h\":100},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":44,\"y\":27,\"w\":86,\"h\":100},\n\t\"sourceSize\": {\"w\":160,\"h\":136}\n}},\n\"meta\": {\n\t\"app\": \"https://www.codeandweb.com/texturepacker\",\n\t\"version\": \"1.0\",\n\t\"image\": \"rhb_trimmed.png\",\n\t\"format\": \"RGBA8888\",\n\t\"size\": {\"w\":1989,\"h\":366},\n\t\"scale\": \"1\",\n\t\"smartupdate\": \"$TexturePacker:SmartUpdate:57b52b5f31c0bdebc34af7514c40da17:cbdcd04de8b7f111714940a6eac7b511:521d204853d0d2bba515b142dc3ea799$\"\n}\n}\n"
  },
  {
    "path": "static/styles.css",
    "content": "#ui {\n    position: absolute;\n}\n\n@font-face {\n  font-family: 'Ken Future';\n  src: url('kenney_future_narrow-webfont.woff2');\n}\n\n#score {\n    font-family: 'Ken Future';\n    font-size: 16pt;\n    width: 200px;\n    position: absolute;\n    left: 400px;\n    top: 40px;\n}\n\nbutton {\n    font-family: 'Ken Future';\n    background: -72px -60px url('Button.svg');\n    border: none;\n    width: 82px;\n    height: 33px;\n    position: absolute;\n    transform: scale(1.8) translate(150px, 100px);\n}\n\nbutton:hover {\n    background: -158px -60px url('Button.svg');\n}\n\nbutton:active {\n    background: -244px -60px url('Button.svg');\n}\n"
  },
  {
    "path": "static/tiles.json",
    "content": "{\"frames\": {\n\n\"1.png\":\n{\n\t\"frame\": {\"x\":1,\"y\":132,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"2.png\":\n{\n\t\"frame\": {\"x\":262,\"y\":1,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"3.png\":\n{\n\t\"frame\": {\"x\":261,\"y\":261,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"4.png\":\n{\n\t\"frame\": {\"x\":1,\"y\":1,\"w\":129,\"h\":129},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":129,\"h\":129},\n\t\"sourceSize\": {\"w\":129,\"h\":129}\n},\n\"5.png\":\n{\n\t\"frame\": {\"x\":392,\"y\":1,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"6.png\":\n{\n\t\"frame\": {\"x\":391,\"y\":131,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"7.png\":\n{\n\t\"frame\": {\"x\":521,\"y\":131,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"8.png\":\n{\n\t\"frame\": {\"x\":391,\"y\":261,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"9.png\":\n{\n\t\"frame\": {\"x\":521,\"y\":261,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"10.png\":\n{\n\t\"frame\": {\"x\":1,\"y\":262,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"11.png\":\n{\n\t\"frame\": {\"x\":131,\"y\":132,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"12.png\":\n{\n\t\"frame\": {\"x\":132,\"y\":1,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"13.png\":\n{\n\t\"frame\": {\"x\":261,\"y\":391,\"w\":128,\"h\":93},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":93},\n\t\"sourceSize\": {\"w\":128,\"h\":93}\n},\n\"14.png\":\n{\n\t\"frame\": {\"x\":391,\"y\":391,\"w\":128,\"h\":93},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":93},\n\t\"sourceSize\": {\"w\":128,\"h\":93}\n},\n\"15.png\":\n{\n\t\"frame\": {\"x\":521,\"y\":391,\"w\":128,\"h\":93},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":93},\n\t\"sourceSize\": {\"w\":128,\"h\":93}\n},\n\"16.png\":\n{\n\t\"frame\": {\"x\":131,\"y\":262,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n},\n\"17.png\":\n{\n\t\"frame\": {\"x\":522,\"y\":1,\"w\":128,\"h\":99},\n\t\"rotated\": true,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":99},\n\t\"sourceSize\": {\"w\":128,\"h\":99}\n},\n\"18.png\":\n{\n\t\"frame\": {\"x\":261,\"y\":131,\"w\":128,\"h\":128},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":128,\"h\":128},\n\t\"sourceSize\": {\"w\":128,\"h\":128}\n}},\n\"meta\": {\n\t\"app\": \"https://www.codeandweb.com/texturepacker\",\n\t\"version\": \"1.0\",\n\t\"image\": \"tiles.png\",\n\t\"format\": \"RGBA8888\",\n\t\"size\": {\"w\":650,\"h\":485},\n\t\"scale\": \"1\",\n\t\"smartupdate\": \"$TexturePacker:SmartUpdate:6e3fdfd4ed3d5bfdbef834bd6d5c9225:fb784722f87c0e64fd62408e9c7c372e:accbe1e7e294ded8391337fc1c446319$\"\n}\n}\n"
  },
  {
    "path": "tests/app.rs",
    "content": "use futures::prelude::*;\nuse wasm_bindgen::JsValue;\nuse wasm_bindgen_futures::JsFuture;\nuse wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};\n\nwasm_bindgen_test_configure!(run_in_browser);\n\n// This runs a unit test in native Rust, so it can only use Rust APIs.\n#[test]\nfn rust_test() {\n    assert_eq!(1, 1);\n}\n\n// This runs a unit test in the browser, so it can use browser APIs.\n#[wasm_bindgen_test]\nfn web_test() {\n    assert_eq!(1, 1);\n}\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require(\"path\");\nconst CopyPlugin = require(\"copy-webpack-plugin\");\nconst WasmPackPlugin = require(\"@wasm-tool/wasm-pack-plugin\");\n\nconst dist = path.resolve(__dirname, \"dist\");\n\nmodule.exports = {\n  mode: \"production\",\n  entry: {\n    index: \"./js/index.js\"\n  },\n  output: {\n    path: dist,\n    filename: \"[name].js\"\n  },\n  devServer: {\n    contentBase: dist,\n  },\n  plugins: [\n    new CopyPlugin([\n      path.resolve(__dirname, \"static\")\n    ]),\n\n    new WasmPackPlugin({\n      crateDirectory: __dirname,\n    }),\n  ]\n};\n"
  }
]