Skip to content

Instantly share code, notes, and snippets.

@samfromcadott
Last active May 23, 2022 14:58
Show Gist options
  • Save samfromcadott/579e12a77027a360a5fe61e5a7a2bbbc to your computer and use it in GitHub Desktop.
Save samfromcadott/579e12a77027a360a5fe61e5a7a2bbbc to your computer and use it in GitHub Desktop.
Ever-changing maze, based on Legend 64 devlog
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<style media="screen">
canvas {
background-size: 100px 100px;
background-image:
linear-gradient(to right, aquamarine 1px, transparent 1px),
linear-gradient(to bottom, aquamarine 1px, transparent 1px);
border-right: 1px solid aquamarine;
border-bottom: 1px solid aquamarine;
}
</style>
<body>
<canvas id="canvas" width="700" height="700"></canvas>
<script type="text/javascript">
const TILE_SIZE = 100
const MAP_SIZE = 7
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Math
function rand_int(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function dot(a, b) {
return a.x * b.x + a.y * b.y
}
function dist(a, b) {
return Math.sqrt( Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2) )
}
function shuffle(n) {
array = [...Array(n).keys()]
var i = n, r
while (i != 0) {
r = Math.floor(Math.random() * i)
i--
[array[i], array[r]] = [array[r], array[i]]
}
return array
}
function Vector(x, y) {
this.x = x
this.y = y
this.add = function(other) {
this.x += other.x
this.y += other.y
}
this.sub = function(other) {
this.x -= other.x
this.y -= other.y
}
}
// Tiles
const tile_types = [
// {up:false, down:false, left:false, right:false}, // No openings
{up:false, down:false, left:false, right:true }, // Dead end
{up:false, down:false, left:true , right:false}, // Dead end
{up:false, down:false, left:true , right:true },
{up:false, down:true , left:false, right:false}, // Dead end
{up:false, down:true , left:false, right:true },
{up:false, down:true , left:true , right:false},
{up:false, down:true , left:true , right:true },
{up:true , down:false, left:false, right:false}, // Dead end
{up:true , down:false, left:false, right:true },
{up:true , down:false, left:true , right:false},
{up:true , down:false, left:true , right:true },
{up:true , down:true , left:false, right:false},
{up:true , down:true , left:false, right:true },
{up:true , down:true , left:true , right:false},
{up:true , down:true , left:true , right:true }
]
function Tile(x, y, openings) {
this.x = x
this.y = y
this.up = openings.up
this.down = openings.down
this.left = openings.left
this.right = openings.right
this.draw = function() {
const HALL_WIDTH = TILE_SIZE/3
ctx.fillStyle = 'black'
ctx.fillRect( // Draw center
this.x*TILE_SIZE+HALL_WIDTH,
this.y*TILE_SIZE+HALL_WIDTH,
HALL_WIDTH,
HALL_WIDTH
);
if (this.up)
ctx.fillRect(
this.x*TILE_SIZE+HALL_WIDTH,
this.y*TILE_SIZE,
HALL_WIDTH,
HALL_WIDTH+1
);
if (this.down)
ctx.fillRect(
this.x*TILE_SIZE+HALL_WIDTH,
this.y*TILE_SIZE+HALL_WIDTH*2-1,
HALL_WIDTH,
HALL_WIDTH+1
);
if (this.left)
ctx.fillRect(
this.x*TILE_SIZE,
this.y*TILE_SIZE+HALL_WIDTH,
HALL_WIDTH+1,
HALL_WIDTH
);
if (this.right)
ctx.fillRect(
this.x*TILE_SIZE+HALL_WIDTH*2-1,
this.y*TILE_SIZE+HALL_WIDTH,
HALL_WIDTH+1,
HALL_WIDTH
);
}
this.position = function () {
var x = this.x * TILE_SIZE + TILE_SIZE/2
var y = this.y * TILE_SIZE + TILE_SIZE/2
return new Vector(x, y)
}
this.can_add = function () {
// Check that for each side the connected tile fits
// Up
if (this.y > 0 && tilemap[this.x][this.y-1] instanceof Tile && this.up != tilemap[this.x][this.y-1].down)
return false
// Down
if (this.y < MAP_SIZE-1 && tilemap[this.x][this.y+1] instanceof Tile && this.down != tilemap[this.x][this.y+1].up)
return false
// Left
if (this.x > 0 && tilemap[this.x-1][this.y] instanceof Tile && this.left != tilemap[this.x-1][this.y].right)
return false
// Right
if (this.x < MAP_SIZE-1 && tilemap[this.x+1][this.y] instanceof Tile && this.right != tilemap[this.x+1][this.y].left)
return false
// Check if an opening would leave tilemap
if (this.up && this.y == 0) // Up
return false
if (this.down && this.y == MAP_SIZE-1) // Down
return false
if (this.left && this.x == 0) // Left
return false
if (this.right && this.x == MAP_SIZE-1) // Right
return false
if (this.x < MAP_SIZE && this.y < MAP_SIZE && this.x >= 0 && this.y >= 0)
return true
}
}
// Tilemap
var tilemap = Array.from(Array(MAP_SIZE), () => new Array(MAP_SIZE))
tilemap.draw = function() {
for (var i = 0; i < tilemap.length; i++)
for (var j = 0; j < tilemap[0].length; j++) {
if (tilemap[i][j] instanceof Tile)
tilemap[i][j].draw()
}
}
tilemap.add_tile = function (x, y) {
var tiles = shuffle(tile_types.length)
for (var n of tiles) {
var new_tile = new Tile( x, y, tile_types[n] )
if (new_tile.can_add()) {
tilemap[x][y] = new_tile
break
}
}
}
tilemap.update_maze = function () {
const CHANGE_DIST = 125
for (var i = 0; i < tilemap.length; i++)
for (var j = 0; j < tilemap[0].length; j++) {
// Move on to the next tile if this one is empty
if ( !(tilemap[i][j] instanceof Tile) )
continue
var tile = tilemap[i][j]
var dir = new Vector(tile.position().x-player.position.x, tile.position().y-player.position.y) // Vector from player to tile center
var facing = dot(player.velocity, dir)
var d = dist(player.position, tile.position())
// Player is moving away from tile
if ( facing < 0 && d > CHANGE_DIST )
delete tilemap[i][j]
// Player is moving toward the tile
if ( facing > 0 && d < CHANGE_DIST ) {
// Up
if (tile.up && tile.y > 0 && tilemap[i][j-1] === undefined)
tilemap.add_tile(i, j-1)
// Down
if (tile.down && tile.y < MAP_SIZE-1 && tilemap[i][j+1] === undefined)
tilemap.add_tile(i, j+1)
// Left
if (tile.left && tile.x > 0 && tilemap[i-1][j] === undefined)
tilemap.add_tile(i-1, j)
// Right
if (tile.right && tile.x < MAP_SIZE-1 && tilemap[i+1][j] === undefined)
tilemap.add_tile(i+1, j)
}
}
}
// Player
var player = {
position: new Vector(MAP_SIZE*TILE_SIZE/2, MAP_SIZE*TILE_SIZE/2),
velocity: new Vector(0, 0),
width: 10,
speed: 5,
draw: function () {
ctx.fillStyle = 'orange'
ctx.fillRect(this.position.x-this.width/2, this.position.y-this.width/2, this.width, this.width)
}
}
// Input
var map = {}
onkeydown = onkeyup = function(e) {
e = e || event
map[e.code] = e.type == 'keydown'
player.velocity = new Vector(0, 0)
if (map["ArrowUp"])
player.velocity.add( new Vector(0,-player.speed) )
if (map["ArrowDown"])
player.velocity.add( new Vector(0,player.speed) )
if (map["ArrowLeft"])
player.velocity.add( new Vector(-player.speed,0) )
if (map["ArrowRight"])
player.velocity.add( new Vector(player.speed,0) )
}
// Main loop
tilemap[3][3] = new Tile(3, 3, {up: true, down: true, left: true, right: true})
tilemap[3][2] = new Tile(3, 2, {up: true, down: true, left: false, right: false})
tilemap[3][4] = new Tile(3, 4, {up: true, down: true, left: false, right: false})
tilemap[2][3] = new Tile(2, 3, {up: false, down: false, left: true, right: true})
tilemap[4][3] = new Tile(4, 3, {up: false, down: false, left: true, right: true})
window.requestAnimationFrame(loop)
function loop(timestamp) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
tilemap.draw()
player.draw()
player.position.add(player.velocity)
tilemap.update_maze()
window.requestAnimationFrame(loop)
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment