[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto"
  },
  {
    "path": ".gitignore",
    "content": "target\n.vscode\n.idea\nhtml/rocket.wasm\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"rocket\"\nversion = \"1.0.0\"\nauthors = [\"Adolfo Ochagavía <aochagavia92@gmail.com>\"]\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nclippy = { version = \"0.0.118\", optional = true }\nitertools-num = \"0.1.1\"\nlazy_static = \"1.0\"\nrand = \"0.3.18\"\npcg_rand = \"0.7.1\"\n\n[patch.crates-io]\nrand = { git = \"https://github.com/aochagavia/rand.git\", branch = \"wasm\" }\n\n[features]\ndefault = []\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Adolfo Ochagavía\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\nall copies 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\nTHE SOFTWARE.\n"
  },
  {
    "path": "html/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <style>\n      html, body {\n        width: 100%;\n        height: 100%;\n        margin: 0px;\n        border: 0;\n        overflow: hidden; /*  Disable scrollbars */\n        display: block;  /* No floating content on sides */\n      }\n      </style>\n  </head>\n\n  <body>\n    <canvas id=\"canvas\" style='position:absolute; left:0px; top:0px; width: 100%'></canvas>\n\n<script>\n\n\n  // Returns an object containing resources that will be used later for drawing\n  function resources() {\n    let res = {\n      player: document.createElement('canvas'),\n      enemy: document.createElement('canvas'),\n      bullet: document.createElement('canvas'),\n      particle: document.createElement('canvas')\n    }\n\n    // Particle\n    res.particle.width = 20;\n    res.particle.height = 20;\n    let pCtx = res.particle.getContext('2d');\n    pCtx.fillStyle = \"darkviolet\";\n    pCtx.beginPath();\n    pCtx.arc(10, 10, 10, 0, 2 * Math.PI);\n    pCtx.fill();\n\n    // Bullet\n    res.bullet.width = 6;\n    res.bullet.height = 6;\n    let bCtx = res.bullet.getContext('2d');\n    bCtx.fillStyle = \"blue\";\n    bCtx.beginPath();\n    bCtx.arc(3, 3, 3, 0, 2 * Math.PI);\n    bCtx.fill();\n\n    // Enemy\n    res.enemy.width = 20;\n    res.enemy.height = 20;\n    let eCtx = res.enemy.getContext('2d');\n    eCtx.fillStyle = \"yellow\";\n    eCtx.beginPath();\n    eCtx.arc(10, 10, 10, 0, 2 * Math.PI);\n    eCtx.fill();\n\n    // Player\n    res.player.width = 20;\n    res.player.height = 16;\n    let plCtx = res.player.getContext('2d');\n    plCtx.fillStyle = \"red\";\n    plCtx.beginPath();\n    plCtx.lineTo(20, 8);\n    plCtx.lineTo(0, 16);\n    plCtx.lineTo(0, 0);\n    plCtx.fill();\n\n    return res;\n  }\n\n  // We create this here because it will be used from within `imports`\n  let canvas = document.getElementById('canvas');\n\n  // Returns an object containing functions that will be linked to our wasm model\n  // This means that they can be called from Rust\n  function imports() {\n    const res = resources();\n    var ctx = canvas.getContext(\"2d\");\n\n    function clear_screen() {\n      ctx.fillStyle = \"black\";\n      ctx.fillRect(0, 0, canvas.width, canvas.height);\n    }\n\n    function draw_player(x, y, angle) {\n      ctx.translate(x, y);\n      ctx.rotate(angle);\n      ctx.translate(0, -8);\n      ctx.drawImage(res.player, 0, 0);\n      ctx.setTransform(1, 0, 0, 1, 0, 0);\n\n      ctx.fillStyle = \"black\";\n      //ctx.fillRect(x - 17, y - 12, 4, 4);\n    }\n\n    function draw_enemy(x, y) {\n      ctx.drawImage(res.enemy, x - 10, y - 10);\n    }\n\n    function draw_bullet(x, y) {\n      ctx.drawImage(res.bullet, x - 3, y - 3);\n    }\n\n    function draw_particle(x, y, radius) {\n      ctx.drawImage(res.particle, x - radius, y - radius, 2 * radius, 2 * radius);\n    }\n\n    function draw_score(x) {\n      ctx.fillStyle = \"orange\";\n      ctx.textBaseline = \"top\";\n      ctx.font = \"20px sans-serif\";\n      ctx.fillText('Score: ' + x, 10, 10)\n    }\n\n    // The real loading and running of our wasm starts here\n    let imports = { clear_screen, draw_player, draw_enemy, draw_bullet, draw_particle, draw_score };\n    imports.Math_atan = Math.atan;\n    imports.sin = Math.sin;\n    imports.cos = Math.cos;\n    return imports;\n  }\n\n  // Fetch and instantiate our wasm module\n  fetch(\"program.wasm\").then(response =>\n    response.arrayBuffer()\n  ).then(bytes =>\n    WebAssembly.instantiate(bytes, { env: imports() })\n  ).then(results => {\n    let module = {};\n    let mod = results.instance;\n    module.update = mod.exports.update;\n    module.toggle_shoot = mod.exports.toggle_shoot;\n    module.toggle_boost = mod.exports.toggle_boost;\n    module.toggle_turn_left = mod.exports.toggle_turn_left;\n    module.toggle_turn_right = mod.exports.toggle_turn_right;\n    module.resize = mod.exports.resize;\n    module.draw = mod.exports.draw;\n\n    // Input processing\n    function processKey(key, b) {\n      switch (key) {\n        case \"ArrowLeft\":\n          module.toggle_turn_left(b);\n          break;\n        case \"ArrowRight\":\n          module.toggle_turn_right(b);\n          break;\n        case \"ArrowUp\":\n          module.toggle_boost(b);\n          break;\n        case \" \":\n          module.toggle_shoot(b);\n          break;\n      }\n    }\n    document.addEventListener('keydown', e => processKey(e.key, true));\n    document.addEventListener('keyup', e => processKey(e.key, false));\n\n    // Resizing\n    function resize() {\n      // We make the canvas somewhat smaller to get some zooming\n      canvas.width = window.innerWidth * 0.8;\n      canvas.height = window.innerHeight * 0.8;\n      module.resize(canvas.width, canvas.height);\n    }\n    window.addEventListener('resize', () => {\n      resize();\n    });\n\n    // Game loop\n    let start = null;\n    let prevTimestamp = null;\n    let drawAndUpdate = (timestamp) => {\n      // Initialization\n      if (!prevTimestamp) {\n        start = timestamp;\n        prevTimestamp = timestamp;\n        requestAnimationFrame(drawAndUpdate);\n        return;\n      }\n\n      // Update and draw\n      let progress = (timestamp - prevTimestamp) / 1000;\n      module.update(progress);\n      module.draw();\n\n      // Some bookkeeping\n      prevTimestamp = timestamp;\n      requestAnimationFrame(drawAndUpdate);\n    };\n\n    resize();\n    drawAndUpdate();\n  });\n  </script>\n  </body>\n</html>\n"
  },
  {
    "path": "post_build.py",
    "content": "from shutil import copyfile\nfrom subprocess import call\n\ncopyfile('target/wasm32-unknown-unknown/release/rocket.wasm', 'html/rocket.wasm')\ncall(['wasm-gc', 'html/rocket.wasm', 'html/program.wasm'])\n"
  },
  {
    "path": "readme.md",
    "content": "Rocket on WASM\n==============\n\nAn adapted version of the [Rocket](https://github.com/aochagavia/rocket) game, running on WASM!\n\n[Read the blog post](https://ochagavia.nl/blog/rocket-a-rust-game-running-on-wasm/)\nabout the development of this WASM version (includes an embedded canvas where you can play the game).\n\n## Screenshot\n\n![Screenshot](screenshots/gameplay1.png)\n\n## How to play\n\nAs you can see in the screenshot, you are the red rocket and have to save the world from\nthe yellow invaders. To do so, you can use the following controls:\n\nKeyboard                | Action\n----------------------- | ------------\n<kbd>&uparrow;</kbd>    | Boost\n<kbd>&leftarrow;</kbd>  | Rotate left\n<kbd>&rightarrow;</kbd> | Rotate right\n<kbd>Space</kbd>        | Shoot\n\n## Compiling and running\n\nFollow the steps on the [hellorust website](https://www.hellorust.com/setup/wasm-target/)\nin order to set up everything. Besides the `wasm32-unknown-unknown` target, the `post_build.py`\nscript requires python 2.7 and `wasm-gc`.\n\nAfter setting things up, you should be able to compile the code using the commands below:\n\n```\ncargo build --release --target wasm32-unknown-unknown\npython post_build.py\n```\n\nThe generated wasm will be copied to the `html` directory and `wasm-gc`ed.\n\n```\npython -m SimpleHTTPServer\n```\n\nTry opening http://localhost:8000/ on your browser to check whether it works.\n\n## Related projects\n\n* [Running Rocket in a Python environment through WebAssembly](https://almarklein.org/python_and_webassembly.html)\n"
  },
  {
    "path": "src/controllers/collisions.rs",
    "content": "use game_state::GameState;\nuse geometry::{Collide, Position};\nuse util;\n\nconst SCORE_PER_ENEMY: u32 = 10;\n\npub struct CollisionsController;\n\nimpl CollisionsController {\n    pub fn handle_collisions(state: &mut GameState) {\n        CollisionsController::handle_bullet_collisions(state);\n        CollisionsController::handle_player_collisions(state);\n    }\n\n    /// Handles collisions between the bullets and the enemies\n    ///\n    /// When an enemy is reached by a bullet, both the enemy and the bullet\n    /// will be removed. Additionally, the score of the player will be increased.\n    fn handle_bullet_collisions(state: &mut GameState) {\n        let old_enemy_count = state.world.enemies.len();\n\n        // We introduce a scope to shorten the lifetime of the borrows below\n        {\n            let bullets = &mut state.world.bullets;\n            let enemies = &mut state.world.enemies;\n            let particles = &mut state.world.particles;\n\n            // Note: this is O(n * m) where n = amount of bullets and n = amount of enemies\n            // This is pretty bad, but we don't care because n and m are small\n            util::fast_retain(bullets, |bullet| {\n                // Remove the first enemy that collides with a bullet (if any)\n                // Add an explosion on its place\n                if let Some((index, position)) = enemies.iter().enumerate()\n                    .find(|&(_, enemy)| enemy.collides_with(bullet))\n                    .map(|(index, enemy)| (index, enemy.position()))\n                    {\n                        util::make_explosion(particles, &position, 10);\n                        enemies.remove(index);\n                        false\n                    } else {\n                    true\n                }\n            });\n        }\n\n        let killed_enemies = (old_enemy_count - state.world.enemies.len()) as u32;\n        state.score += SCORE_PER_ENEMY * killed_enemies;\n    }\n\n    /// Handles collisions between the player and the enemies\n    fn handle_player_collisions(state: &mut GameState) {\n        if state.world.enemies.iter().any(|enemy| state.world.player.collides_with(enemy)) {\n            // Make an explosion where the player was\n            let ppos = state.world.player.position();\n            util::make_explosion(&mut state.world.particles, &ppos, 8);\n\n            state.reset();\n        }\n    }\n}\n"
  },
  {
    "path": "src/controllers/input.rs",
    "content": "/// Active actions (toggled by user input)\n#[derive(Default)]\npub struct Actions {\n    pub rotate_left: bool,\n    pub rotate_right: bool,\n    pub boost: bool,\n    pub shoot: bool\n}\n"
  },
  {
    "path": "src/controllers/mod.rs",
    "content": "//! This module contains the game logic\n//!\n//! There are three main controllers: collisions, input and time\n\nmod collisions;\nmod input;\nmod time;\n\npub use self::collisions::CollisionsController;\npub use self::input::Actions;\npub use self::time::TimeController;\n"
  },
  {
    "path": "src/controllers/time.rs",
    "content": "use std::f64;\nuse rand::Rng;\n\nuse super::Actions;\nuse game_state::GameState;\nuse geometry::{Advance, Position, Point};\nuse models::{Bullet, Enemy, Particle, Vector};\nuse util;\n\n// Constants related to time\nconst BULLETS_PER_SECOND: f64 = 100.0;\nconst BULLET_RATE: f64 = 1.0 / BULLETS_PER_SECOND;\n\nconst ENEMY_SPAWNS_PER_SECOND: f64 = 1.0;\nconst ENEMY_SPAWN_RATE: f64 = 1.0 / ENEMY_SPAWNS_PER_SECOND;\n\nconst TRAIL_PARTICLES_PER_SECOND: f64 = 20.0;\nconst TRAIL_PARTICLE_RATE: f64 = 1.0 / TRAIL_PARTICLES_PER_SECOND;\n\n// Constants related to movement\n// Speed is measured in pixels per second\n// Rotation speed is measured in radians per second\nconst ADVANCE_SPEED: f64 = 200.0;\nconst BULLET_SPEED: f64 = 500.0;\nconst ENEMY_SPEED: f64 = 100.0;\nconst ROTATE_SPEED: f64 = 2.0 * f64::consts::PI;\n\nconst PLAYER_GRACE_AREA: f64 = 200.0;\n\n/// Timers to handle creation of bullets, enemies and particles\npub struct TimeController<T: Rng> {\n    /// A random number generator\n    rng: T,\n    current_time: f64,\n    last_tail_particle: f64,\n    last_shoot: f64,\n    last_spawned_enemy: f64\n}\n\nimpl<T: Rng> TimeController<T> {\n    pub fn new(rng: T) -> TimeController<T> {\n        TimeController {\n            rng,\n            current_time: 0.0,\n            last_tail_particle: 0.0,\n            last_shoot: 0.0,\n            last_spawned_enemy: 0.0\n        }\n    }\n\n    /// Updates the game\n    ///\n    /// `dt` is the amount of seconds that have passed since the last update\n    pub fn update_seconds(&mut self, dt: f64, actions: &Actions, state: &mut GameState) {\n        self.current_time += dt;\n\n        // Update rocket rotation\n        if actions.rotate_left {\n            *state.world.player.direction_mut() += -ROTATE_SPEED * dt;\n        }\n        if actions.rotate_right {\n            *state.world.player.direction_mut() += ROTATE_SPEED * dt;\n        };\n\n        // Set speed and advance the player with wrap around\n        let speed = if actions.boost { 2.0 * ADVANCE_SPEED } else { ADVANCE_SPEED };\n        state.world.player.advance_wrapping(dt * speed, state.world.size);\n\n        // Update particles\n        for particle in &mut state.world.particles {\n            particle.update(dt);\n        }\n\n        // Remove old particles\n        util::fast_retain(&mut state.world.particles, |p| p.ttl > 0.0);\n\n        // Add new particles at the player's position, to leave a trail\n        if self.current_time - self.last_tail_particle > TRAIL_PARTICLE_RATE {\n            self.last_tail_particle = self.current_time;\n            state.world.particles.push(Particle::new(state.world.player.vector.clone().invert(),\n                                                    0.5));\n        }\n\n        // Add bullets\n        if actions.shoot && self.current_time - self.last_shoot > BULLET_RATE {\n            self.last_shoot = self.current_time;\n            state.world.bullets.push(Bullet::new(Vector::new(state.world.player.front(),\n                                                            state.world.player.direction())));\n        }\n\n        // Advance bullets\n        for bullet in &mut state.world.bullets {\n            bullet.update(dt * BULLET_SPEED);\n        }\n\n        // Remove bullets outside the viewport\n        {\n            // Shorten the lifetime of size\n            let size = &state.world.size;\n            util::fast_retain(&mut state.world.bullets, |b| size.contains(b.position()));\n        }\n\n        // Spawn enemies at random locations\n        if self.current_time - self.last_spawned_enemy > ENEMY_SPAWN_RATE {\n            self.last_spawned_enemy = self.current_time;\n\n            let player_pos: &Vector = &state.world.player.vector;\n            let mut enemy_pos;\n            // We loop here, just in case the new enemy random position is exactly equal\n            // to the players current position, this would break our calculations below\n            loop {\n                enemy_pos = Vector::random(&mut self.rng, state.world.size);\n                if enemy_pos.position != player_pos.position {\n                    break;\n                }\n            }\n            // Check if the newly spawned enemy is inside the player's grace area,\n            // if so, we push its spawn point to the edge of the area\n            if enemy_pos.position.intersect_circle(&player_pos.position, PLAYER_GRACE_AREA) {\n                let length: f64 = enemy_pos.position.squared_distance_to(&player_pos.position).sqrt();\n                let dp: Point = enemy_pos.position - player_pos.position;\n                enemy_pos.position = player_pos.position + dp / length * PLAYER_GRACE_AREA;\n            }\n\n            let new_enemy = Enemy::new(enemy_pos);\n            state.world.enemies.push(new_enemy);\n        }\n\n        // Move enemies in the player's direction\n        for enemy in &mut state.world.enemies {\n            enemy.update(dt * ENEMY_SPEED, state.world.player.position());\n        }\n    }\n}\n"
  },
  {
    "path": "src/game_state.rs",
    "content": "use pcg_rand::Pcg32Basic;\nuse rand::SeedableRng;\n\nuse geometry::{Position, Size};\nuse models::World;\n\n/// The data structure that contains the state of the game\npub struct GameState {\n    /// The world contains everything that needs to be drawn\n    pub world: World,\n    /// The current score of the player\n    pub score: u32\n}\n\nimpl GameState {\n    /// Returns a new `GameState` containing a `World` of the given `Size`\n    pub fn new(size: Size) -> GameState {\n        let mut rng = Pcg32Basic::from_seed([42, 42]);\n        GameState {\n            world: World::new(&mut rng, size),\n            score: 0\n        }\n    }\n\n    /// Reset our game-state\n    pub fn reset(&mut self) {\n        let mut rng = Pcg32Basic::from_seed([42, 42]);\n\n        // Reset player position\n        *self.world.player.x_mut() = self.world.size.random_x(&mut rng);\n        *self.world.player.y_mut() = self.world.size.random_y(&mut rng);\n\n        // Reset score\n        self.score = 0;\n\n        // Remove all enemies and bullets\n        self.world.bullets.clear();\n        self.world.enemies.clear();\n    }\n}\n"
  },
  {
    "path": "src/geometry/mod.rs",
    "content": "mod point;\nmod size;\nmod traits;\n\npub use self::point::Point;\npub use self::size::Size;\npub use self::traits::{Position, Advance, Collide};"
  },
  {
    "path": "src/geometry/point.rs",
    "content": "use rand::Rng;\n\nuse super::Size;\n\nuse std::ops::{Add, Sub, Mul, Div};\n\n/// A `Point` represents a position in space\n#[derive(Clone, Default, Copy)]\npub struct Point {\n    pub x: f64,\n    pub y: f64\n}\n\nimpl Point {\n    /// Returns a new `Point` with the given coordinates\n    pub fn new(x: f64, y: f64) -> Point {\n        Point { x: x, y: y }\n    }\n\n    /// Returns a random `Point` within the given bounds (exclusive)\n    pub fn random<R: Rng>(rng: &mut R, bounds: Size) -> Point {\n        Point {\n            x: rng.gen_range(0.0, bounds.width),\n            y: rng.gen_range(0.0, bounds.height)\n        }\n    }\n\n    /// Returns the squared distance from this point to the given one\n    pub fn squared_distance_to(&self, target: &Point) -> f64 {\n        (self.x - target.x) * (self.x - target.x)\n            + (self.y - target.y) * (self.y - target.y)\n    }\n\n    /// Rotates the point through the origin in the given angle (radians)\n    pub fn rotate(mut self, radians: f64) -> Point {\n        let radius = (self.x * self.x + self.y * self.y).sqrt();\n        let point_angle = (self.y / self.x).atan();\n        let final_angle = point_angle + radians;\n        self.x = final_angle.cos() * radius;\n        self.y = final_angle.sin() * radius;\n        self\n    }\n\n    /// Translates the point by another point\n    pub fn translate(mut self, other: &Point) -> Point {\n        self.x += other.x;\n        self.y += other.y;\n        self\n    }\n\n    /// Checks if this point is contained in a circle\n    pub fn intersect_circle(self, center: &Point, radius: f64) -> bool {\n        (self.x - center.x).powi(2) +\n            (self.y - center.y).powi(2) < radius.powi(2)\n    }\n}\n\n/// Implements '==' for Point, as well as its inverse '!='\nimpl PartialEq for Point {\n    fn eq (&self, _rhs: &Self) -> bool {\n        (self.x == _rhs.x) && (self.y == _rhs.y)\n    }\n}\n\n/// Implements the '+' operator for Point + Point\nimpl Add for Point {\n    type Output = Point;\n\n    fn add(self, _rhs: Point) -> Point {\n        Point {\n            x: self.x + _rhs.x,\n            y: self.y + _rhs.y,\n        }\n    }\n}\n\n/// Implements the '+' operator for Point + f64\nimpl Add<f64> for Point {\n    type Output = Point;\n\n    fn add(self, _rhs: f64) -> Point {\n        Point {\n            x: self.x + _rhs,\n            y: self.y + _rhs,\n        }\n    }\n}\n\n/// Implements the '-' operator for Point - Point\nimpl Sub for Point {\n    type Output = Point;\n\n    fn sub(self, _rhs: Point) -> Point {\n        Point {\n            x: self.x - _rhs.x,\n            y: self.y - _rhs.y,\n        }\n    }\n}\n\n/// Implements the '-' operator for Point - f64\nimpl Sub<f64> for Point {\n    type Output = Point;\n\n    fn sub(self, _rhs: f64) -> Point {\n        Point {\n            x: self.x - _rhs,\n            y: self.y - _rhs,\n        }\n    }\n}\n\n/// Implements the '*' operator for Point * Point\nimpl Mul for Point {\n    type Output = Point;\n\n    fn mul(self, _rhs: Point) -> Point {\n        Point {\n            x: self.x * _rhs.x,\n            y: self.y * _rhs.y,\n        }\n    }\n}\n\n/// Implements the '*' operator for Point * f64\nimpl Mul<f64> for Point {\n    type Output = Point;\n\n    fn mul(self, _rhs: f64) -> Point {\n        Point {\n            x: self.x * _rhs,\n            y: self.x * _rhs,\n        }\n    }\n}\n\n/// Implements the '/' operator for Point / Point\nimpl Div for Point {\n    type Output = Point;\n\n    fn div(self, _rhs: Point) -> Point {\n        assert!(_rhs.x != 0f64);\n        assert!(_rhs.y != 0f64);\n        Point {\n            x: self.x / _rhs.x,\n            y: self.y / _rhs.y,\n        }\n    }\n}\n\n/// Implements the '/' operator for Point / f64:\nimpl Div<f64> for Point {\n    type Output = Point;\n\n    fn div(self, _rhs: f64) -> Point {\n        assert!(_rhs != 0f64);\n        Point {\n            x: self.x / _rhs,\n            y: self.y / _rhs,\n        }\n    }\n}\n"
  },
  {
    "path": "src/geometry/size.rs",
    "content": "use rand::Rng;\n\nuse super::Point;\n\n/// A `Size` represents a region in space\n#[derive(Clone, Copy, Default)]\npub struct Size {\n    pub width: f64,\n    pub height: f64\n}\n\nimpl Size {\n    /// Returns a new `Size` of the given dimensions\n    pub fn new(width: f64, height: f64) -> Size {\n        Size { width: width, height: height }\n    }\n\n    /// Returns true if the `Point` is contained in this `Size` or false otherwise\n    pub fn contains(&self, point: Point) -> bool {\n        0.0 <= point.x && point.x <= self.width\n            && 0.0 <= point.y && point.y <= self.height\n    }\n\n    /// Returns a random x coordinate within the bounds of this `Size`\n    pub fn random_x<R: Rng>(&self, rng: &mut R) -> f64 {\n        rng.gen_range(0.0, self.width)\n    }\n\n    /// Returns a random y coordinate within the bounds of this `Size`\n    pub fn random_y<R: Rng>(&self, rng: &mut R) -> f64 {\n        rng.gen_range(0.0, self.height)\n    }\n}\n"
  },
  {
    "path": "src/geometry/traits.rs",
    "content": "//! Traits used by the models\n\nuse std::f64;\n\nuse super::{Point, Size};\n\n/// A trait for objects that occupy a position in space\npub trait Position {\n    /// Returns the x coordinate of the object\n    fn x(&self) -> f64;\n\n    /// Returns a mutable reference to the x coordinate\n    fn x_mut(&mut self) -> &mut f64;\n\n    /// Returns the y coordinate of the object\n    fn y(&self) -> f64;\n\n    /// Returns a mutable reference to the y coordinate\n    fn y_mut(&mut self) -> &mut f64;\n\n    /// Returns the position of the object\n    fn position(&self) -> Point {\n        Point::new(self.x(), self.y())\n    }\n}\n\n/// A trait for objects that have can move in a given direction\npub trait Advance: Position {\n    /// Returns the direction of the object, measured in radians\n    ///\n    /// Note: 0.0 points to the right and a positive number means a clockwise\n    /// rotation\n    fn direction(&self) -> f64;\n\n    /// Returns a mutable reference to the direction of the object\n    fn direction_mut(&mut self) -> &mut f64;\n\n    /// Changes the direction of the vector to point to the given target\n    fn point_to(&mut self, target: Point) {\n        let m = (self.y() - target.y) / (self.x() - target.x);\n\n        *self.direction_mut() = if target.x > self.x() {\n            m.atan()\n        } else {\n            m.atan() + f64::consts::PI\n        };\n    }\n\n    /// Advances the object in the given amount of units, according to its direction\n    fn advance(&mut self, units: f64) {\n        *self.x_mut() += self.direction().cos() * units;\n        *self.y_mut() += self.direction().sin() * units;\n    }\n\n    /// Similar to `Advance::advance`, but the final position will be wrapped\n    /// around the given bounds\n    fn advance_wrapping(&mut self, units: f64, bounds: Size) {\n        self.advance(units);\n\n        fn wrap(k: &mut f64, bound: f64) {\n            if *k < 0.0 {\n                *k += bound;\n            } else if *k >= bound {\n                *k -= bound;\n            }\n        }\n\n        wrap(self.x_mut(), bounds.width);\n        wrap(self.y_mut(), bounds.height);\n    }\n}\n\n/// A trait that provides collision detection for objects with a position and a radius\n///\n/// For collision purposes, all objects are treated as circles\npub trait Collide: Position {\n    /// Returns the radius of the object\n    fn radius(&self) -> f64;\n\n    /// Returns the diameter of the objects\n    fn diameter(&self) -> f64 {\n        self.radius() * 2.0\n    }\n\n    /// Returns true if the two objects collide and false otherwise\n    fn collides_with<O: Collide>(&self, other: &O) -> bool {\n        let radii = self.radius() + other.radius();\n        self.position().squared_distance_to(&other.position()) < radii * radii\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "extern crate itertools_num;\n#[macro_use]\nextern crate lazy_static;\nextern crate rand;\nextern crate pcg_rand;\n\nmod controllers;\nmod game_state;\nmod geometry;\nmod models;\nmod util;\n\nuse std::os::raw::{c_double, c_int};\nuse std::sync::Mutex;\n\nuse pcg_rand::Pcg32Basic;\nuse rand::SeedableRng;\n\nuse self::game_state::GameState;\nuse self::geometry::Size;\nuse self::controllers::{Actions, TimeController, CollisionsController};\n\nlazy_static! {\n    static ref DATA: Mutex<GameData> = Mutex::new(new_game_data(1024.0, 600.0));\n}\n\nstruct GameData {\n    state: GameState,\n    actions: Actions,\n    time_controller: TimeController<Pcg32Basic>\n}\n\nfn new_game_data(width: f64, height: f64) -> GameData {\n    GameData {\n        state: GameState::new(Size::new(width, height)),\n        actions: Actions::default(),\n        time_controller: TimeController::new(Pcg32Basic::from_seed([42, 42]))\n    }\n}\n\n// These functions are provided by the runtime\nextern \"C\" {\n    fn clear_screen();\n    fn draw_player(_: c_double, _: c_double, _: c_double);\n    fn draw_enemy(_: c_double, _: c_double);\n    fn draw_bullet(_: c_double, _: c_double);\n    fn draw_particle(_: c_double, _: c_double, _: c_double);\n    fn draw_score(_: c_double);\n}\n\n#[no_mangle]\npub extern \"C\" fn resize(width: c_double, height: c_double) {\n    *DATA.lock().unwrap() = new_game_data(width, height);\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn draw() {\n    use geometry::{Advance, Position};\n    let data = &mut DATA.lock().unwrap();\n    let world = &data.state.world;\n\n    clear_screen();\n    for particle in &world.particles {\n        draw_particle(particle.x(), particle.y(), 5.0 * particle.ttl);\n    }\n\n    for bullet in &world.bullets {\n        draw_bullet(bullet.x(), bullet.y());\n    }\n\n    for enemy in &world.enemies {\n        draw_enemy(enemy.x(), enemy.y());\n    }\n\n    draw_player(world.player.x(), world.player.y(), world.player.direction());\n    draw_score(data.state.score as f64);\n}\n\n#[no_mangle]\npub extern \"C\" fn update(time: c_double) {\n    let data: &mut GameData = &mut DATA.lock().unwrap();\n    data.time_controller.update_seconds(time, &data.actions, &mut data.state);\n    CollisionsController::handle_collisions(&mut data.state);\n}\n\nfn int_to_bool(i: c_int) -> bool {\n    i != 0\n}\n\n#[no_mangle]\npub extern \"C\" fn toggle_shoot(b: c_int) {\n    let data = &mut DATA.lock().unwrap();\n    data.actions.shoot = int_to_bool(b);\n}\n\n#[no_mangle]\npub extern \"C\" fn toggle_boost(b: c_int) {\n    let data = &mut DATA.lock().unwrap();\n    data.actions.boost = int_to_bool(b);\n}\n\n#[no_mangle]\npub extern \"C\" fn toggle_turn_left(b: c_int) {\n    let data = &mut DATA.lock().unwrap();\n    data.actions.rotate_left = int_to_bool(b);\n}\n\n#[no_mangle]\npub extern \"C\" fn toggle_turn_right(b: c_int) {\n    let data = &mut DATA.lock().unwrap();\n    data.actions.rotate_right = int_to_bool(b);\n}\n"
  },
  {
    "path": "src/models/bullet.rs",
    "content": "use super::Vector;\nuse geometry::{Advance, Collide};\n\n/// Bullets are spawned when the player shoots\n///\n/// When an enemy is reached by a bullet, it will explode\npub struct Bullet {\n    vector: Vector\n}\n\nderive_position_direction!(Bullet);\n\nimpl Bullet {\n    /// Create a bullet with the given vector\n    pub fn new(vector: Vector) -> Bullet {\n        Bullet { vector: vector }\n    }\n\n    /// Update the bullet's position\n    pub fn update(&mut self, units: f64) {\n        self.advance(units);\n    }\n}\n\nimpl Collide for Bullet {\n    fn radius(&self) -> f64 { 3.0 }\n}\n"
  },
  {
    "path": "src/models/enemy.rs",
    "content": "use geometry::Point;\nuse super::Vector;\nuse geometry::{Advance, Collide};\n\n/// Enemies follow the player in order to cause a collision and let him explode\npub struct Enemy {\n    vector: Vector\n}\n\nderive_position_direction!(Enemy);\n\nimpl Enemy {\n    /// Create a enemy with the given vector\n    pub fn new(vector: Vector) -> Enemy {\n        Enemy { vector: vector }\n    }\n\n    /// Update the enemy\n    pub fn update(&mut self, speed: f64, player_position: Point) {\n        // Point to the player\n        self.point_to(player_position);\n        self.advance(speed);\n    }\n}\n\nimpl Collide for Enemy {\n    fn radius(&self) -> f64 { 10.0 }\n}\n"
  },
  {
    "path": "src/models/mod.rs",
    "content": "// macro_use needs to go first so the macro is visible for the other modules\n#[macro_use]\nmod vector;\n\nmod bullet;\nmod enemy;\nmod particle;\nmod player;\nmod world;\n\npub use self::bullet::Bullet;\npub use self::enemy::Enemy;\npub use self::particle::Particle;\npub use self::player::{Player, POLYGON as PLAYER_POLYGON};\npub use self::vector::Vector;\npub use self::world::World;\n"
  },
  {
    "path": "src/models/particle.rs",
    "content": "use super::Vector;\nuse geometry::Advance;\n\n/// A model representing a particle\n///\n/// Particles are visible objects that have a time to live and move around\n/// in a given direction until their time is up. They are spawned when the\n/// player or an enemy is killed\npub struct Particle {\n    pub vector: Vector,\n    pub ttl: f64\n}\n\nderive_position_direction!(Particle);\n\nimpl Particle {\n    /// Create a particle with the given vector and time to live in seconds\n    pub fn new(vector: Vector, ttl: f64) -> Particle {\n        Particle { vector: vector, ttl: ttl }\n    }\n\n    /// Update the particle\n    pub fn update(&mut self, elapsed_time: f64) {\n        self.ttl -= elapsed_time;\n        let speed = 500.0 * self.ttl * self.ttl;\n        self.advance(elapsed_time * speed);\n    }\n}\n"
  },
  {
    "path": "src/models/player.rs",
    "content": "use rand::Rng;\n\nuse geometry::{Point, Size};\nuse super::Vector;\nuse geometry::{Advance, Collide, Position};\n\n/// The `Player` is the rocket controlled by the user\n#[derive(Default)]\npub struct Player {\n    pub vector: Vector\n}\n\nderive_position_direction!(Player);\n\n/// The player is represented as the polygon below\npub const POLYGON: &'static [[f64; 2]] = &[\n    [0.0, -8.0],\n    [20.0, 0.0],\n    [0.0, 8.0]\n];\n\nimpl Player {\n    /// Create a new `Player` with a random position and direction\n    pub fn random<R: Rng>(rng: &mut R, bounds: Size) -> Player {\n        Player { vector: Vector::random(rng, bounds) }\n    }\n\n    /// Returns the front of the rocket\n    pub fn front(&self) -> Point {\n        Point::new(POLYGON[1][0], POLYGON[1][1])\n            .rotate(self.direction())\n            .translate(&self.position())\n    }\n}\n\nimpl Collide for Player {\n    fn radius(&self) -> f64 { 6.0 }\n}\n"
  },
  {
    "path": "src/models/vector.rs",
    "content": "use std::f64;\n\nuse rand::Rng;\n\nuse geometry::{Point, Size};\n\n/// A `Vector`\n#[derive(Clone, Default)]\npub struct Vector {\n    /// The position of the vector\n    pub position: Point,\n    /// The direction angle, in radians\n    pub direction: f64\n}\n\nimpl Vector {\n    /// Returns a new `Vector`\n    pub fn new(position: Point, direction: f64) -> Vector {\n        Vector { position: position, direction: direction }\n    }\n\n    /// Returns a random `Vector` within the given bounds\n    pub fn random<R: Rng>(rng: &mut R, bounds: Size) -> Vector {\n        Vector::new(Point::random(rng, bounds), rng.gen())\n    }\n\n    /// Consumes the vector and returns a new one with inverted direction\n    pub fn invert(mut self) -> Vector {\n        self.direction -= f64::consts::PI;\n        self\n    }\n}\n\n/// A macro to implement `Position` and `Direction` for any type that has a field named `vector`\n#[macro_export]\nmacro_rules! derive_position_direction {\n    ($t:ty) => {\n        impl ::geometry::Position for $t {\n            fn x(&self) -> f64 { self.vector.position.x }\n            fn x_mut(&mut self) -> &mut f64 { &mut self.vector.position.x }\n            fn y(&self) -> f64 { self.vector.position.y }\n            fn y_mut(&mut self) -> &mut f64 { &mut self.vector.position.y }\n        }\n\n        impl ::geometry::Advance for $t {\n            fn direction(&self) -> f64 {\n                self.vector.direction\n            }\n\n            fn direction_mut(&mut self) -> &mut f64 {\n                &mut self.vector.direction\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/models/world.rs",
    "content": "use rand::Rng;\n\nuse geometry::Size;\nuse models::{Bullet, Enemy, Particle, Player};\n\n/// A model that contains the other models and renders them\npub struct World {\n    pub player: Player,\n    pub particles: Vec<Particle>,\n    pub bullets: Vec<Bullet>,\n    pub enemies: Vec<Enemy>,\n    pub size: Size\n}\n\nimpl World {\n    /// Returns a new world of the given size\n    pub fn new<R: Rng>(rng: &mut R, size: Size) -> World {\n        World {\n            player: Player::random(rng, size),\n            particles: Vec::with_capacity(1000),\n            bullets: vec![],\n            enemies: vec![],\n            size: size\n        }\n    }\n}\n"
  },
  {
    "path": "src/util.rs",
    "content": "use geometry::Point;\nuse models::{Particle, Vector};\n\n/// Optimized version of `Vec::retain`\n///\n/// We achieve better performance by renouncing to keep the original order of the `Vec`\npub fn fast_retain<T, F>(vec: &mut Vec<T>, mut f: F)\nwhere F: FnMut(&T) -> bool {\n    let mut i = 0;\n    while i < vec.len() {\n        if !f(&vec[i]) {\n            vec.swap_remove(i);\n        }\n\n        i += 1;\n    }\n}\n\n/// Generates a new explosion of the given intensity at the given position.\n/// This works best with values between 5 and 25\npub fn make_explosion(particles: &mut Vec<Particle>, position: &Point, intensity: u8) {\n    use itertools_num;\n    for rotation in itertools_num::linspace(0.0, 2.0 * ::std::f64::consts::PI, 30) {\n        for ttl in (1..intensity).map(|x| (x as f64) / 10.0) {\n            particles.push(Particle::new(Vector::new(position.clone(), rotation), ttl));\n        }\n    }\n}\n"
  }
]