Browse Source

Initial commit.

simondevyoutube 5 years ago
parent
commit
cf1772c326
3 changed files with 705 additions and 0 deletions
  1. 18 0
      index.html
  2. 605 0
      main.js
  3. 82 0
      mazegen.js

+ 18 - 0
index.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+  <head>
+      <script src="https://cdn.jsdelivr.net/npm/buckets-js/dist/buckets.min.js"></script>
+      <script src="https://cdn.jsdelivr.net/npm/phaser/dist/phaser.js"></script>
+  </head>
+  <body>
+    <style>
+        body {
+          overflow: hidden;
+          padding: 0px;
+          margin: 0px;
+        }
+    </style>
+
+    <script src="main.js" type="module"></script>
+  </body>
+</html>

+ 605 - 0
main.js

@@ -0,0 +1,605 @@
+import {mazegen} from './mazegen.js';
+
+const _CONFIG_WIDTH = 1920;
+const _CONFIG_HEIGHT = 1080;
+const _TILES_X = 64;
+const _TILES_Y = _TILES_X / 2;
+const _TIME_PER_STEP = 1.0 / 30.0;
+
+const _LAYER_NODES = 100;
+const _LAYER_EDGES = 50;
+const _LAYER_WALL = 10;
+const _LAYER_BG = 0;
+
+
+class SquareSprite {
+  constructor(scene, node, nodes) {
+    this._node = node;
+    this._nodes = nodes;
+    this._scene = scene;
+    this._gfx = null;
+    this._text = null;
+    this._params = {
+      tint: 0xFFFFFF,
+      start: false,
+      end: false,
+      fScore: '',
+      gScore: 0,
+    };
+    this._Redraw({});
+  }
+
+  destroy() {
+    if (this._gfx != null) {
+      this._gfx.destroy();
+    }
+
+    if (this._text != null) {
+      this._text.destroy();
+    }
+  }
+
+  Redraw(params) {
+    let changed = false;
+    for (const k in this._params) {
+      if (k in params && this._params[k] != params[k]) {
+        changed = true;
+        this._params[k] = params[k];
+      }
+    }
+
+    if (changed||1) {
+      this._Redraw();
+    }
+  }
+
+  _Redraw() {
+    const x = this._node.metadata.position[0];
+    const y = this._node.metadata.position[1];
+    const w = _CONFIG_WIDTH / _TILES_X;
+    const h = _CONFIG_HEIGHT / _TILES_Y;
+
+    if (this._gfx != null) {
+      this._gfx.destroy();
+    }
+    this._gfx = this._scene.add.graphics(0, 0);
+    this._gfx.lineStyle(w / 60.0, 0xC0C0C0, 1.0);
+    this._gfx.fillStyle(this._params.tint, 1.0);
+    this._gfx.fillRect(x * w, y * h, w, h);
+
+    this._gfx.displayOriginX = 0;
+    this._gfx.displayOriginY = 0;
+    this._gfx.setDepth(_LAYER_BG);
+  }
+}
+
+class WallRenderer {
+  constructor(scene, nodes) {
+    this._nodes = nodes;
+    this._scene = scene;
+    this._gfx = null;
+  }
+
+  destroy() {
+    if (this._gfx != null) {
+      this._gfx.destroy();
+    }
+  }
+
+  get visible() {
+    return this._visible;
+  }
+
+  set visible(v) {
+    this._visible = v;
+  }
+
+  Redraw() {
+    if (this._gfx != null) {
+      this._gfx.destroy();
+    }
+    this._gfx = this._scene.add.graphics(0, 0);
+
+    const edges = {};
+
+    for (const k in this._nodes) {
+      const curNode = this._nodes[k];
+      const x = curNode.metadata.position[0];
+      const y = curNode.metadata.position[1];
+      const w = _CONFIG_WIDTH / _TILES_X;
+      const h = _CONFIG_HEIGHT / _TILES_Y;
+
+      this._gfx.lineStyle(w / 60.0, 0xC0C0C0, 1.0);
+      if (curNode.metadata.render.active) {
+        this._gfx.fillStyle(0x8080FF, 1.0);
+      } else if (curNode.metadata.render.visited) {
+        this._gfx.fillStyle(0xFFFFFF, 1.0);
+      } else {
+        this._gfx.fillStyle(0x808080, 1.0);
+      }
+      this._gfx.fillRect(x * w, y * h, w, h);
+
+      const neighbours = [[0, 1], [1, 0], [0, -1], [-1, 0]];
+
+      this._gfx.lineStyle(w * 0.05, 0x000000, 1.0);
+      this._gfx.beginPath();
+      for (let ni = 0; ni < neighbours.length; ni++) {
+        const n = neighbours[ni];
+        const ki = _Key(x + n[0], y + n[1]);
+
+        if (curNode.edges.indexOf(_Key(x, y + 1)) < 0) {
+          this._gfx.moveTo(w * (x + 0.0), h * (y + 1.0));
+          this._gfx.lineTo(w * (x + 1.0), h * (y + 1.0));
+        }
+
+        if (curNode.edges.indexOf(_Key(x + 1, y + 0)) < 0) {
+          this._gfx.moveTo(w * (x + 1.0), h * (y + 0.0));
+          this._gfx.lineTo(w * (x + 1.0), h * (y + 1.0));
+        }
+
+        if (curNode.edges.indexOf(_Key(x, y - 1)) < 0) {
+          this._gfx.moveTo(w * (x + 0.0), h * (y + 0.0));
+          this._gfx.lineTo(w * (x + 1.0), h * (y + 0.0));
+        }
+
+        if (curNode.edges.indexOf(_Key(x - 1, y)) < 0) {
+          this._gfx.moveTo(w * (x + 0.0), h * (y + 0.0));
+          this._gfx.lineTo(w * (x + 0.0), h * (y + 1.0));
+        }
+      }
+      this._gfx.closePath();
+      this._gfx.strokePath();
+    }
+
+    this._gfx.displayOriginX = 0;
+    this._gfx.displayOriginY = 0;
+    this._gfx.setDepth(_LAYER_WALL);
+    this._gfx.setVisible(this._visible);
+  }
+}
+
+class PotentialEdgeRenderer {
+  constructor(scene, nodes) {
+    this._nodes = nodes;
+    this._scene = scene;
+    this._gfx = null;
+  }
+
+  destroy() {
+    if (this._gfx != null) {
+      this._gfx.destroy();
+    }
+  }
+
+  get visible() {
+    return this._visible;
+  }
+
+  set visible(v) {
+    this._visible = v;
+  }
+
+  Redraw() {
+    if (this._gfx != null) {
+      this._gfx.destroy();
+    }
+    this._gfx = this._scene.add.graphics(0, 0);
+
+    const edges = {};
+
+    for (const k in this._nodes) {
+      const curNode = this._nodes[k];
+      const x = curNode.metadata.position[0];
+      const y = curNode.metadata.position[1];
+      const w = _CONFIG_WIDTH / _TILES_X;
+      const h = _CONFIG_HEIGHT / _TILES_Y;
+
+      for (let nk of curNode.potentialEdges) {
+        if ((k + '.' + nk) in edges ||
+            (nk + '.' + k) in edges) {
+          continue;
+        }
+        const neighbourNode = this._nodes[nk];
+        const x1 = neighbourNode.metadata.position[0];
+        const y1 = neighbourNode.metadata.position[1];
+
+        if (curNode.metadata.render.active) {
+          if (neighbourNode.metadata.render.visited) {
+            this._gfx.lineStyle(w * 0.025, 0xFF8080, 1.0);
+          } else {
+            this._gfx.lineStyle(w * 0.025, 0x80FF80, 1.0);
+          }
+        } else if (neighbourNode.metadata.render.active) {
+          if (curNode.metadata.render.visited) {
+            this._gfx.lineStyle(w * 0.025, 0xFF8080, 1.0);
+          } else {
+            this._gfx.lineStyle(w * 0.025, 0x80FF80, 1.0);
+          }
+        } else {
+          if (curNode.edges.indexOf(nk) >= 0) {
+            this._gfx.lineStyle(w * 0.025, 0x0000FF, 1.0);
+          } else {
+            this._gfx.lineStyle(w * 0.001, 0x000000, 1.0);
+          }
+        }
+
+        this._gfx.beginPath();
+        this._gfx.moveTo(w * (x + 0.5), h * (y + 0.5));
+        this._gfx.lineTo(w * (x1 + 0.5), h * (y1 + 0.5));
+        this._gfx.closePath();
+        this._gfx.strokePath();
+
+        edges[k + '.' + nk] = true;
+        edges[nk + '.' + k] = true;
+      }
+    }
+    this._gfx.displayOriginX = 0;
+    this._gfx.displayOriginY = 0;
+    this._gfx.setDepth(_LAYER_EDGES);
+    this._gfx.setVisible(this._visible);
+  }
+}
+
+class EdgeRenderer {
+  constructor(scene, nodes) {
+    this._nodes = nodes;
+    this._scene = scene;
+    this._gfx = null;
+    this._visible = false;
+  }
+
+  destroy() {
+    if (this._gfx != null) {
+      this._gfx.destroy();
+    }
+  }
+
+  get visible() {
+    return this._visible;
+  }
+
+  set visible(v) {
+    this._visible = v;
+  }
+
+  Redraw() {
+    if (this._gfx != null) {
+      this._gfx.destroy();
+    }
+    this._gfx = this._scene.add.graphics(0, 0);
+
+    const edges = {};
+
+    for (const k in this._nodes) {
+      const curNode = this._nodes[k];
+      const x = curNode.metadata.position[0];
+      const y = curNode.metadata.position[1];
+      const w = _CONFIG_WIDTH / _TILES_X;
+      const h = _CONFIG_HEIGHT / _TILES_Y;
+
+      for (let nk of curNode.edges) {
+        if ((k + '.' + nk) in edges ||
+            (nk + '.' + k) in edges) {
+          continue;
+        }
+        const neighbourNode = this._nodes[nk];
+        const x1 = neighbourNode.metadata.position[0];
+        const y1 = neighbourNode.metadata.position[1];
+
+        this._gfx.lineStyle(w * 0.025, 0x000000, 1.0);
+
+        this._gfx.beginPath();
+        this._gfx.moveTo(w * (x + 0.5), h * (y + 0.5));
+        this._gfx.lineTo(w * (x1 + 0.5), h * (y1 + 0.5));
+        this._gfx.closePath();
+        this._gfx.strokePath();
+
+        edges[k + '.' + nk] = true;
+        edges[nk + '.' + k] = true;
+      }
+    }
+    this._gfx.displayOriginX = 0;
+    this._gfx.displayOriginY = 0;
+    this._gfx.setDepth(_LAYER_EDGES);
+    this._gfx.setVisible(this._visible);
+  }
+}
+
+class NodeRenderer {
+  constructor(scene, nodes) {
+    this._nodes = nodes;
+    this._scene = scene;
+    this._gfx = null;
+    this._visible = false;
+  }
+
+  destroy() {
+    if (this._gfx != null) {
+      this._gfx.destroy();
+    }
+  }
+
+  get visible() {
+    return this._visible;
+  }
+
+  set visible(v) {
+    this._visible = v;
+  }
+
+  Redraw() {
+    if (this._gfx != null) {
+      this._gfx.destroy();
+    }
+    this._gfx = this._scene.add.graphics(0, 0);
+
+    for (const k in this._nodes) {
+      const node = this._nodes[k];
+      const x = node.metadata.position[0];
+      const y = node.metadata.position[1];
+      const w = _CONFIG_WIDTH / _TILES_X;
+      const h = _CONFIG_HEIGHT / _TILES_Y;
+
+      if (node.metadata.render.visited) {
+        this._gfx.fillStyle(0xFF8080, 1.0);
+      } else {
+        this._gfx.fillStyle(0x80FF80, 1.0);
+      }
+      this._gfx.fillCircle(w * (x + 0.5), h * (y + 0.5), w * 0.1);
+    }
+    this._gfx.displayOriginX = 0;
+    this._gfx.displayOriginY = 0;
+    this._gfx.setDepth(_LAYER_NODES);
+    this._gfx.setVisible(this._visible);
+  }
+}
+
+
+function _Key(x, y) {
+  return x + '.' + y;
+}
+
+function _Distance(p1, p2) {
+  return ((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5;
+}
+
+
+class AStarRenderer {
+  constructor(nodes, scene) {
+    this._scene = scene;
+    this._nodes = nodes;
+    this._edgeRenderer = new EdgeRenderer(scene, nodes);
+    this._potentialEdgeRenderer = new PotentialEdgeRenderer(scene, nodes);
+    this._nodeRenderer = new NodeRenderer(scene, nodes);
+    this._wallRenderer = new WallRenderer(scene, nodes);
+    this._wallRenderer._visible = true;
+    this._sprites = {};
+  }
+
+  destroy() {
+    for (const k in this._sprites) {
+      this._sprites[k].destroy();
+    }
+
+    this._nodeRenderer.destroy();
+    this._edgeRenderer.destroy();
+    this._potentialEdgeRenderer.destroy();
+    this._wallRenderer.destroy();
+  }
+
+  Update(touched) {
+    if (touched == null) {
+      touched = Object.keys(this._nodes);
+    }
+
+    for (const k of touched) {
+      const node = this._nodes[k];
+
+      const k_bg = k + '.bg';
+
+      if (!(k_bg in this._sprites)) {
+        this._sprites[k_bg] = new SquareSprite(this._scene, node, this._nodes);
+      }
+
+      const params = {};
+
+      if (node.metadata.render.visited) {
+        params.tint = 0xFFFFFF;
+      } else {
+        params.tint = 0x808080;
+      }
+
+      if (node.metadata.render.active) {
+        params.tint = 0x8080FF;
+      }
+
+      this._sprites[k_bg].Redraw(params);
+    }
+
+    this._nodeRenderer.Redraw();
+    this._edgeRenderer.Redraw();
+    this._potentialEdgeRenderer.Redraw();
+    this._wallRenderer.Redraw();
+  }
+}
+
+
+class Graph {
+  constructor() {
+    this._nodes = {};
+  }
+
+  get Nodes() {
+    return this._nodes;
+  }
+
+  AddNode(k, e, m) {
+    this._nodes[k] = {
+      edges: [...e],
+      potentialEdges: [...e],
+      metadata: m
+    };
+  }
+}
+
+
+class MazeGenDemo {
+  constructor() {
+    this._game = this._CreateGame();
+  }
+
+  _CreateGame() {
+    const self = this;
+    const config = {
+        type: Phaser.AUTO,
+        scene: {
+            preload: function() { self._OnPreload(this); },
+            create: function() { self._OnCreate(this); },
+            update: function() { self._OnUpdate(this); },
+        },
+        scale: {
+          mode: Phaser.Scale.FIT,
+          autoCenter: Phaser.Scale.CENTER_BOTH,
+          width: _CONFIG_WIDTH,
+          height: _CONFIG_HEIGHT
+        }
+    };
+
+    return new Phaser.Game(config);
+  }
+
+  _Reset() {
+    this._astarRenderer.destroy();
+    this._Init();
+  }
+
+  _Init() {
+    this._stepTime = 0.0;
+    this._previousFrame = null;
+    this._graph = new Graph();
+
+    for (let x = 0; x < _TILES_X; x++) {
+      for (let y = 0; y < _TILES_Y; y++) {
+        const k = _Key(x, y);
+        this._graph.AddNode(
+            k, [],
+            {
+              position: [x, y],
+              weight: 0,
+              render: {
+                visited: false,
+              }
+            });
+      }
+    }
+
+    // for (let x = 0; x < _TILES_X; x++) {
+    //   for (let y = 0; y < _TILES_Y; y++) {
+    //     const roll = Math.random();
+    //     if (roll < 0.2) {
+    //       const k = _Key(x, y);
+    //       this._graph.Nodes[k].metadata.weight = -1;
+    //     }
+    //   }
+    // }
+    //
+    for (let x = 0; x < _TILES_X; x++) {
+      for (let y = 0; y < _TILES_Y; y++) {
+        const k = _Key(x, y);
+
+        for (let xi = -1; xi <= 1; xi++) {
+          for (let yi = -1; yi <= 1; yi++) {
+            if (xi == 0 && yi == 0 || (Math.abs(xi) + Math.abs(yi) != 1)) {
+              continue;
+            }
+
+            const ki = _Key(x + xi, y + yi);
+
+            if (ki in this._graph.Nodes) {
+              this._graph.Nodes[k].potentialEdges.push(ki);
+            }
+          }
+        }
+      }
+    }
+
+    const start = _Key(0, 0);
+    const end = _Key(4, 0);
+
+    this._mazeGenerator = new mazegen.MazeGenerator(this._graph.Nodes);
+    this._mazeIterator = this._mazeGenerator.GenerateIteratively(start);
+
+    this._astarRenderer = new AStarRenderer(this._graph.Nodes, this._scene);
+  }
+
+  _OnPreload(scene) {
+    this._scene = scene;
+    // /this._scene.load.image('sky', 'assets/sky.png');
+  }
+
+  _OnCreate(scene) {
+    this._keys = {
+      f: this._scene.input.keyboard.addKey('F'),
+      r: this._scene.input.keyboard.addKey('R'),
+      n: this._scene.input.keyboard.addKey('N'),
+      e: this._scene.input.keyboard.addKey('E'),
+      p: this._scene.input.keyboard.addKey('P'),
+      w: this._scene.input.keyboard.addKey('W'),
+    };
+
+    this._keys.w.on('down', function() {
+      this._astarRenderer._wallRenderer.visible = !this._astarRenderer._wallRenderer.visible;
+    }, this);
+
+    this._keys.n.on('down', function() {
+      this._astarRenderer._nodeRenderer.visible = !this._astarRenderer._nodeRenderer.visible;
+    }, this);
+
+    this._keys.e.on('down', function() {
+      this._astarRenderer._edgeRenderer.visible = !this._astarRenderer._edgeRenderer.visible;
+    }, this);
+
+    this._keys.p.on('down', function() {
+      this._astarRenderer._potentialEdgeRenderer.visible = !this._astarRenderer._potentialEdgeRenderer.visible;
+    }, this);
+
+    this._keys.r.on('down', function() {
+      this._Reset();
+    }, this);
+
+    this._keys.f.on('down', function() {
+      this._mazeIterator.next();
+    }, this);
+
+    this._Init();
+  }
+
+  _OnUpdate(scene) {
+    const currentFrame = scene.time.now;
+    if (this._previousFrame == null) {
+      this._previousFrame = currentFrame;
+      this._astarRenderer.Update(null);
+    }
+
+    const timeElapsedInS = Math.min(
+        (currentFrame - this._previousFrame) / 1000.0, 1.0 / 30.0);
+
+    let touched = [];
+    this._stepTime += timeElapsedInS;
+    while (this._stepTime >= _TIME_PER_STEP) {
+      this._stepTime -= _TIME_PER_STEP;
+      const r = this._mazeIterator.next();
+      if (r.done) {
+        setTimeout(() => {
+          this._Reset();
+        }, 2000);
+      }
+    }
+
+    this._astarRenderer.Update(null);
+
+    this._previousFrame = currentFrame;
+  }
+}
+
+const _GAME = new MazeGenDemo();

+ 82 - 0
mazegen.js

@@ -0,0 +1,82 @@
+export const mazegen = (function() {
+
+  function RouletteSelect(src) {
+    const roll = Math.random() * src.length;
+
+    let sum = 0;
+    for (let i = 0; i < src.length; i++) {
+      sum += 1.0;
+      if (roll < sum) {
+        const res = src[i];
+        src = src.splice(i, 1);
+        return res;
+      }
+    }
+  }
+
+  function _Key(x, y) {
+    return x + '.' + y;
+  }
+
+  return {
+    MazeGenerator: class {
+      constructor(nodes) {
+        this._nodes = nodes;
+        this._visited = {};
+      }
+
+      *GenerateIteratively(nodeKey) {
+        this._visited[nodeKey] = true;
+
+        const node = this._nodes[nodeKey];
+
+        node.metadata.render.visited = true;
+        node.metadata.render.active = true;
+
+        const neighbours = [...node.potentialEdges];
+        while (neighbours.length > 0) {
+          const ki = RouletteSelect(neighbours);
+
+          if (!(ki in this._visited)) {
+            node.metadata.render.active = true;
+
+            const adjNode = this._nodes[ki];
+
+            yield;
+            node.edges.push(ki);
+            adjNode.edges.push(nodeKey);
+            node.metadata.render.active = false;
+            yield* this.GenerateIteratively(ki);
+            node.metadata.render.active = true;
+          }
+        }
+
+        yield;
+        node.metadata.render.active = false;
+      }
+
+      GenerateMaze(start) {
+        this._ProcessNode(start);
+      }
+
+      _ProcessNode(nodeKey) {
+        this._visited[nodeKey] = true;
+
+        const node = this._nodes[nodeKey];
+
+        const neighbours = [...node.potentialEdges];
+        while (neighbours.length > 0) {
+          const ki = RouletteSelect(neighbours);
+
+          if (!(ki in this._visited)) {
+            const adjNode = this._nodes[ki];
+
+            node.edges.push(ki);
+            adjNode.edges.push(nodeKey);
+            this._ProcessNode(ki);
+          }
+        }
+      }
+    }
+  };
+})();