Browse Source

basic framework added from roguelike

Inderjit Gill 9 months ago
parent
commit
5349a90981
26 changed files with 7591 additions and 1 deletions
  1. 9
    0
      .gitignore
  2. 127
    0
      Cargo.lock
  3. 23
    0
      Cargo.toml
  4. 49
    1
      README.md
  5. 13
    0
      benches/bench.rs
  6. 26
    0
      index.html
  7. 5802
    0
      package-lock.json
  8. 15
    0
      package.json
  9. BIN
      research/tileset.pdn
  10. BIN
      research/tileset.png
  11. 38
    0
      serve.go
  12. 71
    0
      src/controller.rs
  13. 36
    0
      src/entity.rs
  14. 87
    0
      src/game.rs
  15. 297
    0
      src/geometry.rs
  16. 127
    0
      src/lib.rs
  17. 26
    0
      src/sprite.rs
  18. 46
    0
      src/state.rs
  19. 103
    0
      src/text.rs
  20. 71
    0
      src/units.rs
  21. BIN
      web/img/tileset.png
  22. 246
    0
      web/js/GLRenderer.js
  23. 259
    0
      web/js/Matrix.js
  24. 4
    0
      web/js/bootstrap.js
  25. 103
    0
      web/js/index.js
  26. 13
    0
      webpack.config.js

+ 9
- 0
.gitignore View File

@@ -0,0 +1,9 @@
1
+/dist
2
+/target
3
+**/*.rs.bk
4
+
5
+/node_modules/
6
+/web/wasm_tetris*
7
+
8
+serve.exe
9
+serve

+ 127
- 0
Cargo.lock View File

@@ -0,0 +1,127 @@
1
+[[package]]
2
+name = "dtoa"
3
+version = "0.4.2"
4
+source = "registry+https://github.com/rust-lang/crates.io-index"
5
+
6
+[[package]]
7
+name = "itoa"
8
+version = "0.4.1"
9
+source = "registry+https://github.com/rust-lang/crates.io-index"
10
+
11
+[[package]]
12
+name = "proc-macro2"
13
+version = "0.4.6"
14
+source = "registry+https://github.com/rust-lang/crates.io-index"
15
+dependencies = [
16
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
17
+]
18
+
19
+[[package]]
20
+name = "quote"
21
+version = "0.6.3"
22
+source = "registry+https://github.com/rust-lang/crates.io-index"
23
+dependencies = [
24
+ "proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
25
+]
26
+
27
+[[package]]
28
+name = "serde"
29
+version = "1.0.69"
30
+source = "registry+https://github.com/rust-lang/crates.io-index"
31
+
32
+[[package]]
33
+name = "serde_derive"
34
+version = "1.0.69"
35
+source = "registry+https://github.com/rust-lang/crates.io-index"
36
+dependencies = [
37
+ "proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
38
+ "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
39
+ "syn 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)",
40
+]
41
+
42
+[[package]]
43
+name = "serde_json"
44
+version = "1.0.22"
45
+source = "registry+https://github.com/rust-lang/crates.io-index"
46
+dependencies = [
47
+ "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
48
+ "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
49
+ "serde 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)",
50
+]
51
+
52
+[[package]]
53
+name = "syn"
54
+version = "0.14.4"
55
+source = "registry+https://github.com/rust-lang/crates.io-index"
56
+dependencies = [
57
+ "proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
58
+ "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
59
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
60
+]
61
+
62
+[[package]]
63
+name = "unicode-xid"
64
+version = "0.1.0"
65
+source = "registry+https://github.com/rust-lang/crates.io-index"
66
+
67
+[[package]]
68
+name = "wasm-bindgen"
69
+version = "0.2.11"
70
+source = "registry+https://github.com/rust-lang/crates.io-index"
71
+dependencies = [
72
+ "wasm-bindgen-macro 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
73
+]
74
+
75
+[[package]]
76
+name = "wasm-bindgen-backend"
77
+version = "0.2.11"
78
+source = "registry+https://github.com/rust-lang/crates.io-index"
79
+dependencies = [
80
+ "proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
81
+ "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
82
+ "serde_json 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)",
83
+ "syn 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)",
84
+ "wasm-bindgen-shared 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
85
+]
86
+
87
+[[package]]
88
+name = "wasm-bindgen-macro"
89
+version = "0.2.11"
90
+source = "registry+https://github.com/rust-lang/crates.io-index"
91
+dependencies = [
92
+ "proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
93
+ "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
94
+ "syn 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)",
95
+ "wasm-bindgen-backend 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
96
+]
97
+
98
+[[package]]
99
+name = "wasm-bindgen-shared"
100
+version = "0.2.11"
101
+source = "registry+https://github.com/rust-lang/crates.io-index"
102
+dependencies = [
103
+ "serde 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)",
104
+ "serde_derive 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)",
105
+]
106
+
107
+[[package]]
108
+name = "wasm_tetris"
109
+version = "0.1.0"
110
+dependencies = [
111
+ "wasm-bindgen 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
112
+]
113
+
114
+[metadata]
115
+"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
116
+"checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682"
117
+"checksum proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "effdb53b25cdad54f8f48843d67398f7ef2e14f12c1b4cb4effc549a6462a4d6"
118
+"checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035"
119
+"checksum serde 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)" = "210e5a3b159c566d7527e9b22e44be73f2e0fcc330bb78fef4dbccb56d2e74c8"
120
+"checksum serde_derive 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)" = "dd724d68017ae3a7e63600ee4b2fdb3cad2158ffd1821d44aff4580f63e2b593"
121
+"checksum serde_json 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)" = "84b8035cabe9b35878adec8ac5fe03d5f6bc97ff6edd7ccb96b44c1276ba390e"
122
+"checksum syn 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2beff8ebc3658f07512a413866875adddd20f4fd47b2a4e6c9da65cd281baaea"
123
+"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
124
+"checksum wasm-bindgen 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e38789b21dd56c9b172efb263d635ba5203d4f6c65a7010a26fba1aaa8c55bda"
125
+"checksum wasm-bindgen-backend 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3360d814e6dfc2d1de99d20e224310cb2a640e5749133d7e7e6be2f8d401378a"
126
+"checksum wasm-bindgen-macro 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "00cae1d10619d295b817c40cb5bb2fa7969d5abf39c74ceac4dd7e8b285376f0"
127
+"checksum wasm-bindgen-shared 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "12fc3992a9356fb2eec8133e6336996cea864ebfe21660793ecd4a9d522b2985"

+ 23
- 0
Cargo.toml View File

@@ -0,0 +1,23 @@
1
+[package]
2
+name = "wasm_tetris"
3
+version = "0.1.0"
4
+authors = ["Inderjit Gill <inderjit.gill@gmail.com>"]
5
+
6
+[lib]
7
+crate-type = ["cdylib"]
8
+
9
+[dependencies]
10
+wasm-bindgen = "0.2.8"
11
+
12
+[profile.release]
13
+# Include function names in the `.wasm` for better debugging and
14
+# profiling. Comment this out if you're trying to create the smallest `.wasm`
15
+# binaries you can.
16
+debug = false
17
+
18
+# # Uncomment these lines to create smaller `.wasm` binaries, at the cost of
19
+# # longer compile times.
20
+codegen-units = 1
21
+incremental = false
22
+lto = true
23
+opt-level = "z"

+ 49
- 1
README.md View File

@@ -1,2 +1,50 @@
1
-# tetris
1
+Tetris
2
+======
2 3
 
4
+## Overview
5
+
6
+A Game
7
+
8
+## ensure that the correct toolchain is installed
9
+```sh
10
+$ rustup update
11
+$ rustup install nightly
12
+$ rustup target add wasm32-unknown-unknown --toolchain nightly
13
+$ cargo +nightly install wasm-bindgen-cli
14
+
15
+$ npm install
16
+$ npm run build:server
17
+```
18
+
19
+## during dev
20
+```sh
21
+$ npm run build:dev:wasm
22
+$ npm run build:dev
23
+$ npm run serve:dev
24
+```
25
+
26
+can now visit http://localhost:8080
27
+
28
+
29
+## for 'release'
30
+```sh
31
+$ npm run build:release
32
+$ npm run serve:release
33
+```
34
+
35
+can now visit http://localhost:3000
36
+
37
+
38
+# misc. notes
39
+
40
+## graphics
41
+  - origin is in the bottom left
42
+  - rendering order for a square is: bl, br, tl, tr
43
+
44
+## file structure
45
+  /dist -> destination directory for webpack output (npm run build)
46
+  index.html
47
+  /src -> rust source for top-level program + wasm declarations
48
+  /target -> destination directory for rust
49
+  /web -> destination for wasm-bindgen generated files (*.js, *.ts, *.wasm)
50
+  /web/js -> js files

+ 13
- 0
benches/bench.rs View File

@@ -0,0 +1,13 @@
1
+#![feature(test)]
2
+
3
+extern crate test;
4
+extern crate wasm_tetris;
5
+
6
+#[bench]
7
+fn universe_ticks(b: &mut test::Bencher) {
8
+    let mut universe = wasm_game_of_life::Universe::new();
9
+
10
+    b.iter(|| {
11
+        universe.tick();
12
+    });
13
+}

+ 26
- 0
index.html View File

@@ -0,0 +1,26 @@
1
+<html>
2
+  <head>
3
+    <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
4
+    <style>
5
+      body {
6
+      width: 100%;
7
+      height: 100%;
8
+      display: flex;
9
+      flex-direction: column;
10
+      align-items: center;
11
+      justify-content: center;
12
+      font-family: monospace;
13
+      white-space: pre;
14
+      background: #333333;
15
+      }
16
+      #render-canvas {
17
+        background: #101010;
18
+        padding: 1em;
19
+      }
20
+    </style>
21
+  </head>
22
+  <body>
23
+    <canvas id="render-canvas"></canvas>
24
+    <script src='bootstrap.js'></script>
25
+  </body>
26
+</html>

+ 5802
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 15
- 0
package.json View File

@@ -0,0 +1,15 @@
1
+{
2
+  "scripts": {
3
+    "serve:dev": "webpack-dev-server",
4
+    "serve:release": "serve",
5
+    "build:dev": "webpack --mode=development --progress",
6
+    "build:dev:wasm": "cargo +nightly build --target wasm32-unknown-unknown && wasm-bindgen target/wasm32-unknown-unknown/debug/wasm_tetris.wasm --out-dir web",
7
+    "build:release": "cargo +nightly build --release --target wasm32-unknown-unknown && wasm-bindgen target/wasm32-unknown-unknown/release/wasm_tetris.wasm --out-dir web && webpack --mode=production --progress && cp index.html dist/index.html",
8
+    "build:server": "go build serve.go"
9
+  },
10
+  "devDependencies": {
11
+    "webpack": "4.9.2",
12
+    "webpack-cli": "3.0.8",
13
+    "webpack-dev-server": "3.1.4"
14
+  }
15
+}

BIN
research/tileset.pdn View File


BIN
research/tileset.png View File


+ 38
- 0
serve.go View File

@@ -0,0 +1,38 @@
1
+package main
2
+
3
+import (
4
+	"fmt"
5
+	"net/http"
6
+	"regexp"
7
+)
8
+
9
+/*
10
+serve web file from: /web (for images used as textures)
11
+serve everything else from: /dist (generated with npm run build:prod)
12
+*/
13
+
14
+var wasmFile = regexp.MustCompile("\\.wasm$")
15
+
16
+func maxAgeHandler(seconds int, h http.Handler) http.Handler {
17
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18
+		w.Header().Add("Cache-Control", fmt.Sprintf("max-age=%d, public, must-revalidate, proxy-revalidate", seconds))
19
+
20
+		ruri := r.RequestURI
21
+		if wasmFile.MatchString(ruri) {
22
+			w.Header().Set("Content-Type", "application/wasm")
23
+		}
24
+
25
+		h.ServeHTTP(w, r)
26
+	})
27
+}
28
+
29
+func main() {
30
+	fs := http.FileServer(http.Dir("web"))
31
+	http.Handle("/web/", http.StripPrefix("/web/", fs))
32
+
33
+	fs = http.FileServer(http.Dir("dist"))
34
+	http.Handle("/", maxAgeHandler(0, fs))
35
+
36
+	fmt.Printf("Serving localhost:3000\n")
37
+	http.ListenAndServe(":3000", nil)
38
+}

+ 71
- 0
src/controller.rs View File

@@ -0,0 +1,71 @@
1
+
2
+#[derive(Clone, Copy)]
3
+pub enum ControllerAction {
4
+    Down,
5
+    Up,
6
+}
7
+
8
+#[derive(Clone, Copy)]
9
+pub enum ControllerButton {
10
+    Left,
11
+    Right,
12
+    Up,
13
+    Down,
14
+    A,
15
+    B,
16
+    Start,
17
+    Select,
18
+}
19
+
20
+// a virtual controller, used to normalise player input into a standard set of controls
21
+#[derive(Default)]
22
+pub struct Controller {
23
+    pub up: bool,
24
+    pub down: bool,
25
+    pub left: bool,
26
+    pub right: bool,
27
+
28
+    pub a: bool,
29
+    pub b: bool,
30
+
31
+    pub start: bool,
32
+    pub select: bool,
33
+}
34
+
35
+impl Controller {
36
+    pub fn new() -> Controller {
37
+        Controller {
38
+            up: false,
39
+            down: false,
40
+            left: false,
41
+            right: false,
42
+
43
+            a: false,
44
+            b: false,
45
+
46
+            start: false,
47
+            select: false,
48
+        }
49
+    }
50
+
51
+    pub fn input(&mut self, button: ControllerButton, action: ControllerAction) {
52
+        match button {
53
+            ControllerButton::Left => self.left = self.bool_from_action(action),
54
+            ControllerButton::Right => self.right = self.bool_from_action(action),
55
+            ControllerButton::Up => self.up = self.bool_from_action(action),
56
+            ControllerButton::Down => self.down = self.bool_from_action(action),
57
+            ControllerButton::A => self.a = self.bool_from_action(action),
58
+            ControllerButton::B => self.b = self.bool_from_action(action),
59
+            ControllerButton::Start => self.start = self.bool_from_action(action),
60
+            ControllerButton::Select => self.select = self.bool_from_action(action),
61
+        };
62
+    }
63
+
64
+    fn bool_from_action(&self, action: ControllerAction) -> bool {
65
+        match action {
66
+            ControllerAction::Up => false,
67
+            ControllerAction::Down => true,
68
+        }
69
+    }
70
+
71
+}

+ 36
- 0
src/entity.rs View File

@@ -0,0 +1,36 @@
1
+use units::*;
2
+use sprite::{Sprite};
3
+
4
+#[derive(Debug, Clone, Copy)]
5
+pub enum Colour {
6
+    Red,
7
+    Green,
8
+    Blue,
9
+    Yellow,
10
+    Orange,
11
+    Purple,
12
+    Brown,
13
+    Grey,
14
+}
15
+
16
+// player, creatures, walls?, treasure, objects etc
17
+#[derive(Debug, Clone, Copy)]
18
+pub struct Entity {
19
+    pub pos: Tile2D,
20
+
21
+    pub colour: Colour,
22
+    pub sprite: Sprite,
23
+
24
+    pub is_barrier: bool,
25
+}
26
+
27
+impl Entity {
28
+    pub fn new(x: i32, y: i32, colour: Colour, sprite: Sprite, is_barrier: bool) -> Entity {
29
+        Entity {
30
+            pos: Tile2D { x, y },
31
+            colour,
32
+            sprite,
33
+            is_barrier,
34
+        }
35
+    }
36
+}

+ 87
- 0
src/game.rs View File

@@ -0,0 +1,87 @@
1
+use log;
2
+use controller::{Controller, ControllerButton, ControllerAction};
3
+use state::State;
4
+use geometry::Geometry;
5
+
6
+pub enum GameMode {
7
+    Playing,
8
+    Editing,
9
+}
10
+
11
+pub struct Game {
12
+    delta: f32,
13
+
14
+    mode: GameMode,
15
+
16
+    controller: Controller,
17
+    state: State,
18
+    geometry: Geometry,
19
+}
20
+
21
+impl Game {
22
+    pub fn new(
23
+        canvas_width: f32,
24
+        canvas_height: f32,
25
+        view_frame_width: i32,       // view-frame width
26
+        view_frame_height: i32,      // view-frame height
27
+        tileset_texture_width: i32,  // tileset texture width
28
+        tileset_texture_height: i32, // tileset texture height
29
+    ) -> Game {
30
+        Game {
31
+            delta: 0.0,
32
+
33
+            mode: GameMode::Playing,
34
+
35
+            controller: Controller::new(),
36
+
37
+            state: State::new(view_frame_width, view_frame_height),
38
+
39
+            geometry: Geometry::new(canvas_width,
40
+                                    canvas_height,
41
+                                    view_frame_width,
42
+                                    view_frame_height,
43
+                                    tileset_texture_width,
44
+                                    tileset_texture_height),
45
+        }
46
+    }
47
+
48
+    pub fn init(&mut self) {
49
+        self.state.init();
50
+    }
51
+
52
+    pub fn input(&mut self, button: ControllerButton, action: ControllerAction) {
53
+        self.controller.input(button, action);
54
+    }
55
+
56
+    pub fn tick(&mut self, delta: f32) -> bool {
57
+        // always followed by a render
58
+        // returns true if tick should be called again
59
+        //
60
+        self.delta = delta;
61
+
62
+        match self.mode {
63
+            GameMode::Playing => self.state.tick_playing(&self.controller),
64
+            GameMode::Editing => tick_editing(),
65
+        }
66
+    }
67
+
68
+    // use the current gamestate to update geometry
69
+    pub fn update_geometry(&mut self) {
70
+        self.state.update_geometry(&mut self.geometry);
71
+        self.state.update_geometry_debug(&mut self.geometry);
72
+    }
73
+
74
+    pub fn geo_len(&self) -> usize {
75
+        self.geometry.geo.len()
76
+    }
77
+
78
+    pub fn geo_ptr(&self) -> *const f32 {
79
+        self.geometry.geo.as_ptr() as *const f32
80
+    }
81
+}
82
+
83
+fn tick_editing() -> bool {
84
+    log("IMPLEMENT: tick_editing");
85
+
86
+    false
87
+}

+ 297
- 0
src/geometry.rs View File

@@ -0,0 +1,297 @@
1
+use units::*;
2
+use entity::*;
3
+use sprite::{Sprite, get_sprite_location};
4
+use text::{sprite_location_from_char};
5
+
6
+struct UV {
7
+    u: f32,
8
+    v: f32,
9
+}
10
+
11
+pub struct Geometry {
12
+    pub tile_width: f32, // pixels
13
+    pub tile_height: f32,
14
+
15
+    pub sprite_u_unit: f32, // size of each sprite in u,v normalized co-ordinates
16
+    pub sprite_v_unit: f32,
17
+
18
+    pub geo: Vec<f32>,
19
+}
20
+
21
+impl Geometry {
22
+
23
+    pub fn new(
24
+        canvas_width: f32,
25
+        canvas_height: f32,
26
+        view_frame_width: i32,       // view-frame width
27
+        view_frame_height: i32,      // view-frame height
28
+        tileset_texture_width: i32,  // tileset texture width
29
+        tileset_texture_height: i32, // tileset texture height
30
+    ) -> Geometry {
31
+
32
+        // the size of each sprite in the tileset (in pixels)
33
+        let sprite_width: i32 = 16;
34
+        let sprite_height: i32 = 16;
35
+
36
+        Geometry {
37
+            tile_width: canvas_width / (view_frame_width as f32),
38
+            tile_height: canvas_height / (view_frame_height as f32),
39
+
40
+            sprite_u_unit: 1.0 / ((tileset_texture_width / sprite_width) as f32),
41
+            sprite_v_unit: 1.0 / ((tileset_texture_height / sprite_height) as f32),
42
+
43
+            geo: Vec::with_capacity(4096),
44
+        }
45
+    }
46
+
47
+    pub fn clear(&mut self) {
48
+        self.geo.clear();
49
+    }
50
+
51
+    pub fn push(&mut self, x: f32, y: f32, r: f32, g: f32, b: f32, a: f32, u: f32, v: f32) {
52
+        self.geo.append(&mut vec![x, y, r, g, b, a, u, v]);
53
+    }
54
+
55
+    // called once at the start of drawing a line
56
+    pub fn push_line(&mut self, p1: Vec2D, p2: Vec2D, thickness: f32, c: Col) {
57
+        // keep thickness relative to tile_width just like the other thickness parameters
58
+        //
59
+        let hw = (thickness * self.tile_width) / 2.0;       // half-width
60
+
61
+        let (t1, t2, t3, t4) = self.get_sprite_uv(Sprite::DebugFilled);
62
+
63
+        let n = normal(p1, p2);
64
+        let op = opposite_normal(n);
65
+
66
+        self.dup();
67
+        self.push(p1.x + (hw * n.x), p1.y + (hw * n.y), 0.0, 0.0, 0.0, 1.0, 0.0, 0.0);
68
+
69
+        self.push(p1.x + (hw * n.x), p1.y + (hw * n.y), c.r, c.g, c.b, c.a, t1.u, t1.v);
70
+        self.push(p1.x + (hw * op.x), p1.y + (hw * op.y), c.r, c.g, c.b, c.a, t2.u, t2.v);
71
+        self.push(p2.x + (hw * n.x), p2.y + (hw * n.y), c.r, c.g, c.b, c.a, t3.u, t3.v);
72
+        self.push(p2.x + (hw * op.x), p2.y + (hw * op.y), c.r, c.g, c.b, c.a, t4.u, t4.v);
73
+    }
74
+
75
+    // todo: push_path/push_lines
76
+
77
+
78
+    pub fn centre_point_from_tile(&self, tile: Tile2D) -> Vec2D {
79
+        let w = self.tile_width as f32;
80
+        let h = self.tile_height as f32;
81
+
82
+        let x = ((tile.x as f32) * w) + (w / 2.0);
83
+        let y = ((tile.y as f32) * h) + (h / 2.0);
84
+
85
+        Vec2D { x, y }
86
+    }
87
+
88
+    pub fn point_from_tile(&self, tile: Tile2D) -> Vec2D {
89
+        let w = self.tile_width as f32;
90
+        let h = self.tile_height as f32;
91
+
92
+        let x = (tile.x as f32) * w;
93
+        let y = (tile.y as f32) * h;
94
+
95
+        Vec2D { x, y }
96
+    }
97
+
98
+    // renders a rectangle over the TileFrame
99
+    // frame should be in screen co-ordinates
100
+    pub fn push_filled_tile(&mut self, tile: Tile2D, c: Col) {
101
+        let w = self.tile_width;
102
+        let h = self.tile_height;
103
+
104
+        let x1 = (tile.x as f32) * w;
105
+        let y1 = (tile.y as f32) * h;
106
+        let x2 = ((tile.x + 1) as f32) * w;
107
+        let y2 = ((tile.y + 1) as f32) * h;
108
+
109
+        self.push_filled(x1, y1, x2, y2, c.r, c.g, c.b, c.a);
110
+    }
111
+
112
+    pub fn push_outline_tile(&mut self, tile: Tile2D, thickness: f32, c: Col) {
113
+        // thickness of 1.0 == tile_width, tile_height
114
+        let w = self.tile_width;
115
+        let h = self.tile_height;
116
+
117
+        let x1 = (tile.x as f32) * w;
118
+        let y1 = (tile.y as f32) * h;
119
+        let x2 = ((tile.x + 1) as f32) * w;
120
+        let y2 = ((tile.y + 1) as f32) * h;
121
+
122
+        self.push_outline(x1, y1, x2, y2, thickness, c.r, c.g, c.b, c.a);
123
+    }
124
+
125
+    pub fn push_sprite(
126
+        &mut self,
127
+        sprite: Sprite,
128
+        pos_x: i32,             // tilespace
129
+        pos_y: i32,
130
+        colour: Colour,
131
+    ) {
132
+        // need local variable because they're used in calls to add_vert
133
+        let w = self.tile_width;
134
+        let h = self.tile_height;
135
+
136
+        let x = (pos_x as f32) * w;
137
+        let y = (pos_y as f32) * h;
138
+
139
+        let (r, g, b, a) = match colour {
140
+            Colour::Red => (1.0, 0.0, 0.0, 1.0),
141
+            Colour::Green => (0.0, 1.0, 0.0, 1.0),
142
+            Colour::Blue => (0.0, 0.0, 1.0, 1.0),
143
+            Colour::Yellow => (1.0, 1.0, 0.0, 1.0),
144
+            Colour::Orange => (1.0, 0.25, 0.0, 1.0),
145
+            Colour::Purple => (1.0, 0.0, 1.0, 1.0),
146
+            Colour::Brown => (0.5, 0.25, 0.0, 1.0),
147
+            Colour::Grey => (0.5, 0.5, 0.5, 1.0),
148
+        };
149
+
150
+        let (t1, t2, t3, t4) = self.get_sprite_uv(sprite);
151
+
152
+        // building a degenerate triangle:
153
+        // push last vertex then the 1st vertex followed by the 1st vertex again
154
+        self.dup();
155
+        self.push(x, y, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0);
156
+
157
+        self.push(x, y, r, g, b, a, t1.u, t1.v);
158
+        self.push(x + w, y, r, g, b, a, t2.u, t2.v);
159
+        self.push(x, y + h, r, g, b, a, t3.u, t3.v);
160
+        self.push(x + w, y + h, r, g, b, a, t4.u, t4.v);
161
+    }
162
+
163
+    pub fn push_text(
164
+        &mut self,
165
+        text: &str,
166
+        pos: Tile2D,            // in tilespace screen-coordinates
167
+        col: Col,
168
+    ) {
169
+        let mut v2 = self.point_from_tile(pos);
170
+
171
+        for c in text.chars() {
172
+            v2 = self.push_char(c, v2, col);
173
+        }
174
+    }
175
+
176
+    fn push_char(&mut self, c: char, pos: Vec2D, col: Col) -> Vec2D {
177
+        // need local variable because they're used in calls to add_vert
178
+        let w = self.tile_width / 2.0;
179
+        let h = self.tile_height;
180
+
181
+        let (t1, t2, t3, t4) = self.get_char_uv(c);
182
+
183
+        // building a degenerate triangle:
184
+        // push last vertex then the 1st vertex followed by the 1st vertex again
185
+        self.dup();
186
+        self.push(pos.x, pos.y, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0);
187
+
188
+        self.push(pos.x, pos.y, col.r, col.g, col.b, col.a, t1.u, t1.v);
189
+        self.push(pos.x + w, pos.y, col.r, col.g, col.b, col.a, t2.u, t2.v);
190
+        self.push(pos.x, pos.y + h, col.r, col.g, col.b, col.a, t3.u, t3.v);
191
+        self.push(pos.x + w, pos.y + h, col.r, col.g, col.b, col.a, t4.u, t4.v);
192
+
193
+        Vec2D {
194
+            x: pos.x + w,
195
+            y: pos.y,
196
+        }
197
+    }
198
+
199
+    fn push_filled(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, r: f32, g: f32, b: f32, a: f32) {
200
+        let (t1, t2, t3, t4) = self.get_sprite_uv(Sprite::DebugFilled);
201
+
202
+        self.dup();
203
+        self.push(x1, y1, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0);
204
+
205
+        self.push(x1, y1, r, g, b, a, t1.u, t1.v);
206
+        self.push(x2, y1, r, g, b, a, t2.u, t2.v);
207
+        self.push(x1, y2, r, g, b, a, t3.u, t3.v);
208
+        self.push(x2, y2, r, g, b, a, t4.u, t4.v);
209
+    }
210
+
211
+    fn push_outline(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32, r: f32, g: f32, b: f32, a: f32) {
212
+        let w = self.tile_width;
213
+        let h = self.tile_height;
214
+
215
+        let tw = w * thickness;
216
+        let th = h * thickness;
217
+
218
+        let (t1, t2, t3, t4) = self.get_sprite_uv(Sprite::DebugFilled);
219
+
220
+        self.dup();
221
+        self.push(x1, y1, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0);
222
+
223
+        // left bar
224
+        self.push(x1, y1, r, g, b, a, t1.u, t1.v);
225
+        self.push(x1 + tw, y1 + th, r, g, b, a, t2.u, t2.v);
226
+        self.push(x1, y2, r, g, b, a, t3.u, t3.v);
227
+        self.push(x1 + tw, y2 - th, r, g, b, a, t4.u, t4.v);
228
+
229
+        // top bar (tr corner)
230
+        self.push(x2, y2, r, g, b, a, t1.u, t1.v);
231
+        self.push(x2 - tw, y2 - th, r, g, b, a, t2.u, t2.v);
232
+
233
+        // right bar (br corner)
234
+        self.push(x2, y1, r, g, b, a, t3.u, t3.v);
235
+        self.push(x2 - tw, y1 + th, r, g, b, a, t4.u, t4.v);
236
+
237
+        // bottom bar
238
+        self.push(x1, y1, r, g, b, a, t1.u, t1.v);
239
+        self.push(x1 + tw, y1 + th, r, g, b, a, t2.u, t2.v);
240
+    }
241
+
242
+    // duplicate the last geometry point
243
+    fn dup(&mut self) {
244
+        let len = self.geo.len();
245
+
246
+        let x: f32;
247
+        let y: f32;
248
+        let r: f32;
249
+        let g: f32;
250
+        let b: f32;
251
+        let a: f32;
252
+        let u: f32;
253
+        let v: f32;
254
+        {
255
+            x = self.geo[len - 8];
256
+            y = self.geo[len - 7];
257
+            r = self.geo[len - 6];
258
+            g = self.geo[len - 5];
259
+            b = self.geo[len - 4];
260
+            a = self.geo[len - 3];
261
+            u = self.geo[len - 2];
262
+            v = self.geo[len - 1];
263
+        }
264
+
265
+        self.push(x, y, r, g, b, a, u, v);
266
+    }
267
+
268
+    fn get_sprite_uv(&self, sprite: Sprite) -> (UV, UV, UV, UV) {
269
+        let sprite_location = get_sprite_location(sprite);
270
+
271
+        let u_unit = self.sprite_u_unit;
272
+        let v_unit = self.sprite_v_unit;
273
+
274
+        let u = (sprite_location.col as f32) * u_unit;
275
+        let v = (sprite_location.row as f32) * v_unit;
276
+
277
+        (UV{u, v: 1.0 - (v + v_unit)},
278
+         UV{u: u + u_unit, v: 1.0 - (v + v_unit)},
279
+         UV{u, v: 1.0 - v},
280
+         UV{u: u + u_unit, v: 1.0 - v})
281
+    }
282
+
283
+    fn get_char_uv(&self, c: char) -> (UV, UV, UV, UV) {
284
+        let sprite_location = sprite_location_from_char(c);
285
+
286
+        let u_unit = self.sprite_u_unit / 2.0;
287
+        let v_unit = self.sprite_v_unit;
288
+
289
+        let u = (sprite_location.col as f32) * u_unit;
290
+        let v = (sprite_location.row as f32) * v_unit;
291
+
292
+        (UV{u, v: 1.0 - (v + v_unit)},
293
+         UV{u: u + u_unit, v: 1.0 - (v + v_unit)},
294
+         UV{u, v: 1.0 - v},
295
+         UV{u: u + u_unit, v: 1.0 - v})
296
+    }
297
+}

+ 127
- 0
src/lib.rs View File

@@ -0,0 +1,127 @@
1
+#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
2
+#![cfg_attr(feature = "cargo-clippy", allow(many_single_char_names, too_many_arguments))]
3
+
4
+extern crate wasm_bindgen;
5
+
6
+use wasm_bindgen::prelude::*;
7
+
8
+pub mod units;
9
+pub mod sprite;
10
+pub mod text;
11
+pub mod entity;
12
+pub mod game;
13
+pub mod state;
14
+pub mod geometry;
15
+pub mod controller;
16
+
17
+use controller::{ControllerButton, ControllerAction};
18
+use game::Game;
19
+
20
+#[wasm_bindgen(js_namespace = console)]
21
+extern "C" {
22
+    fn log(s: &str);
23
+}
24
+
25
+#[wasm_bindgen]
26
+pub struct KeyEventReturn {
27
+    call_tick: bool,
28
+    prevent_default: bool,
29
+}
30
+
31
+#[wasm_bindgen]
32
+impl KeyEventReturn {
33
+    pub fn call_tick(&self) -> bool {
34
+        self.call_tick
35
+    }
36
+
37
+    pub fn prevent_default(&self) -> bool {
38
+        self.prevent_default
39
+    }
40
+}
41
+
42
+#[wasm_bindgen]
43
+pub struct GameBridge {
44
+    game: Game,
45
+}
46
+
47
+#[wasm_bindgen]
48
+impl GameBridge {
49
+    #[wasm_bindgen(constructor)]
50
+    pub fn new(
51
+        canvas_width: f32,
52
+        canvas_height: f32,
53
+        view_frame_width: i32,       // view-frame width
54
+        view_frame_height: i32,      // view-frame height
55
+        tileset_texture_width: i32,  // tileset texture width
56
+        tileset_texture_height: i32, // tileset texture height
57
+    ) -> GameBridge {
58
+        GameBridge {
59
+            game: Game::new(canvas_width, canvas_height,
60
+                            view_frame_width, view_frame_height,
61
+                            tileset_texture_width, tileset_texture_height)
62
+        }
63
+    }
64
+
65
+    pub fn init(&mut self) {
66
+        self.game.init();
67
+    }
68
+
69
+    pub fn event_key_down(&mut self, key: String) -> KeyEventReturn {
70
+        let mut prevent_default = true;
71
+        match key.as_ref() {
72
+            "ArrowLeft" => self.game.input(ControllerButton::Left, ControllerAction::Down),
73
+            "ArrowRight" => self.game.input(ControllerButton::Right, ControllerAction::Down),
74
+            "ArrowUp" => self.game.input(ControllerButton::Up, ControllerAction::Down),
75
+            "ArrowDown" => self.game.input(ControllerButton::Down, ControllerAction::Down),
76
+            "a" => self.game.input(ControllerButton::A, ControllerAction::Down),
77
+            "z" => self.game.input(ControllerButton::B, ControllerAction::Down),
78
+            "Enter" => self.game.input(ControllerButton::Start, ControllerAction::Down),
79
+            "Shift" => self.game.input(ControllerButton::Select, ControllerAction::Down),
80
+            _ => prevent_default = false,
81
+        }
82
+
83
+        // return true if we should call tick
84
+        KeyEventReturn {
85
+            call_tick: true,
86
+            prevent_default,
87
+        }
88
+    }
89
+
90
+    pub fn event_key_up(&mut self, key: String) -> KeyEventReturn {
91
+        let mut prevent_default = true;
92
+        match key.as_ref() {
93
+            "ArrowLeft" => self.game.input(ControllerButton::Left, ControllerAction::Up),
94
+            "ArrowRight" => self.game.input(ControllerButton::Right, ControllerAction::Up),
95
+            "ArrowUp" => self.game.input(ControllerButton::Up, ControllerAction::Up),
96
+            "ArrowDown" => self.game.input(ControllerButton::Down, ControllerAction::Up),
97
+            "a" => self.game.input(ControllerButton::A, ControllerAction::Up),
98
+            "z" => self.game.input(ControllerButton::B, ControllerAction::Up),
99
+            "Enter" => self.game.input(ControllerButton::Start, ControllerAction::Up),
100
+            "Shift" => self.game.input(ControllerButton::Select, ControllerAction::Up),
101
+            _ => prevent_default = false,
102
+        }
103
+
104
+        // return true if we should call tick
105
+        KeyEventReturn {
106
+            call_tick: false,
107
+            prevent_default,
108
+        }
109
+    }
110
+
111
+    pub fn tick(&mut self, delta: f32) -> bool {
112
+        self.game.tick(delta)
113
+    }
114
+
115
+    // use the current gamestate to update geometry
116
+    pub fn update_geometry(&mut self) {
117
+        self.game.update_geometry();
118
+    }
119
+
120
+    pub fn geo_len(&self) -> usize {
121
+        self.game.geo_len()
122
+    }
123
+
124
+    pub fn geo_ptr(&self) -> *const f32 {
125
+        self.game.geo_ptr()
126
+    }
127
+}

+ 26
- 0
src/sprite.rs View File

@@ -0,0 +1,26 @@
1
+#[derive(Debug, Clone, Copy)]
2
+pub struct SpriteLocation {
3
+    // location of sprite on tileset bitmap
4
+    // units are given in tiles (16x16 pixels)
5
+    pub row: i32,
6
+    pub col: i32,
7
+}
8
+
9
+#[derive(Debug, Clone, Copy)]
10
+pub enum Sprite {
11
+    Block,
12
+    DebugChecker,
13
+    DebugBlank,
14
+    DebugFilled,
15
+    Debug4Corners,
16
+}
17
+
18
+pub fn get_sprite_location(sprite: Sprite) -> SpriteLocation {
19
+    match sprite {
20
+        Sprite::Block => SpriteLocation { row: 6, col: 9 },
21
+        Sprite::DebugChecker => SpriteLocation { row: 0, col: 13 },
22
+        Sprite::DebugBlank => SpriteLocation { row: 0, col: 14 },
23
+        Sprite::DebugFilled => SpriteLocation { row: 0, col: 15 },
24
+        Sprite::Debug4Corners => SpriteLocation { row: 0, col: 16 },
25
+    }
26
+}

+ 46
- 0
src/state.rs View File

@@ -0,0 +1,46 @@
1
+use entity::*;
2
+use sprite::Sprite;
3
+use controller::Controller;
4
+use geometry::Geometry;
5
+
6
+pub struct State {
7
+
8
+    player: Entity,
9
+}
10
+
11
+impl State {
12
+    pub fn new(view_frame_width: i32, view_frame_height: i32) -> State {
13
+        State {
14
+            player: Entity::new(0, 0, Colour::Blue, Sprite::Block, false),
15
+        }
16
+    }
17
+
18
+    pub fn init(&mut self) {
19
+    }
20
+
21
+    pub fn update_geometry(&self, geometry: &mut Geometry) {
22
+        geometry.clear();
23
+
24
+        // zero vert
25
+        geometry.push(0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0);
26
+
27
+        render_entity(geometry, &self.player);
28
+    }
29
+
30
+    pub fn update_geometry_debug(&self, geometry: &mut Geometry) {
31
+
32
+    }
33
+
34
+    pub fn tick_playing(&mut self, _controller: &Controller) -> bool {
35
+        false
36
+    }
37
+}
38
+
39
+fn render_entity(geometry: &mut Geometry, e: &Entity) {
40
+    geometry.push_sprite(
41
+        e.sprite,
42
+        e.pos.x,
43
+        e.pos.y,
44
+        e.colour,
45
+    )
46
+}

+ 103
- 0
src/text.rs View File

@@ -0,0 +1,103 @@
1
+use sprite::SpriteLocation;
2
+
3
+// note: these spritelocation values are for letters which have half the width of regular sprites
4
+
5
+pub fn sprite_location_from_char(c: char) -> SpriteLocation {
6
+    match c {
7
+        ' ' => SpriteLocation { row: 23, col: 10 },
8
+
9
+        '0' => SpriteLocation { row: 23, col: 0 },
10
+        '1' => SpriteLocation { row: 23, col: 1 },
11
+        '2' => SpriteLocation { row: 23, col: 2 },
12
+        '3' => SpriteLocation { row: 23, col: 3 },
13
+        '4' => SpriteLocation { row: 23, col: 4 },
14
+        '5' => SpriteLocation { row: 23, col: 5 },
15
+        '6' => SpriteLocation { row: 23, col: 6 },
16
+        '7' => SpriteLocation { row: 23, col: 7 },
17
+        '8' => SpriteLocation { row: 23, col: 8 },
18
+        '9' => SpriteLocation { row: 23, col: 9 },
19
+
20
+        'A' => SpriteLocation { row: 24, col: 0 },
21
+        'B' => SpriteLocation { row: 24, col: 1 },
22
+        'C' => SpriteLocation { row: 24, col: 2 },
23
+        'D' => SpriteLocation { row: 24, col: 3 },
24
+        'E' => SpriteLocation { row: 24, col: 4 },
25
+        'F' => SpriteLocation { row: 24, col: 5 },
26
+        'G' => SpriteLocation { row: 24, col: 6 },
27
+        'H' => SpriteLocation { row: 24, col: 7 },
28
+        'I' => SpriteLocation { row: 24, col: 8 },
29
+        'J' => SpriteLocation { row: 24, col: 9 },
30
+        'K' => SpriteLocation { row: 24, col: 10 },
31
+        'L' => SpriteLocation { row: 24, col: 11 },
32
+        'M' => SpriteLocation { row: 24, col: 12 },
33
+        'N' => SpriteLocation { row: 24, col: 13 },
34
+        'O' => SpriteLocation { row: 24, col: 14 },
35
+        'P' => SpriteLocation { row: 24, col: 15 },
36
+        'Q' => SpriteLocation { row: 24, col: 16 },
37
+        'R' => SpriteLocation { row: 24, col: 17 },
38
+        'S' => SpriteLocation { row: 24, col: 18 },
39
+        'T' => SpriteLocation { row: 24, col: 19 },
40
+        'U' => SpriteLocation { row: 24, col: 20 },
41
+        'V' => SpriteLocation { row: 24, col: 21 },
42
+        'W' => SpriteLocation { row: 24, col: 22 },
43
+        'X' => SpriteLocation { row: 24, col: 23 },
44
+        'Y' => SpriteLocation { row: 24, col: 24 },
45
+        'Z' => SpriteLocation { row: 24, col: 25 },
46
+
47
+        'a' => SpriteLocation { row: 26, col: 0 },
48
+        'b' => SpriteLocation { row: 26, col: 1 },
49
+        'c' => SpriteLocation { row: 26, col: 2 },
50
+        'd' => SpriteLocation { row: 26, col: 3 },
51
+        'e' => SpriteLocation { row: 26, col: 4 },
52
+        'f' => SpriteLocation { row: 26, col: 5 },
53
+        'g' => SpriteLocation { row: 26, col: 6 },
54
+        'h' => SpriteLocation { row: 26, col: 7 },
55
+        'i' => SpriteLocation { row: 26, col: 8 },
56
+        'j' => SpriteLocation { row: 26, col: 9 },
57
+        'k' => SpriteLocation { row: 26, col: 10 },
58
+        'l' => SpriteLocation { row: 26, col: 11 },
59
+        'm' => SpriteLocation { row: 26, col: 12 },
60
+        'n' => SpriteLocation { row: 26, col: 13 },
61
+        'o' => SpriteLocation { row: 26, col: 14 },
62
+        'p' => SpriteLocation { row: 26, col: 15 },
63
+        'q' => SpriteLocation { row: 26, col: 16 },
64
+        'r' => SpriteLocation { row: 26, col: 17 },
65
+        's' => SpriteLocation { row: 26, col: 18 },
66
+        't' => SpriteLocation { row: 26, col: 19 },
67
+        'u' => SpriteLocation { row: 26, col: 20 },
68
+        'v' => SpriteLocation { row: 26, col: 21 },
69
+        'w' => SpriteLocation { row: 26, col: 22 },
70
+        'x' => SpriteLocation { row: 26, col: 23 },
71
+        'y' => SpriteLocation { row: 26, col: 24 },
72
+        'z' => SpriteLocation { row: 26, col: 25 },
73
+
74
+        '#' => SpriteLocation { row: 28, col: 0 },
75
+        '%' => SpriteLocation { row: 28, col: 1 },
76
+        '&' => SpriteLocation { row: 28, col: 2 },
77
+        '@' => SpriteLocation { row: 28, col: 3 },
78
+        '$' => SpriteLocation { row: 28, col: 4 },
79
+        '.' => SpriteLocation { row: 28, col: 5 },
80
+        ',' => SpriteLocation { row: 28, col: 6 },
81
+        '!' => SpriteLocation { row: 28, col: 7 },
82
+        '?' => SpriteLocation { row: 28, col: 8 },
83
+        ':' => SpriteLocation { row: 28, col: 9 },
84
+        ';' => SpriteLocation { row: 28, col: 10 },
85
+        '\'' => SpriteLocation { row: 28, col: 11 },
86
+        '"' => SpriteLocation { row: 28, col: 12 },
87
+        '(' => SpriteLocation { row: 28, col: 13 },
88
+        ')' => SpriteLocation { row: 28, col: 14 },
89
+        '[' => SpriteLocation { row: 28, col: 15 },
90
+        ']' => SpriteLocation { row: 28, col: 16 },
91
+        '*' => SpriteLocation { row: 28, col: 17 },
92
+        '/' => SpriteLocation { row: 28, col: 18 },
93
+        '\\' => SpriteLocation { row: 28, col: 19 },
94
+        '+' => SpriteLocation { row: 28, col: 20 },
95
+        '-' => SpriteLocation { row: 28, col: 21 },
96
+        '<' => SpriteLocation { row: 28, col: 22 },
97
+        '=' => SpriteLocation { row: 28, col: 23 },
98
+        '>' => SpriteLocation { row: 28, col: 24 },
99
+
100
+        _ => SpriteLocation { row: 23, col: 10 },
101
+
102
+    }
103
+}

+ 71
- 0
src/units.rs View File

@@ -0,0 +1,71 @@
1
+use log;
2
+
3
+#[derive(Debug, Clone, Copy)]
4
+pub struct Col {
5
+    pub r: f32,
6
+    pub g: f32,
7
+    pub b: f32,
8
+    pub a: f32,
9
+}
10
+
11
+impl Col {
12
+    pub fn new(r: f32, g: f32, b: f32, a: f32) -> Col {
13
+        Col {
14
+            r, g, b, a
15
+        }
16
+    }
17
+}
18
+
19
+#[derive(Debug, Clone, Copy)]
20
+pub struct Vec2D {
21
+    pub x: f32,
22
+    pub y: f32,
23
+}
24
+
25
+impl Vec2D {
26
+    pub fn log(self, msg: &str) {
27
+        log(&format!("{}: ({}, {})", msg, self.x, self.y))
28
+    }
29
+}
30
+
31
+pub fn length_vec2d(v: Vec2D) -> f32 {
32
+    ((v.x * v.x) + (v.y * v.y)).sqrt()
33
+}
34
+
35
+pub fn normalize(v: Vec2D) -> Vec2D {
36
+    let len = length_vec2d(v);
37
+
38
+    Vec2D {
39
+        x: v.x / len,
40
+        y: v.y / len,
41
+    }
42
+}
43
+
44
+pub fn normal(p1: Vec2D, p2: Vec2D) -> Vec2D {
45
+    let v = Vec2D {
46
+        x: p1.y - p2.y,
47
+        y: p2.x - p1.x,
48
+    };
49
+
50
+    normalize(v)
51
+}
52
+
53
+pub fn opposite_normal(v: Vec2D) -> Vec2D {
54
+    Vec2D {
55
+        x: -v.x,
56
+        y: -v.y,
57
+    }
58
+}
59
+
60
+// 2D Vector in tilespace
61
+#[derive(Debug, Clone, Copy)]
62
+pub struct Tile2D {
63
+    pub x: i32,
64
+    pub y: i32,
65
+}
66
+
67
+impl Tile2D {
68
+    pub fn log(self, msg: &str) {
69
+        log(&format!("{}: ({}, {})", msg, self.x, self.y))
70
+    }
71
+}

BIN
web/img/tileset.png View File


+ 246
- 0
web/js/GLRenderer.js View File

@@ -0,0 +1,246 @@
1
+import Matrix from './Matrix';
2
+
3
+const logToConsole = false;
4
+
5
+function memorySubArray(mem, ptr, length) {
6
+  const nByte = 4;
7
+  const pos = ptr / nByte;
8
+  return mem.subarray(pos, pos + length);
9
+}
10
+
11
+function initGL(canvas) {
12
+  try {
13
+    const gl = canvas.getContext('experimental-webgl', {
14
+      alpha: false,
15
+      preserveDrawingBuffer: true
16
+    });
17
+    // commented out because of jshint
18
+    //    if (!gl) {
19
+    //alert('Could not initialise WebGL, sorry :-(');
20
+    //    }
21
+
22
+    return gl;
23
+  } catch (e) {
24
+    return undefined;
25
+  }
26
+}
27
+
28
+function compileShader(gl, type, src) {
29
+  const shader = gl.createShader(type);
30
+  gl.shaderSource(shader, src);
31
+  gl.compileShader(shader);
32
+
33
+  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
34
+    //alert(gl.getShaderInfoLog(shader));
35
+    return null;
36
+  }
37
+  return shader;
38
+}
39
+
40
+function setupShaders(gl) {
41
+  const shaderProgram = gl.createProgram();
42
+
43
+  const fragmentSrc = `
44
+  precision mediump float;
45
+  varying vec4 vColor;
46
+  varying highp vec2 vTextureCoord;
47
+
48
+  uniform sampler2D uSampler;
49
+
50
+  void main(void) {
51
+    vec4 tex = texture2D(uSampler, vTextureCoord);
52
+
53
+    gl_FragColor.r = tex.r * vColor.a * vColor.r;
54
+    gl_FragColor.g = tex.r * vColor.a * vColor.g;
55
+    gl_FragColor.b = tex.r * vColor.a * vColor.b;
56
+    gl_FragColor.a = tex.r * vColor.a;
57
+
58
+  }
59
+  `;
60
+
61
+  const vertexSrc = `
62
+  attribute vec2 aVertexPosition;
63
+  attribute vec4 aVertexColor;
64
+  attribute vec2 aVertexTexture;
65
+
66
+  uniform mat4 uMVMatrix;
67
+  uniform mat4 uPMatrix;
68
+
69
+  varying vec4 vColor;
70
+  varying highp vec2 vTextureCoord;
71
+
72
+  void main(void) {
73
+    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 0.0, 1.0);
74
+    vColor = aVertexColor;
75
+    vTextureCoord = aVertexTexture;
76
+  }
77
+  `;
78
+
79
+  const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc);
80
+  const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc);
81
+
82
+  gl.attachShader(shaderProgram, vertexShader);
83
+  gl.attachShader(shaderProgram, fragmentShader);
84
+
85
+  gl.linkProgram(shaderProgram);
86
+
87
+  // commented out because of jshint
88
+  //  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
89
+  //alert('Could not initialise shaders');
90
+  //  }
91
+
92
+  gl.useProgram(shaderProgram);
93
+
94
+  shaderProgram.positionAttribute =
95
+    gl.getAttribLocation(shaderProgram, 'aVertexPosition');
96
+  gl.enableVertexAttribArray(shaderProgram.positionAttribute);
97
+
98
+  shaderProgram.colourAttribute =
99
+    gl.getAttribLocation(shaderProgram, 'aVertexColor');
100
+  gl.enableVertexAttribArray(shaderProgram.colourAttribute);
101
+
102
+  shaderProgram.textureAttribute =
103
+    gl.getAttribLocation(shaderProgram, 'aVertexTexture');
104
+  gl.enableVertexAttribArray(shaderProgram.textureAttribute);
105
+
106
+  shaderProgram.pMatrixUniform =
107
+    gl.getUniformLocation(shaderProgram, 'uPMatrix');
108
+  shaderProgram.mvMatrixUniform =
109
+    gl.getUniformLocation(shaderProgram, 'uMVMatrix');
110
+
111
+  return shaderProgram;
112
+}
113
+
114
+function setupGLState(gl) {
115
+  gl.clearColor(0.0, 0.0, 0.0, 0.0);
116
+  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
117
+  gl.enable(gl.BLEND);
118
+
119
+  // assuming that we'll be using pre-multiplied alpha
120
+  // see http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
121
+  gl.blendEquation(gl.FUNC_ADD);
122
+  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
123
+
124
+//  gl.disable(gl.DEPTH_TEST);
125
+}
126
+
127
+
128
+function handleTextureLoaded(gl, image, texture, shaderProgram) {
129
+  console.log(`handleTextureLoaded ${texture}`);
130
+  gl.bindTexture(gl.TEXTURE_2D, texture);
131
+  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
132
+  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
133
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
134
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
135
+
136
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
137
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
138
+
139
+  // gl.generateMipmap(gl.TEXTURE_2D);
140
+  gl.bindTexture(gl.TEXTURE_2D, null);
141
+
142
+  gl.activeTexture(gl.TEXTURE0);
143
+  gl.bindTexture(gl.TEXTURE_2D, texture);
144
+  gl.uniform1i(gl.getUniformLocation(shaderProgram, 'uSampler'), 0);
145
+}
146
+
147
+export class GLRenderer {
148
+  constructor(gameConfig, canvasElement) {
149
+    this.glDomElement = canvasElement;
150
+
151
+    // webgl setup
152
+    const gl = initGL(this.glDomElement);
153
+    this.gl = gl;
154
+
155
+    this.shaderProgram = setupShaders(gl);
156
+    setupGLState(gl);
157
+
158
+    this.glVertexBuffer = gl.createBuffer();
159
+    this.glColourBuffer = gl.createBuffer();
160
+    this.glTextureBuffer = gl.createBuffer();
161
+
162
+    this.mvMatrix = Matrix.create();
163
+    this.pMatrix = Matrix.create();
164
+    Matrix.ortho(this.pMatrix, 0, gameConfig.frustrum_width, 0, gameConfig.frustrum_height, 10, -10);
165
+  }
166
+
167
+  loadTexture(src) {
168
+    let that = this;
169
+
170
+    return new Promise(function(resolve, reject) {
171
+
172
+      const gl = that.gl;
173
+      that.texture = gl.createTexture();
174
+      const image = new Image();
175
+
176
+      image.addEventListener('load', () => {
177
+        handleTextureLoaded(that.gl, image, that.texture, that.shaderProgram);
178
+        resolve([image.width, image.height]);
179
+      });
180
+
181
+      image.addEventListener('error', () => {
182
+        reject();
183
+      });
184
+
185
+      image.src = src;
186
+    });
187
+  }
188
+
189
+  preDrawScene(frustrumWidth, frustrumHeight) {
190
+    const gl = this.gl;
191
+
192
+    gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
193
+    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
194
+
195
+
196
+    // render the entirety of the scene
197
+    Matrix.ortho(this.pMatrix, 0, frustrumWidth, 0, frustrumHeight, 10, -10);
198
+
199
+    gl.uniformMatrix4fv(this.shaderProgram.pMatrixUniform,
200
+                        false,
201
+                        this.pMatrix);
202
+
203
+    gl.uniformMatrix4fv(this.shaderProgram.mvMatrixUniform,
204
+                        false,
205
+                        this.mvMatrix);
206
+  }
207
+
208
+  drawBuffer(memoryF32, geo_len, geo_ptr) {
209
+    const gl = this.gl;
210
+    const shaderProgram = this.shaderProgram;
211
+
212
+    const glVertexBuffer = this.glVertexBuffer;
213
+    const glColourBuffer = this.glColourBuffer;
214
+    const glTextureBuffer = this.glTextureBuffer;
215
+
216
+    const vertexItemSize = 2;
217
+    const colourItemSize = 4;
218
+    const textureItemSize = 2;
219
+    const totalSize = (vertexItemSize + colourItemSize + textureItemSize);
220
+
221
+    const gbuf = memorySubArray(memoryF32, geo_ptr, geo_len);
222
+
223
+    gl.bindBuffer(gl.ARRAY_BUFFER, glVertexBuffer);
224
+    gl.bufferData(gl.ARRAY_BUFFER, gbuf, gl.STATIC_DRAW);
225
+    gl.vertexAttribPointer(shaderProgram.positionAttribute,
226
+                           vertexItemSize,
227
+                           gl.FLOAT, false, totalSize * 4,
228
+                           0);
229
+
230
+    gl.bindBuffer(gl.ARRAY_BUFFER, glColourBuffer);
231
+    gl.bufferData(gl.ARRAY_BUFFER, gbuf, gl.STATIC_DRAW);
232
+    gl.vertexAttribPointer(shaderProgram.colourAttribute,
233
+                           colourItemSize,
234
+                           gl.FLOAT, false, totalSize * 4,
235
+                           vertexItemSize * 4);
236
+
237
+    gl.bindBuffer(gl.ARRAY_BUFFER, glTextureBuffer);
238
+    gl.bufferData(gl.ARRAY_BUFFER, gbuf, gl.STATIC_DRAW);
239
+    gl.vertexAttribPointer(shaderProgram.textureAttribute,
240
+                           textureItemSize,
241
+                           gl.FLOAT, false, totalSize * 4,
242
+                           (vertexItemSize + colourItemSize) * 4);
243
+
244
+    gl.drawArrays(gl.TRIANGLE_STRIP, 0, geo_len / totalSize);
245
+  }
246
+}

+ 259
- 0
web/js/Matrix.js View File

@@ -0,0 +1,259 @@
1
+/**
2
+ * Code taken from gl-matrix (http://glmatrix.net/)
3
+
4
+Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV.
5
+
6
+Permission is hereby granted, free of charge, to any person obtaining a copy
7
+of this software and associated documentation files (the "Software"), to deal
8
+in the Software without restriction, including without limitation the rights
9
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+copies of the Software, and to permit persons to whom the Software is
11
+furnished to do so, subject to the following conditions:
12
+
13
+The above copyright notice and this permission notice shall be included in
14
+all copies or substantial portions of the Software.
15
+
16
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+THE SOFTWARE.
23
+**/
24
+
25
+function create() {
26
+  const out = new Float32Array(16);
27
+  out[0] = 1;
28
+  out[1] = 0;
29
+  out[2] = 0;
30
+  out[3] = 0;
31
+  out[4] = 0;
32
+  out[5] = 1;
33
+  out[6] = 0;
34
+  out[7] = 0;
35
+  out[8] = 0;
36
+  out[9] = 0;
37
+  out[10] = 1;
38
+  out[11] = 0;
39
+  out[12] = 0;
40
+  out[13] = 0;
41
+  out[14] = 0;
42
+  out[15] = 1;
43
+
44
+  return out;
45
+}
46
+
47
+function identity(out) {
48
+  out[0] = 1;
49
+  out[1] = 0;
50
+  out[2] = 0;
51
+  out[3] = 0;
52
+  out[4] = 0;
53
+  out[5] = 1;
54
+  out[6] = 0;
55
+  out[7] = 0;
56
+  out[8] = 0;
57
+  out[9] = 0;
58
+  out[10] = 1;
59
+  out[11] = 0;
60
+  out[12] = 0;
61
+  out[13] = 0;
62
+  out[14] = 0;
63
+  out[15] = 1;
64
+
65
+  return out;
66
+}
67
+
68
+function ortho(out, left, right, bottom, top, near, far) {
69
+  const lr = 1 / (left - right);
70
+  const bt = 1 / (bottom - top);
71
+  const nf = 1 / (near - far);
72
+
73
+  out[0] = -2 * lr;
74
+  out[1] = 0;
75
+  out[2] = 0;
76
+  out[3] = 0;
77
+  out[4] = 0;
78
+  out[5] = -2 * bt;
79
+  out[6] = 0;
80
+  out[7] = 0;
81
+  out[8] = 0;
82
+  out[9] = 0;
83
+  out[10] = 2 * nf;
84
+  out[11] = 0;
85
+  out[12] = (left + right) * lr;
86
+  out[13] = (top + bottom) * bt;
87
+  out[14] = (far + near) * nf;
88
+  out[15] = 1;
89
+
90
+  return out;
91
+}
92
+
93
+function clone(a) {
94
+  const out = new Float32Array(16);
95
+  out[0] = a[0];
96
+  out[1] = a[1];
97
+  out[2] = a[2];
98
+  out[3] = a[3];
99
+  out[4] = a[4];
100
+  out[5] = a[5];
101
+  out[6] = a[6];
102
+  out[7] = a[7];
103
+  out[8] = a[8];
104
+  out[9] = a[9];
105
+  out[10] = a[10];
106
+  out[11] = a[11];
107
+  out[12] = a[12];
108
+  out[13] = a[13];
109
+  out[14] = a[14];
110
+  out[15] = a[15];
111
+
112
+  return out;
113
+}
114
+
115
+function scale(out, a, v) {
116
+  const x = v[0], y = v[1], z = v[2];
117
+
118
+  out[0] = a[0] * x;
119
+  out[1] = a[1] * x;
120
+  out[2] = a[2] * x;
121
+  out[3] = a[3] * x;
122
+  out[4] = a[4] * y;
123
+  out[5] = a[5] * y;
124
+  out[6] = a[6] * y;
125
+  out[7] = a[7] * y;
126
+  out[8] = a[8] * z;
127
+  out[9] = a[9] * z;
128
+  out[10] = a[10] * z;
129
+  out[11] = a[11] * z;
130
+  out[12] = a[12];
131
+  out[13] = a[13];
132
+  out[14] = a[14];
133
+  out[15] = a[15];
134
+
135
+  return out;
136
+}
137
+
138
+function multiply(out, a, b) {
139
+  const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
140
+  const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
141
+  const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
142
+  const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
143
+
144
+  // Cache only the current line of the second matrix
145
+  const b0  = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
146
+  out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
147
+  out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
148
+  out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
149
+  out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
150
+
151
+  b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
152
+  out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
153
+  out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
154
+  out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
155
+  out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
156
+
157
+  b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
158
+  out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
159
+  out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
160
+  out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
161
+  out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
162
+
163
+  b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
164
+  out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
165
+  out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
166
+  out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
167
+  out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
168
+
169
+  return out;
170
+}
171
+
172
+function translate(out, a, v) {
173
+  const x = v[0], y = v[1], z = v[2];
174
+  // let a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23;
175
+
176
+  if (a === out) {
177
+    out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
178
+    out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
179
+    out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
180
+    out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
181
+  } else {
182
+    const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
183
+    const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
184
+    const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
185
+
186
+    out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03;
187
+    out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13;
188
+    out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23;
189
+
190
+    out[12] = a00 * x + a10 * y + a20 * z + a[12];
191
+    out[13] = a01 * x + a11 * y + a21 * z + a[13];
192
+    out[14] = a02 * x + a12 * y + a22 * z + a[14];
193
+    out[15] = a03 * x + a13 * y + a23 * z + a[15];
194
+  }
195
+
196
+  return out;
197
+}
198
+
199
+function rotateZ(out, a, rad) {
200
+  const s = Math.sin(rad), c = Math.cos(rad);
201
+  const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
202
+  const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
203
+
204
+  if (a !== out) {
205
+    out[8] = a[8];
206
+    out[9] = a[9];
207
+    out[10] = a[10];
208
+    out[11] = a[11];
209
+    out[12] = a[12];
210
+    out[13] = a[13];
211
+    out[14] = a[14];
212
+    out[15] = a[15];
213
+  }
214
+
215
+  // Perform axis-specific matrix multiplication
216
+  out[0] = a00 * c + a10 * s;
217
+  out[1] = a01 * c + a11 * s;
218
+  out[2] = a02 * c + a12 * s;
219
+  out[3] = a03 * c + a13 * s;
220
+  out[4] = a10 * c - a00 * s;
221
+  out[5] = a11 * c - a01 * s;
222
+  out[6] = a12 * c - a02 * s;
223
+  out[7] = a13 * c - a03 * s;
224
+
225
+  return out;
226
+}
227
+
228
+function transformVec2(out, a, m) {
229
+  const x = a[0];
230
+  const y = a[1];
231
+  out[0] = m[0] * x + m[4] * y + m[12];
232
+  out[1] = m[1] * x + m[5] * y + m[13];
233
+
234
+  return out;
235
+}
236
+
237
+function transformVec3(out, a, m) {
238
+  const x = a[0], y = a[1], z = a[2];
239
+  let w = m[3] * x + m[7] * y + m[11] * z + m[15];
240
+  w = w || 1.0;
241
+  out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w;
242
+  out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w;
243
+  out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;
244
+
245
+  return out;
246
+}
247
+
248
+export default {
249
+  create,
250
+  identity,
251
+  ortho,
252
+  clone,
253
+  scale,
254
+  multiply,
255
+  translate,
256
+  rotateZ,
257
+  transformVec2,
258
+  transformVec3
259
+};

+ 4
- 0
web/js/bootstrap.js View File

@@ -0,0 +1,4 @@
1
+const index = import("./index");
2
+index.then(() => {
3
+  console.log("Loaded...");
4
+});

+ 103
- 0
web/js/index.js View File

@@ -0,0 +1,103 @@
1
+import { GameBridge } from "../wasm_tetris";
2
+import { memory } from "../wasm_tetris_bg";
3
+import { GLRenderer } from "./GLRenderer";
4
+
5
+let gState = {
6
+  canvasId: 'render-canvas',
7
+
8
+  canvasWidth: 1280, // 4:3 ratio
9
+  canvasHeight: 960,
10
+
11
+  // the number of tiles that can be shown on screen
12
+  viewFrameWidth: 40, //  4:3 ratio
13
+  viewFrameHeight: 30, // 15
14
+
15
+  tileset: '/web/img/tileset.png',
16
+
17
+  gameBridge: undefined,
18
+  animationId: undefined,
19
+  renderer: undefined
20
+};
21
+
22
+const tick = () => {
23
+  const delta = 0.033;
24
+
25
+  const gameBridge = gState.gameBridge;
26
+
27
+  // game state + time + user input -> game state
28
+  const tickAgain = gameBridge.tick(delta);
29
+
30
+  render();
31
+
32
+  if (tickAgain) {
33
+    gState.animationId = requestAnimationFrame(tick);
34
+  }
35
+};
36
+
37
+const render = () => {
38
+  const gameBridge = gState.gameBridge;
39
+  const renderer = gState.renderer;
40
+
41
+  // gameBridge state -> geometry
42
+  gameBridge.update_geometry();
43
+
44
+  // render geometry
45
+  const memoryF32 = new Float32Array(memory.buffer);
46
+  const geo_len = gameBridge.geo_len();
47
+  const geo_ptr = gameBridge.geo_ptr();
48
+
49
+  renderer.preDrawScene(gState.canvasWidth, gState.canvasHeight);
50
+  renderer.drawBuffer(memoryF32, geo_len, geo_ptr);
51
+};
52
+
53
+function main() {
54
+  const canvasElement = document.getElementById(gState.canvasId);
55
+  canvasElement.width = gState.canvasWidth;
56
+  canvasElement.height = gState.canvasHeight;
57
+
58
+  let renderer = new GLRenderer(gState, canvasElement);
59
+  renderer.loadTexture(gState.tileset).then(([tilesetWidth, tilesetHeight]) => {
60
+    gState.gameBridge = new GameBridge(gState.canvasWidth,
61
+                                       gState.canvasHeight,
62
+                                       gState.viewFrameWidth,
63
+                                       gState.viewFrameHeight,
64
+                                       tilesetWidth,
65
+                                       tilesetHeight);
66
+
67
+    gState.renderer = renderer;
68
+
69
+    document.addEventListener('keydown', event => {
70
+      let key_event_return = gState.gameBridge.event_key_down(event.key);
71
+
72
+      if (key_event_return.prevent_default()) {
73
+        event.preventDefault();
74
+      }
75
+      if (key_event_return.call_tick()) {
76
+        tick();
77
+      }
78
+
79
+      key_event_return.free();
80
+    });
81
+    document.addEventListener('keyup', event => {
82
+      let key_event_return = gState.gameBridge.event_key_up(event.key);
83
+
84
+      if (key_event_return.prevent_default()) {
85
+        event.preventDefault();
86
+      }
87
+      if (key_event_return.call_tick()) {
88
+        tick();
89
+      }
90
+
91
+      key_event_return.free();
92
+    });
93
+
94
+
95
+    gState.gameBridge.init();
96
+    tick();
97
+  }).catch(error => console.error(error));
98
+
99
+  // isg: NOTE: remember to free GameBridge
100
+  // gState.gameBridge.free();
101
+}
102
+
103
+main();

+ 13
- 0
webpack.config.js View File

@@ -0,0 +1,13 @@
1
+const path = require("path");
2
+
3
+module.exports = {
4
+  entry: "./web/js/bootstrap.js",
5
+  output: {
6
+    path: path.resolve(__dirname, "dist"),
7
+    filename: "bootstrap.js",
8
+  },
9
+  optimization: {
10
+    minimize: false
11
+  },
12
+  mode: "development"
13
+};