game working
This commit is contained in:
commit
b6e9ebd18d
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
|
bin/
|
||||||
|
pkg/
|
||||||
|
wasm-pack.log
|
45
Cargo.toml
Normal file
45
Cargo.toml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
[package]
|
||||||
|
name = "game-of-life"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["aj <andrewjpack@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
repository = "https://github.com/Sarsoo/game-of-life"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["console_error_panic_hook"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wasm-bindgen = "0.2.63"
|
||||||
|
|
||||||
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
|
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||||
|
# code size when deploying.
|
||||||
|
console_error_panic_hook = { version = "0.1.6", optional = true }
|
||||||
|
|
||||||
|
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||||
|
# compared to the default allocator's ~10K. It is slower than the default
|
||||||
|
# allocator, however.
|
||||||
|
#
|
||||||
|
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
|
||||||
|
wee_alloc = { version = "0.4.5", optional = true }
|
||||||
|
|
||||||
|
[dependencies.web-sys]
|
||||||
|
version = "0.3.45"
|
||||||
|
features = [
|
||||||
|
"console",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wasm-bindgen-test = "0.3.13"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
# Tell `rustc` to optimize for small code size.
|
||||||
|
opt-level = "s"
|
||||||
|
|
||||||
|
[package.metadata.wasm-pack.profile.release]
|
||||||
|
wasm-opt = false
|
||||||
|
|
4
README.md
Normal file
4
README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Game of Life
|
||||||
|
============
|
||||||
|
|
||||||
|
WASM-based game of life following the [Rust WASM book](https://rustwasm.github.io/docs/book/introduction.html) tutorial.
|
155
src/lib.rs
Normal file
155
src/lib.rs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
mod utils;
|
||||||
|
|
||||||
|
// use std::cmp::Ordering;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||||
|
// allocator.
|
||||||
|
#[cfg(feature = "wee_alloc")]
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
|
||||||
|
macro_rules! log {
|
||||||
|
( $( $t:tt )* ) => {
|
||||||
|
web_sys::console::log_1(&format!( $( $t )* ).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Cell {
|
||||||
|
Dead = 0,
|
||||||
|
Alive = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Universe {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
cells: Vec<Cell>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Universe {
|
||||||
|
fn get_index(&self, row: u32, column: u32) -> usize {
|
||||||
|
(row * self.width + column) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
|
||||||
|
let mut count = 0;
|
||||||
|
for delta_row in [self.height - 1, 0, 1].iter().cloned() {
|
||||||
|
for delta_col in [self.width - 1, 0, 1].iter().cloned() {
|
||||||
|
if delta_row == 0 && delta_col == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let neighbor_row = (row + delta_row) % self.height;
|
||||||
|
let neighbor_col = (column + delta_col) % self.width;
|
||||||
|
let idx = self.get_index(neighbor_row, neighbor_col);
|
||||||
|
count += self.cells[idx] as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
fn populate_cell(i: u32) -> Cell {
|
||||||
|
if i % 2 == 0 || i % 7 == 0 {
|
||||||
|
Cell::Alive
|
||||||
|
} else {
|
||||||
|
Cell::Dead
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Universe {
|
||||||
|
pub fn tick(&mut self) {
|
||||||
|
// log!("ticking");
|
||||||
|
let mut next = self.cells.clone();
|
||||||
|
|
||||||
|
for row in 0..self.height {
|
||||||
|
for col in 0..self.width {
|
||||||
|
let idx = self.get_index(row, col);
|
||||||
|
let cell = self.cells[idx];
|
||||||
|
let live_neighbors = self.live_neighbor_count(row, col);
|
||||||
|
|
||||||
|
let next_cell = match (cell, live_neighbors) {
|
||||||
|
// Rule 1: Any live cell with fewer than two live neighbours
|
||||||
|
// dies, as if caused by underpopulation.
|
||||||
|
(Cell::Alive, x) if x < 2 => Cell::Dead,
|
||||||
|
// Rule 2: Any live cell with two or three live neighbours
|
||||||
|
// lives on to the next generation.
|
||||||
|
(Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
|
||||||
|
// Rule 3: Any live cell with more than three live
|
||||||
|
// neighbours dies, as if by overpopulation.
|
||||||
|
(Cell::Alive, x) if x > 3 => Cell::Dead,
|
||||||
|
// Rule 4: Any dead cell with exactly three live neighbours
|
||||||
|
// becomes a live cell, as if by reproduction.
|
||||||
|
(Cell::Dead, 3) => Cell::Alive,
|
||||||
|
// All other cells remain in the same state.
|
||||||
|
(otherwise, _) => otherwise,
|
||||||
|
};
|
||||||
|
|
||||||
|
next[idx] = next_cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cells = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(width: u32, height: u32) -> Universe {
|
||||||
|
log!("Generating new board {}x{}", width, height);
|
||||||
|
|
||||||
|
let cells = (0..width * height)
|
||||||
|
.map(|i| {
|
||||||
|
Universe::populate_cell(i)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Universe {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
cells,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&self) -> String {
|
||||||
|
self.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> u32 {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> u32 {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cells(&self) -> *const Cell {
|
||||||
|
self.cells.as_ptr()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.cells = (0..self.width * self.height)
|
||||||
|
.map(|i| {
|
||||||
|
Universe::populate_cell(i)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Universe {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
for line in self.cells.as_slice().chunks(self.width as usize) {
|
||||||
|
for &cell in line {
|
||||||
|
let symbol = if cell == Cell::Dead { '◻' } else { '◼' };
|
||||||
|
write!(f, "{}", symbol)?;
|
||||||
|
}
|
||||||
|
write!(f, "\n")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
10
src/utils.rs
Normal file
10
src/utils.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
pub fn set_panic_hook() {
|
||||||
|
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||||
|
// `set_panic_hook` function at least once during initialization, and then
|
||||||
|
// we will get better error messages if our code ever panics.
|
||||||
|
//
|
||||||
|
// For more details see
|
||||||
|
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||||
|
#[cfg(feature = "console_error_panic_hook")]
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
}
|
13
tests/web.rs
Normal file
13
tests/web.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//! Test suite for the Web and headless browsers.
|
||||||
|
|
||||||
|
#![cfg(target_arch = "wasm32")]
|
||||||
|
|
||||||
|
extern crate wasm_bindgen_test;
|
||||||
|
use wasm_bindgen_test::*;
|
||||||
|
|
||||||
|
wasm_bindgen_test_configure!(run_in_browser);
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn pass() {
|
||||||
|
assert_eq!(1 + 1, 2);
|
||||||
|
}
|
2
www/.gitignore
vendored
Normal file
2
www/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
67
www/README.md
Normal file
67
www/README.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|
<h1><code>create-wasm-app</code></h1>
|
||||||
|
|
||||||
|
<strong>An <code>npm init</code> template for kick starting a project that uses NPM packages containing Rust-generated WebAssembly and bundles them with Webpack.</strong>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://travis-ci.org/rustwasm/create-wasm-app"><img src="https://img.shields.io/travis/rustwasm/create-wasm-app.svg?style=flat-square" alt="Build Status" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<a href="#usage">Usage</a>
|
||||||
|
<span> | </span>
|
||||||
|
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
This template is designed for depending on NPM packages that contain
|
||||||
|
Rust-generated WebAssembly and using them to create a Website.
|
||||||
|
|
||||||
|
* Want to create an NPM package with Rust and WebAssembly? [Check out
|
||||||
|
`wasm-pack-template`.](https://github.com/rustwasm/wasm-pack-template)
|
||||||
|
* Want to make a monorepo-style Website without publishing to NPM? Check out
|
||||||
|
[`rust-webpack-template`](https://github.com/rustwasm/rust-webpack-template)
|
||||||
|
and/or
|
||||||
|
[`rust-parcel-template`](https://github.com/rustwasm/rust-parcel-template).
|
||||||
|
|
||||||
|
## 🚴 Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
npm init wasm-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔋 Batteries Included
|
||||||
|
|
||||||
|
- `.gitignore`: ignores `node_modules`
|
||||||
|
- `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you
|
||||||
|
- `README.md`: the file you are reading now!
|
||||||
|
- `index.html`: a bare bones html document that includes the webpack bundle
|
||||||
|
- `index.js`: example js file with a comment showing how to import and use a wasm pkg
|
||||||
|
- `package.json` and `package-lock.json`:
|
||||||
|
- pulls in devDependencies for using webpack:
|
||||||
|
- [`webpack`](https://www.npmjs.com/package/webpack)
|
||||||
|
- [`webpack-cli`](https://www.npmjs.com/package/webpack-cli)
|
||||||
|
- [`webpack-dev-server`](https://www.npmjs.com/package/webpack-dev-server)
|
||||||
|
- defines a `start` script to run `webpack-dev-server`
|
||||||
|
- `webpack.config.js`: configuration file for bundling your js with webpack
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under either of
|
||||||
|
|
||||||
|
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
at your option.
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally
|
||||||
|
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
||||||
|
license, shall be dual licensed as above, without any additional terms or
|
||||||
|
conditions.
|
5
www/bootstrap.js
vendored
Normal file
5
www/bootstrap.js
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// A dependency graph that contains any wasm must all be imported
|
||||||
|
// asynchronously. This `bootstrap.js` file does the single async import, so
|
||||||
|
// that no one else needs to worry about it again.
|
||||||
|
import("./index.js")
|
||||||
|
.catch(e => console.error("Error importing `index.js`:", e));
|
25
www/index.html
Normal file
25
www/index.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>game of life</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- <pre id="game-of-life-canvas"></pre> -->
|
||||||
|
<canvas id="game-of-life-canvas"></canvas>
|
||||||
|
<script src="./bootstrap.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
87
www/index.js
Normal file
87
www/index.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { Universe, Cell } from "game-of-life";
|
||||||
|
import { memory } from "game-of-life/game_of_life_bg";
|
||||||
|
|
||||||
|
let PLAY = true;
|
||||||
|
// let PLAY = false;
|
||||||
|
|
||||||
|
const CELL_SIZE = 5; // px
|
||||||
|
const GRID_COLOR = "#BBBBBB";
|
||||||
|
const DEAD_COLOR = "#FFFFFF";
|
||||||
|
const ALIVE_COLOR = "#FF55AA";
|
||||||
|
|
||||||
|
const universe = Universe.new(200, 109);
|
||||||
|
const width = universe.width();
|
||||||
|
const height = universe.height();
|
||||||
|
// console.log(universe);
|
||||||
|
|
||||||
|
const canvas = document.getElementById("game-of-life-canvas");
|
||||||
|
canvas.height = (CELL_SIZE + 1) * height + 1;
|
||||||
|
canvas.width = (CELL_SIZE + 1) * width + 1;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
const drawGrid = () => {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.strokeStyle = GRID_COLOR;
|
||||||
|
|
||||||
|
// Vertical lines.
|
||||||
|
for (let i = 0; i <= width; i++) {
|
||||||
|
ctx.moveTo(i * (CELL_SIZE + 1) + 1, 0);
|
||||||
|
ctx.lineTo(i * (CELL_SIZE + 1) + 1, (CELL_SIZE + 1) * height + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal lines.
|
||||||
|
for (let j = 0; j <= height; j++) {
|
||||||
|
ctx.moveTo(0, j * (CELL_SIZE + 1) + 1);
|
||||||
|
ctx.lineTo((CELL_SIZE + 1) * width + 1, j * (CELL_SIZE + 1) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIndex = (row, column) => {
|
||||||
|
return row * width + column;
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawCells = () => {
|
||||||
|
const cellsPtr = universe.cells();
|
||||||
|
const cells = new Uint8Array(memory.buffer, cellsPtr, width * height);
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
for (let row = 0; row < height; row++) {
|
||||||
|
for (let col = 0; col < width; col++) {
|
||||||
|
const idx = getIndex(row, col);
|
||||||
|
|
||||||
|
ctx.fillStyle = cells[idx] === Cell.Dead
|
||||||
|
? DEAD_COLOR
|
||||||
|
: ALIVE_COLOR;
|
||||||
|
|
||||||
|
ctx.fillRect(
|
||||||
|
col * (CELL_SIZE + 1) + 1,
|
||||||
|
row * (CELL_SIZE + 1) + 1,
|
||||||
|
CELL_SIZE,
|
||||||
|
CELL_SIZE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSingle = () => {
|
||||||
|
universe.tick();
|
||||||
|
|
||||||
|
drawGrid();
|
||||||
|
drawCells();
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderLoop = () => {
|
||||||
|
if(PLAY){
|
||||||
|
renderSingle();
|
||||||
|
requestAnimationFrame(renderLoop);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderSingle();
|
||||||
|
requestAnimationFrame(renderLoop);
|
5328
www/package-lock.json
generated
Normal file
5328
www/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
www/package.json
Normal file
34
www/package.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "game-of-life-web",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "basic wasm-based game-of-life",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --config webpack.config.js",
|
||||||
|
"start": "webpack-dev-server"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/Sarsoo/game-of-life.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"webassembly",
|
||||||
|
"wasm",
|
||||||
|
"rust",
|
||||||
|
"webpack"
|
||||||
|
],
|
||||||
|
"author": "sarsoo",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/Sarsoo/game-of-life/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Sarsoo/game-of-life#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"game-of-life": "file:../pkg"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"copy-webpack-plugin": "^5.1.2",
|
||||||
|
"webpack": "^4.44.2",
|
||||||
|
"webpack-cli": "^3.1.0",
|
||||||
|
"webpack-dev-server": "^3.1.5"
|
||||||
|
}
|
||||||
|
}
|
14
www/webpack.config.js
Normal file
14
www/webpack.config.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: "./bootstrap.js",
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, "dist"),
|
||||||
|
filename: "bootstrap.js",
|
||||||
|
},
|
||||||
|
mode: "development",
|
||||||
|
plugins: [
|
||||||
|
new CopyWebpackPlugin(['index.html'])
|
||||||
|
],
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user