How could I achieve the effect of "lava" leaving traces
Asked Answered
M

2

12

After some time of "development" of the JavaScript game, I've came to a great idea, or so it seemed / sounded.

I was thinking of creating an entity which would represent lava. That lava would move in a specific direction, using:

function Lava(pos, ch) {
  this.pos = pos;
  this.size = new Vector(1, 1);
  if(ch == '-') {
    this.speed = new Vector(3, 0)
  }
}

where is var acrotchar = {"-": Lava};.

The whole code can be seen here or below:

var LEVELS = [
  ["                                  x  x",
   "                                  xx x",
   "                      xxx         x  x",
   "                     xx!xx        x ox",
   "                     x!!!x        x xx",
   "                     xx!xx        x  x",
   "  x                   xvx         x  x",
   "  x                               xx x",
   "  x                               x  x",
   "  xx                              x  x",
   "  x                               x xx",
   "  x                                  x",
   "  x @                xxxxx     o     x",
   "  xxxxxx     xxxxxxxxx   xxxxxxxxxxxxx",
   "       x     x                        ",
   "       x!!!!!x                        ",
   "       x!!!!!x                        ",
   "       xxxxxxx                        ",
   "--------------------------------------"]
   ];

//set variables (HP and EXP)
var life = 3;
var expo = 0;
document.getElementById("life").innerHTML = ("Lives left: " + life);
document.getElementById("expo").innerHTML = ("Points: " + expo);

//set the playzone
function Vector(x, y) {
	this.x = x; this.y = y;
}

Vector.prototype.plus = function(other) {
	return new Vector(this.x + other.x, this.y + other.y);
};

Vector.prototype.times = function(scale) {
	return new Vector(this.x * scale, this.y * scale);
};

// Note: uppercase words are used that means constructor are values
var actorchars =  {
	"@": Player,
	"o": Coin,
	"=": Lava,
	"|": Lava,
	"v": Lava,
	"#": Lava,
	"-": Flood
};

function Player(pos) {
	this.pos = pos.plus(new Vector(0, -.5));
	this.size = new Vector(.5, 1);
	this.speed = new Vector(0, 0);
}
Player.prototype.type = "player";

function Lava(pos, ch) {
	this.pos = pos;
	this.size = new Vector(1, 1);
	if (ch === "=")
		this.speed = new Vector(2, 0);
	else if (ch === '|')
		this.speed = new Vector(0, 2);
	else if (ch === 'v'){
		this.speed = new Vector(0, 5);	   
		this.repeatPos = pos;
	} else if (ch === '#')
  	this.speed = new Vector(0, 10);
}
Lava.prototype.type = "lava"

function Flood(pos, ch) {
  this.pos = pos;
  this.size = new Vector(1, 1);
  if  (ch === '-') {
    this.speed = new Vector(0, 1);
    this.repeatPos = pos;  //will be removed in the future 
  }
}
Flood.prototype.type = "flood"

//Lava.prototype.type = "Lava";

// function Wall(pos, ch) {
	// this.pos = pos;
	// this.size = new Vector(1, 1);
	// if (ch === "z")
		// this.speed = new Vector(0, 1);
// }
// Wall.prototype.type = "wall"

function Coin(pos) {
	this.basePos = this.pos = pos;
	this.size = new Vector(.6, .6);
	// take a look back
	this.wobble = Math.random() * Math.PI * 2;
}
Coin.prototype.type = "coin";

Level.prototype.isFinished = function() {
  return this.status !== null && this.finishDelay < 0;
};

function Level(plan) {
	this.width = plan[0].length;
	this.height = plan.length;
	this.grid = [];
	this.actors = [];
	
	for (var y = 0; y < this.height; y++) {
		var line = plan[y],  gridLine = [];
		for (var x = 0; x < this.width; x++) {
			var ch = line[x], fieldType = null;
			var Actor = actorchars[ch];
			if (Actor)
				this.actors.push(new Actor(new Vector(x, y), ch));
			else if (ch === "x")
				fieldType = "wall";
			else if (ch === "z")
				fieldType = "wall";
			else if (ch === "!")
				fieldType = "lava";
			else if (ch === "|")
				fieldType = "lava";
			else if (ch === "=")
				fieldType = "lava";
			else if (ch === "#")
				fieldType = "lava";
      else if (ch === "-")
        fieldType = "flood";
			else if (ch === "v"){
				fieldType = "lava";
				console.log(fieldType);
			}
			gridLine.push(fieldType);
		}
		this.grid.push(gridLine);
	}
	this.player = this.actors.filter(function(actor) {
		return actor.type === "player";
	})[0];	
	this.status = this.finishDelay = null;
}

function element(name, className) {
	var elem = document.createElement(name);
	if(className) elem.className = className;
	return elem;
}

function DOMDisplay(parent, level) {
	this.wrap = parent.appendChild(element("div", "game"));
	this.level = level;
	
	this.wrap.appendChild(this.drawBackground());
	this.actorLayer = null;
	this.drawFrame();
}


var scale = 15;


DOMDisplay.prototype.drawBackground = function() {
	var table = element("table", "background");
	table.style.width = this.level.width * scale + "px";
	table.style.height = this.level.height * scale + "px";
	this.level.grid.forEach(function(row) {
  var rowElement = table.appendChild(element("tr"));
		rowElement.style.height = scale + "px";
		row.forEach(function(type) {
			rowElement.appendChild(element("td", type));
		});
	});
	return table;
};

DOMDisplay.prototype.drawActors = function() {
	var wrap = element("div");
	this.level.actors.forEach(function(actor) {
		var rect = wrap.appendChild(element("div", "actor " + actor.type));
		rect.style.width = actor.size.x * scale + "px";
		rect.style.height = actor.size.y * scale + "px";
		rect.style.left = actor.pos.x * scale + "px";
		rect.style.top = actor.pos.y * scale + "px";
	});
	return wrap;
};

DOMDisplay.prototype.drawFrame = function() {
	if (this.actorLayer)
		this.wrap.removeChild(this.actorLayer);
	this.actorLayer = this.wrap.appendChild(this.drawActors());
	this.wrap.className = "game " + (this.level.status || "");
	this.scrollPlayerIntoView();
};


// clear it later
DOMDisplay.prototype.scrollPlayerIntoView = function() {
  var width = this.wrap.clientWidth;
  var height = this.wrap.clientHeight;
  var margin = width / 3;

  // The viewport
  var left = this.wrap.scrollLeft, right = left + width;
  var top = this.wrap.scrollTop, bottom = top + height;

  var player = this.level.player;
  var center = player.pos.plus(player.size.times(0.5))
                 .times(scale);

  if (center.x < left + margin)
    this.wrap.scrollLeft = center.x - margin;
  else if (center.x > right - margin)
    this.wrap.scrollLeft = center.x + margin - width;
  if (center.y < top + margin)
    this.wrap.scrollTop = center.y - margin;
  else if (center.y > bottom - margin)
    this.wrap.scrollTop = center.y + margin - height;
};

DOMDisplay.prototype.clear = function() {
	this.wrap.parentNode.removeChild(this.wrap);
};

Level.prototype.obstacleAt = function(pos, size) {
  var xStart = Math.floor(pos.x);
  var xEnd = Math.ceil(pos.x + size.x);
  var yStart = Math.floor(pos.y);
  var yEnd = Math.ceil(pos.y + size.y);

  if (xStart < 0 || xEnd > this.width || yStart < 0)
    return "wall";
  if (yEnd > this.height)
    return "lava", "flood";
  for (var y = yStart; y < yEnd; y++) {
    for (var x = xStart; x < xEnd; x++) {
      var fieldType = this.grid[y][x];
      if (fieldType) return fieldType;
    }
  }
};

Level.prototype.actorAt = function(actor) {
  for (var i = 0; i < this.actors.length; i++) {
    var other = this.actors[i];
    if (other != actor &&
        actor.pos.x + actor.size.x > other.pos.x &&
        actor.pos.x < other.pos.x + other.size.x &&
        actor.pos.y + actor.size.y > other.pos.y &&
        actor.pos.y < other.pos.y + other.size.y)
      return other;
  }
};

var maxStep = 0.05;

Level.prototype.animate = function(step, keys) {
  if (this.status !== null)
    this.finishDelay -= step;

  while (step > 0) {
    var thisStep = Math.min(step, maxStep);
    this.actors.forEach(function(actor) {
      actor.act(thisStep, this, keys);
    }, this);
    step -= thisStep;
  }
};


Lava.prototype.act = function(step, level) {
  var newPos = this.pos.plus(this.speed.times(step));
  if (!level.obstacleAt(newPos, this.size))
    this.pos = newPos;
  else if (this.repeatPos)
    this.pos = this.repeatPos;
  else
    this.speed = this.speed.times(-1);
};

Flood.prototype.act = function(step, level) {
  var newPos = this.pos.plus(this.speed.times(step));
  if (!level.obstacleAt(newPos, this.size))
    this.pos = newPos;
  else
    this.speed = this.speed.times(-1);
};

var wobbleSpeed = 8, wobbleDist = 0.07;

Coin.prototype.act = function(step) {
  this.wobble += step * wobbleSpeed;
  var wobblePos = Math.sin(this.wobble) * wobbleDist;
  this.pos = this.basePos.plus(new Vector(0, wobblePos));
};


var playerXSpeed = 10;

Player.prototype.moveX = function(step, level, keys) {
  this.speed.x = 0;
  if (keys.left) this.speed.x -= playerXSpeed;
  if (keys.right) this.speed.x += playerXSpeed;

  var motion = new Vector(this.speed.x * step, 0);
  var newPos = this.pos.plus(motion);
  var obstacle = level.obstacleAt(newPos, this.size);
  if (obstacle)
    level.playerTouched(obstacle);
  else
    this.pos = newPos;
};

var gravity = 30;
var jumpSpeed = 17;

Player.prototype.moveY = function(step, level, keys) {
  this.speed.y += step * gravity;
  var motion = new Vector(0, this.speed.y * step);
  var newPos = this.pos.plus(motion);
  var obstacle = level.obstacleAt(newPos, this.size);
  if (obstacle) {
    level.playerTouched(obstacle);
    if (keys.up && this.speed.y > 0)
      this.speed.y = -jumpSpeed;
    else
      this.speed.y = 0;
  } else {
    this.pos = newPos;
  }
};

Player.prototype.act = function(step, level, keys) {
  this.moveX(step, level, keys);
  this.moveY(step, level, keys);

  var otherActor = level.actorAt(this);
  if (otherActor)
    level.playerTouched(otherActor.type, otherActor);

  // Losing animation
  if (level.status == "lost") {
    this.pos.y += step;
    this.size.y -= step;
  }
};

Level.prototype.playerTouched = function(type, actor) {
  //if (type == "lava" || type == "Lava" && this.status === null) { //DOESN'T SEEM TO WORK, FIND OUT WHY MASS DAMAGE
  if (type == "lava" && this.status === null || type == "flood" && this.status === null) {
    this.status = "lost";
	life -= 1;
	console.log(life);
    expo = 0;
    document.getElementById("expo").innerHTML = ("Points: " + expo);
	document.getElementById("life").innerHTML = ("Lives left: " + life);
  if(life < 0) {	
	sessionStorage.setItem("reloading", "true");
	document.location.reload();
  }
    this.finishDelay = 1;
  } else if (type == "coin") {
	  expo += 1;
      document.getElementById("expo").innerHTML = ("Points: " + expo);
    this.actors = this.actors.filter(function(other) {
      return other != actor;
    });
    if (!this.actors.some(function(actor) {
      return actor.type == "coin";
    })) {
	  life += 1;
	  document.getElementById("life").innerHTML = ("Lives left: " + life);
      this.status = "won";
      this.finishDelay = 1;
    }
  }
};

var arrowCodes = {37: "left", 38: "up", 39: "right"};

function trackKeys(codes) {
  var pressed = Object.create(null);
  function handler(event) {
    if (codes.hasOwnProperty(event.keyCode)) {
      var down = event.type == "keydown";
      pressed[codes[event.keyCode]] = down;
      event.preventDefault();
    }
  }
  addEventListener("keydown", handler);
  addEventListener("keyup", handler);
  return pressed;
}

function runAnimation(frameFunc) {
  var lastTime = null;
  function frame(time) {
    var stop = false;
    if (lastTime !== null) {
      var timeStep = Math.min(time - lastTime, 100) / 1000;
      stop = frameFunc(timeStep) === false;
    }
    lastTime = time;
    if (!stop)
      requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
}

var arrows = trackKeys(arrowCodes);

function runLevel(level, Display, andThen) {
  var display = new Display(document.body, level);
  runAnimation(function(step) {
    level.animate(step, arrows);
    display.drawFrame(step);
    if (level.isFinished()) {
      display.clear();
      if (andThen)
        andThen(level.status);
      return false;
    }
  });
}


var lives = function() {
	ctx.font = "20px Courier";
	ctx.fontFamily = "monospace";
	ctx.fillStyle = "#666";
	ctx.textAlign = "left";
	ctx.textBaseline = "top";
	ctx.fillText("Lives left: " + life, 10, 10);
};


function runGame(plans, Display) {
  function startLevel(n) {
    runLevel(new Level(plans[n]), Display, function(status) {
      if (status == "lost") {
        startLevel(n);							
	  } else if (n < plans.length - 1)
        startLevel(n + 1);
      else
        alert("You win!");
    });
  }
  startLevel(0);
}

runGame(LEVELS, DOMDisplay);
body {
  background: #222;
}

h2 {
  color: #666;
  font-family: monospace;
  text-align: center;
}

.background {
  table-layout: fixed;
  border-spacing: 0;
}

.background td {
  padding: 0;
}

.lava, .actor {
  background: #e55;
}

.wall {
  background: #444;
  border: solid 3px #333;
  box-sizing: content-box;
}

.actor {
  position: absolute;
}

.coin {
  background: #e2e838;
  border-radius: 50%;
}

.player {
  background: #335699;
  box-shadow: none;
}

.lost .player {
  background: #a04040;
}

.won .player {
  background: green;
}

.game {
  position: relative;
  overflow: hidden;
}

#life, #expo {
  font-size: 16px;
  font-family: monospace;
  color: #666;
  text-align: left;
  baseline: top;
  margin-left: 30px;
  font-weight: bold;
}

input {
  margin-left: 30px;
  
}
<h2>Simple JavaScript Game</h2>
  
  <div id="life"></div>  
  <div id="expo"></div>

What I would like to achieve is the following:

Lava (red colored blocks, in code presented as "-", tagged as "flood") that would:

  • move in a specific direction (is already done, more or less),

  • leaves "trace" behind it's movement, (technically speaking, the lava level in this case would seem as it is rising up),

  • "flood" is able to go through the objects (optional, as I have to check the whole code carefully on my own to see where did I give the entity the "collisions")

I would really appreciate any help, especially with the lava rising effect.

Mcconaghy answered 12/3, 2019 at 13:58 Comment(3)
That is a really fun gameOreopithecus
Thanks, the (atm) whole game can be found here: rpho3nix-game.netlify.com, glad you enjoyed it tho :DMcconaghy
Dude this is seriously impressive. Reminds me of people.vcu.edu/~rpatton/currentlab/n.htmlGillum
T
1

Everything did not fit in one post, so here is the rest:

To make the lava rise through the objects, just reverse the speed and remove the obstacleAt if statement. To make the lava appear like it is behind objects, just add a css class lava-flood and set the z-index to something like -1. If you want the lava to rise in front of the objects, just remove the z-index property from the lava-flood class in CSS.

Full code - lava rising behind objects

var LEVELS = [
  ["                                  x  x",
   "                                  xx x",
   "                      xxx         x  x",
   "                     xx!xx        x ox",
   "                     x!!!x        x xx",
   "                     xx!xx        x  x",
   "  x                   xvx         x  x",
   "  x                               xx x",
   "  x                               x  x",
   "  xx                              x  x",
   "  x                               x xx",
   "  x                                  x",
   "  x @                xxxxx     o     x",
   "  xxxxxx     xxxxxxxxx   xxxxxxxxxxxxx",
   "       x     x                        ",
   "       x!!!!!x                        ",
   "       x!!!!!x                        ",
   "       xxxxxxx                        ",
   "--------------------------------------"]
   ];

//set variables (HP and EXP)
var life = 3;
var expo = 0;
document.getElementById("life").innerHTML = ("Lives left: " + life);
document.getElementById("expo").innerHTML = ("Points: " + expo);

//set the playzone
function Vector(x, y) {
	this.x = x; this.y = y;
}

Vector.prototype.plus = function(other) {
	return new Vector(this.x + other.x, this.y + other.y);
};

Vector.prototype.times = function(scale) {
	return new Vector(this.x * scale, this.y * scale);
};

// Note: uppercase words are used that means constructor are values
var actorchars =  {
	"@": Player,
	"o": Coin,
	"=": Lava,
	"|": Lava,
	"v": Lava,
	"#": Lava,
	"-": LavaFlood
};

function Player(pos) {
	this.pos = pos.plus(new Vector(0, -.5));
	this.size = new Vector(.5, 1);
	this.speed = new Vector(0, 0);
}
Player.prototype.type = "player";

function Lava(pos, ch) {
	this.pos = pos;
	this.size = new Vector(1, 1);
	if (ch === "=")
		this.speed = new Vector(2, 0);
	else if (ch === '|')
		this.speed = new Vector(0, 2);
	else if (ch === 'v'){
		this.speed = new Vector(0, 5);	   
		this.repeatPos = pos;
	} else if (ch === '#')
  	this.speed = new Vector(0, 10);
}
Lava.prototype.type = "lava"

function LavaFlood(pos, ch) {
  this.pos = pos;
  this.size = new Vector(1, 1);
  this.trueSize = new Vector(1, 1);
  if  (ch === '-') {
    this.speed = new Vector(0, -1);
    this.repeatPos = pos;  //will be removed in the future 
  }
}
LavaFlood.prototype.type = "lava-flood"

//Lava.prototype.type = "Lava";

// function Wall(pos, ch) {
	// this.pos = pos;
	// this.size = new Vector(1, 1);
	// if (ch === "z")
		// this.speed = new Vector(0, 1);
// }
// Wall.prototype.type = "wall"

function Coin(pos) {
	this.basePos = this.pos = pos;
	this.size = new Vector(.6, .6);
	// take a look back
	this.wobble = Math.random() * Math.PI * 2;
}
Coin.prototype.type = "coin";

Level.prototype.isFinished = function() {
  return this.status !== null && this.finishDelay < 0;
};

function Level(plan) {
	this.width = plan[0].length;
	this.height = plan.length;
	this.grid = [];
	this.actors = [];
	
	for (var y = 0; y < this.height; y++) {
		var line = plan[y],  gridLine = [];
		for (var x = 0; x < this.width; x++) {
			var ch = line[x], fieldType = null;
			var Actor = actorchars[ch];
			if (Actor)
				this.actors.push(new Actor(new Vector(x, y), ch));
			else if (ch === "x")
				fieldType = "wall";
			else if (ch === "z")
				fieldType = "wall";
			else if (ch === "!")
				fieldType = "lava";
			else if (ch === "|")
				fieldType = "lava";
			else if (ch === "=")
				fieldType = "lava";
			else if (ch === "#")
				fieldType = "lava";
      else if (ch === "-")
        fieldType = "lava-flood";
			else if (ch === "v"){
				fieldType = "lava";
				console.log(fieldType);
			}
			gridLine.push(fieldType);
		}
		this.grid.push(gridLine);
	}
	this.player = this.actors.filter(function(actor) {
		return actor.type === "player";
	})[0];	
	this.status = this.finishDelay = null;
}

function element(name, className) {
	var elem = document.createElement(name);
	if(className) elem.className = className;
	return elem;
}

function DOMDisplay(parent, level) {
	this.wrap = parent.appendChild(element("div", "game"));
	this.level = level;
	
	this.wrap.appendChild(this.drawBackground());
	this.actorLayer = null;
	this.drawFrame();
}


var scale = 15;


DOMDisplay.prototype.drawBackground = function() {
	var table = element("table", "background");
	table.style.width = this.level.width * scale + "px";
	table.style.height = this.level.height * scale + "px";
	this.level.grid.forEach(function(row) {
  var rowElement = table.appendChild(element("tr"));
		rowElement.style.height = scale + "px";
		row.forEach(function(type) {
			rowElement.appendChild(element("td", type));
		});
	});
	return table;
};

DOMDisplay.prototype.drawActors = function() {
	var wrap = element("div");
	this.level.actors.forEach(function(actor) {
		var rect = wrap.appendChild(element("div", "actor " + actor.type));
		rect.style.width = actor.size.x * scale + "px";
		rect.style.height = actor.size.y * scale + "px";
		rect.style.left = actor.pos.x * scale + "px";
		rect.style.top = actor.pos.y * scale + "px";
	});
	return wrap;
};

DOMDisplay.prototype.drawFrame = function() {
	if (this.actorLayer)
		this.wrap.removeChild(this.actorLayer);
	this.actorLayer = this.wrap.appendChild(this.drawActors());
	this.wrap.className = "game " + (this.level.status || "");
	this.scrollPlayerIntoView();
};


// clear it later
DOMDisplay.prototype.scrollPlayerIntoView = function() {
  var width = this.wrap.clientWidth;
  var height = this.wrap.clientHeight;
  var margin = width / 3;

  // The viewport
  var left = this.wrap.scrollLeft, right = left + width;
  var top = this.wrap.scrollTop, bottom = top + height;

  var player = this.level.player;
  var center = player.pos.plus(player.size.times(0.5))
                 .times(scale);

  if (center.x < left + margin)
    this.wrap.scrollLeft = center.x - margin;
  else if (center.x > right - margin)
    this.wrap.scrollLeft = center.x + margin - width;
  if (center.y < top + margin)
    this.wrap.scrollTop = center.y - margin;
  else if (center.y > bottom - margin)
    this.wrap.scrollTop = center.y + margin - height;
};

DOMDisplay.prototype.clear = function() {
	this.wrap.parentNode.removeChild(this.wrap);
};

Level.prototype.obstacleAt = function(pos, size) {
  var xStart = Math.floor(pos.x);
  var xEnd = Math.ceil(pos.x + size.x);
  var yStart = Math.floor(pos.y);
  var yEnd = Math.ceil(pos.y + size.y);

  if (xStart < 0 || xEnd > this.width || yStart < 0)
    return "wall";
  if (yEnd > this.height)
    return "lava", "lava-flood";
  for (var y = yStart; y < yEnd; y++) {
    for (var x = xStart; x < xEnd; x++) {
      var fieldType = this.grid[y][x];
      if (fieldType) return fieldType;
    }
  }
};

Level.prototype.actorAt = function(actor) {
  for (var i = 0; i < this.actors.length; i++) {
    var other = this.actors[i];
    if (other != actor &&
        actor.pos.x + actor.size.x > other.pos.x &&
        actor.pos.x < other.pos.x + other.size.x &&
        actor.pos.y + actor.size.y > other.pos.y &&
        actor.pos.y < other.pos.y + other.size.y)
      return other;
  }
};

var maxStep = 0.05;

Level.prototype.animate = function(step, keys) {
  if (this.status !== null)
    this.finishDelay -= step;

  while (step > 0) {
    var thisStep = Math.min(step, maxStep);
    this.actors.forEach(function(actor) {
      actor.act(thisStep, this, keys);
    }, this);
    step -= thisStep;
  }
};


Lava.prototype.act = function(step, level) {
  var newPos = this.pos.plus(this.speed.times(step));
  if (!level.obstacleAt(newPos, this.size))
    this.pos = newPos;
  else if (this.repeatPos)
    this.pos = this.repeatPos;
  else
    this.speed = this.speed.times(-1);
};

LavaFlood.prototype.act = function(step, level) {
  this.pos = this.pos.plus(this.speed.times(step));
  this.size = this.size.plus(this.speed.times(-step));
};

var wobbleSpeed = 8, wobbleDist = 0.07;

Coin.prototype.act = function(step) {
  this.wobble += step * wobbleSpeed;
  var wobblePos = Math.sin(this.wobble) * wobbleDist;
  this.pos = this.basePos.plus(new Vector(0, wobblePos));
};


var playerXSpeed = 10;

Player.prototype.moveX = function(step, level, keys) {
  this.speed.x = 0;
  if (keys.left) this.speed.x -= playerXSpeed;
  if (keys.right) this.speed.x += playerXSpeed;

  var motion = new Vector(this.speed.x * step, 0);
  var newPos = this.pos.plus(motion);
  var obstacle = level.obstacleAt(newPos, this.size);
  if (obstacle)
    level.playerTouched(obstacle);
  else
    this.pos = newPos;
};

var gravity = 30;
var jumpSpeed = 17;

Player.prototype.moveY = function(step, level, keys) {
  this.speed.y += step * gravity;
  var motion = new Vector(0, this.speed.y * step);
  var newPos = this.pos.plus(motion);
  var obstacle = level.obstacleAt(newPos, this.size);
  if (obstacle) {
    level.playerTouched(obstacle);
    if (keys.up && this.speed.y > 0)
      this.speed.y = -jumpSpeed;
    else
      this.speed.y = 0;
  } else {
    this.pos = newPos;
  }
};

Player.prototype.act = function(step, level, keys) {
  this.moveX(step, level, keys);
  this.moveY(step, level, keys);

  var otherActor = level.actorAt(this);
  if (otherActor)
    level.playerTouched(otherActor.type, otherActor);

  // Losing animation
  if (level.status == "lost") {
    this.pos.y += step;
    this.size.y -= step;
  }
};

Level.prototype.playerTouched = function(type, actor) {
  //if (type == "lava" || type == "Lava" && this.status === null) { //DOESN'T SEEM TO WORK, FIND OUT WHY MASS DAMAGE
  if (type == "lava" && this.status === null || type == "lava-flood" && this.status === null) {
    this.status = "lost";
	life -= 1;
	console.log(life);
    expo = 0;
    document.getElementById("expo").innerHTML = ("Points: " + expo);
	document.getElementById("life").innerHTML = ("Lives left: " + life);
  if(life < 0) {	
	sessionStorage.setItem("reloading", "true");
	document.location.reload();
  }
    this.finishDelay = 1;
  } else if (type == "coin") {
	  expo += 1;
      document.getElementById("expo").innerHTML = ("Points: " + expo);
    this.actors = this.actors.filter(function(other) {
      return other != actor;
    });
    if (!this.actors.some(function(actor) {
      return actor.type == "coin";
    })) {
	  life += 1;
	  document.getElementById("life").innerHTML = ("Lives left: " + life);
      this.status = "won";
      this.finishDelay = 1;
    }
  }
};

var arrowCodes = {37: "left", 38: "up", 39: "right"};

function trackKeys(codes) {
  var pressed = Object.create(null);
  function handler(event) {
    if (codes.hasOwnProperty(event.keyCode)) {
      var down = event.type == "keydown";
      pressed[codes[event.keyCode]] = down;
      event.preventDefault();
    }
  }
  addEventListener("keydown", handler);
  addEventListener("keyup", handler);
  return pressed;
}

function runAnimation(frameFunc) {
  var lastTime = null;
  function frame(time) {
    var stop = false;
    if (lastTime !== null) {
      var timeStep = Math.min(time - lastTime, 100) / 1000;
      stop = frameFunc(timeStep) === false;
    }
    lastTime = time;
    if (!stop)
      requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
}

var arrows = trackKeys(arrowCodes);

function runLevel(level, Display, andThen) {
  var display = new Display(document.body, level);
  runAnimation(function(step) {
    level.animate(step, arrows);
    display.drawFrame(step);
    if (level.isFinished()) {
      display.clear();
      if (andThen)
        andThen(level.status);
      return false;
    }
  });
}


var lives = function() {
	ctx.font = "20px Courier";
	ctx.fontFamily = "monospace";
	ctx.fillStyle = "#666";
	ctx.textAlign = "left";
	ctx.textBaseline = "top";
	ctx.fillText("Lives left: " + life, 10, 10);
};


function runGame(plans, Display) {
  function startLevel(n) {
    runLevel(new Level(plans[n]), Display, function(status) {
      if (status == "lost") {
        startLevel(n);							
	  } else if (n < plans.length - 1)
        startLevel(n + 1);
      else
        alert("You win!");
    });
  }
  startLevel(0);
}

runGame(LEVELS, DOMDisplay);
body {
  background: #222;
}

h2 {
  color: #666;
  font-family: monospace;
  text-align: center;
}

.background {
  table-layout: fixed;
  border-spacing: 0;
}

.background td {
  padding: 0;
}

.lava, .actor {
  background: #e55;	
}

.lava-flood {
  z-index: -1;	
}

.wall {
  background: #444;
  border: solid 3px #333;
  box-sizing: content-box;
}

.actor {
  position: absolute;
}

.coin {
  background: #e2e838;
  border-radius: 50%;
}

.player {
  background: #335699;
  box-shadow: none;
}

.lost .player {
  background: #a04040;
}

.won .player {
  background: green;
}

.game {
  position: relative;
  overflow: hidden;
}

#life, #expo {
  font-size: 16px;
  font-family: monospace;
  color: #666;
  text-align: left;
  baseline: top;
  margin-left: 30px;
  font-weight: bold;
}

input {
  margin-left: 30px; 
}
<h2>Simple JavaScript Game</h2>
  
  <div id="life"></div>  
  <div id="expo"></div>

If you need clarification or help for anything, please feel free to ask.

Tabber answered 14/3, 2019 at 22:29 Comment(2)
First of, sorry for the late reply, I was ill and unable to even launch my PC, therefore not even check web. Second of, thanks for suggestions, will keep that in mind. :) The answer, you gave me, is just what I needed and it will result in some extra time for me to add few more levels to my game :DMcconaghy
@Mcconaghy That's ok, glad to know that you are better now. Don't put off rewriting the code into classes for too long. Right now, you will be able to finish the rewrite within 20 minutes, but I can tell you from experience that it will become a headache as you add more features. And please let me know if you need any help. This is a great game and I would love to see it being developed further.Tabber
T
1

Your game is quite well made, but I have a few suggestions:

  • Please, please don't use prototypes to add functions to Objects. It makes is very difficult for someone not familiar with the code to follow. A better way is to put all the member functions in the same object, so they are grouped together. It makes the code (in my opinion) way easier to read and understand. For example:
function Flood(pos, ch){
    this.pos = pos;
    //other assignments

    this.act = function(){
        //code
    }

    //other functions
}

Keeping all your functions in one class also makes it easier to find a function rather than hunting through the whole file to find it.

  • Even though most of the game is already written, it might be worthwhile looking into the new ES6 class syntax, because this is the perfect example for inheritance and object oriented programming. You can have a generic GridElement class that implements the all standard behaviours, and extend and modify only the parts that differ from type to type. This approach is extremely powerful, and makes it very easy to add new types. I would highly recommend reading more on the theory of object oriented programming, because it is a very useful tool, especially in large projects. Daniel Shiffman is an awesome YouTuber, and makes amazing videos about programming. He has an entire series on Object Oriented programming on youtube, and I would suggest watching the video here.

  • Don't display the elements in the main display function. Instead, let each class have it's own display function that returns a div element. You can call each element's display function in the main display loop. That way, it becomes much easier to add additional behaviour to particular classes. If the Flood object had its own display function, then it could return a noncontinuous trail instead of modifying its size, which is not the ideal way to implement this behaviour.

  • I am unaware of any place on earth where there are floods of Lava, and so I would suggest that you use more accurate names in your code. When I see Flood, I immediately think of water, and it led to a bit of head scratching for me to figure out that Flood was in fact, rising lava. A better class name could have been LavaFlood, so I have taken the liberty of changing the name of the class Flood to LavaFlood in the code below.

As for the lava rising effect, it was quite simple. All I did was add a trueSize variable to the (Lava)Flood class and change the act function:

(I could not post this as normal code, as the site kept saying that the code was not formatted properly)

LavaFlood.prototype.act = function(step, level) {
  var newPos = this.pos.plus(this.speed.times(step));
  if (!level.obstacleAt(newPos, this.trueSize)) {
    this.size = this.size.plus(this.speed.times(-step));
    this.pos = newPos;
  } else
    this.speed = this.speed.times(-1);
};

Full code - lava rising:

var LEVELS = [
  ["                                  x  x",
   "                                  xx x",
   "                      xxx         x  x",
   "                     xx!xx        x ox",
   "                     x!!!x        x xx",
   "                     xx!xx        x  x",
   "  x                   xvx         x  x",
   "  x                               xx x",
   "  x                               x  x",
   "  xx                              x  x",
   "  x                               x xx",
   "  x                                  x",
   "  x @                xxxxx     o     x",
   "  xxxxxx     xxxxxxxxx   xxxxxxxxxxxxx",
   "       x     x                        ",
   "       x!!!!!x                        ",
   "       x!!!!!x                        ",
   "       xxxxxxx                        ",
   "--------------------------------------"]
   ];

//set variables (HP and EXP)
var life = 3;
var expo = 0;
document.getElementById("life").innerHTML = ("Lives left: " + life);
document.getElementById("expo").innerHTML = ("Points: " + expo);

//set the playzone
function Vector(x, y) {
	this.x = x; this.y = y;
}

Vector.prototype.plus = function(other) {
	return new Vector(this.x + other.x, this.y + other.y);
};

Vector.prototype.times = function(scale) {
	return new Vector(this.x * scale, this.y * scale);
};

// Note: uppercase words are used that means constructor are values
var actorchars =  {
	"@": Player,
	"o": Coin,
	"=": Lava,
	"|": Lava,
	"v": Lava,
	"#": Lava,
	"-": LavaFlood
};

function Player(pos) {
	this.pos = pos.plus(new Vector(0, -.5));
	this.size = new Vector(.5, 1);
	this.speed = new Vector(0, 0);
}
Player.prototype.type = "player";

function Lava(pos, ch) {
	this.pos = pos;
	this.size = new Vector(1, 1);
	if (ch === "=")
		this.speed = new Vector(2, 0);
	else if (ch === '|')
		this.speed = new Vector(0, 2);
	else if (ch === 'v'){
		this.speed = new Vector(0, 5);	   
		this.repeatPos = pos;
	} else if (ch === '#')
  	this.speed = new Vector(0, 10);
}
Lava.prototype.type = "lava"

function LavaFlood(pos, ch) {
  this.pos = pos;
  this.size = new Vector(1, 1);
  this.trueSize = new Vector(1, 1);
  if  (ch === '-') {
    this.speed = new Vector(0, -1);
    this.repeatPos = pos;  //will be removed in the future 
  }
}
LavaFlood.prototype.type = "lava-flood"

//Lava.prototype.type = "Lava";

// function Wall(pos, ch) {
	// this.pos = pos;
	// this.size = new Vector(1, 1);
	// if (ch === "z")
		// this.speed = new Vector(0, 1);
// }
// Wall.prototype.type = "wall"

function Coin(pos) {
	this.basePos = this.pos = pos;
	this.size = new Vector(.6, .6);
	// take a look back
	this.wobble = Math.random() * Math.PI * 2;
}
Coin.prototype.type = "coin";

Level.prototype.isFinished = function() {
  return this.status !== null && this.finishDelay < 0;
};

function Level(plan) {
	this.width = plan[0].length;
	this.height = plan.length;
	this.grid = [];
	this.actors = [];
	
	for (var y = 0; y < this.height; y++) {
		var line = plan[y],  gridLine = [];
		for (var x = 0; x < this.width; x++) {
			var ch = line[x], fieldType = null;
			var Actor = actorchars[ch];
			if (Actor)
				this.actors.push(new Actor(new Vector(x, y), ch));
			else if (ch === "x")
				fieldType = "wall";
			else if (ch === "z")
				fieldType = "wall";
			else if (ch === "!")
				fieldType = "lava";
			else if (ch === "|")
				fieldType = "lava";
			else if (ch === "=")
				fieldType = "lava";
			else if (ch === "#")
				fieldType = "lava";
      else if (ch === "-")
        fieldType = "lava-flood";
			else if (ch === "v"){
				fieldType = "lava";
				console.log(fieldType);
			}
			gridLine.push(fieldType);
		}
		this.grid.push(gridLine);
	}
	this.player = this.actors.filter(function(actor) {
		return actor.type === "player";
	})[0];	
	this.status = this.finishDelay = null;
}

function element(name, className) {
	var elem = document.createElement(name);
	if(className) elem.className = className;
	return elem;
}

function DOMDisplay(parent, level) {
	this.wrap = parent.appendChild(element("div", "game"));
	this.level = level;
	
	this.wrap.appendChild(this.drawBackground());
	this.actorLayer = null;
	this.drawFrame();
}


var scale = 15;


DOMDisplay.prototype.drawBackground = function() {
	var table = element("table", "background");
	table.style.width = this.level.width * scale + "px";
	table.style.height = this.level.height * scale + "px";
	this.level.grid.forEach(function(row) {
  var rowElement = table.appendChild(element("tr"));
		rowElement.style.height = scale + "px";
		row.forEach(function(type) {
			rowElement.appendChild(element("td", type));
		});
	});
	return table;
};

DOMDisplay.prototype.drawActors = function() {
	var wrap = element("div");
	this.level.actors.forEach(function(actor) {
		var rect = wrap.appendChild(element("div", "actor " + actor.type));
		rect.style.width = actor.size.x * scale + "px";
		rect.style.height = actor.size.y * scale + "px";
		rect.style.left = actor.pos.x * scale + "px";
		rect.style.top = actor.pos.y * scale + "px";
	});
	return wrap;
};

DOMDisplay.prototype.drawFrame = function() {
	if (this.actorLayer)
		this.wrap.removeChild(this.actorLayer);
	this.actorLayer = this.wrap.appendChild(this.drawActors());
	this.wrap.className = "game " + (this.level.status || "");
	this.scrollPlayerIntoView();
};


// clear it later
DOMDisplay.prototype.scrollPlayerIntoView = function() {
  var width = this.wrap.clientWidth;
  var height = this.wrap.clientHeight;
  var margin = width / 3;

  // The viewport
  var left = this.wrap.scrollLeft, right = left + width;
  var top = this.wrap.scrollTop, bottom = top + height;

  var player = this.level.player;
  var center = player.pos.plus(player.size.times(0.5))
                 .times(scale);

  if (center.x < left + margin)
    this.wrap.scrollLeft = center.x - margin;
  else if (center.x > right - margin)
    this.wrap.scrollLeft = center.x + margin - width;
  if (center.y < top + margin)
    this.wrap.scrollTop = center.y - margin;
  else if (center.y > bottom - margin)
    this.wrap.scrollTop = center.y + margin - height;
};

DOMDisplay.prototype.clear = function() {
	this.wrap.parentNode.removeChild(this.wrap);
};

Level.prototype.obstacleAt = function(pos, size) {
  var xStart = Math.floor(pos.x);
  var xEnd = Math.ceil(pos.x + size.x);
  var yStart = Math.floor(pos.y);
  var yEnd = Math.ceil(pos.y + size.y);

  if (xStart < 0 || xEnd > this.width || yStart < 0)
    return "wall";
  if (yEnd > this.height)
    return "lava", "lava-flood";
  for (var y = yStart; y < yEnd; y++) {
    for (var x = xStart; x < xEnd; x++) {
      var fieldType = this.grid[y][x];
      if (fieldType) return fieldType;
    }
  }
};

Level.prototype.actorAt = function(actor) {
  for (var i = 0; i < this.actors.length; i++) {
    var other = this.actors[i];
    if (other != actor &&
        actor.pos.x + actor.size.x > other.pos.x &&
        actor.pos.x < other.pos.x + other.size.x &&
        actor.pos.y + actor.size.y > other.pos.y &&
        actor.pos.y < other.pos.y + other.size.y)
      return other;
  }
};

var maxStep = 0.05;

Level.prototype.animate = function(step, keys) {
  if (this.status !== null)
    this.finishDelay -= step;

  while (step > 0) {
    var thisStep = Math.min(step, maxStep);
    this.actors.forEach(function(actor) {
      actor.act(thisStep, this, keys);
    }, this);
    step -= thisStep;
  }
};


Lava.prototype.act = function(step, level) {
  var newPos = this.pos.plus(this.speed.times(step));
  if (!level.obstacleAt(newPos, this.size))
    this.pos = newPos;
  else if (this.repeatPos)
    this.pos = this.repeatPos;
  else
    this.speed = this.speed.times(-1);
};

LavaFlood.prototype.act = function(step, level) {
  var newPos = this.pos.plus(this.speed.times(step));
  if (!level.obstacleAt(newPos, this.trueSize)){
	this.size = this.size.plus(this.speed.times(-step)); 
    this.pos = newPos;
  }
  else
    this.speed = this.speed.times(-1);
};

var wobbleSpeed = 8, wobbleDist = 0.07;

Coin.prototype.act = function(step) {
  this.wobble += step * wobbleSpeed;
  var wobblePos = Math.sin(this.wobble) * wobbleDist;
  this.pos = this.basePos.plus(new Vector(0, wobblePos));
};


var playerXSpeed = 10;

Player.prototype.moveX = function(step, level, keys) {
  this.speed.x = 0;
  if (keys.left) this.speed.x -= playerXSpeed;
  if (keys.right) this.speed.x += playerXSpeed;

  var motion = new Vector(this.speed.x * step, 0);
  var newPos = this.pos.plus(motion);
  var obstacle = level.obstacleAt(newPos, this.size);
  if (obstacle)
    level.playerTouched(obstacle);
  else
    this.pos = newPos;
};

var gravity = 30;
var jumpSpeed = 17;

Player.prototype.moveY = function(step, level, keys) {
  this.speed.y += step * gravity;
  var motion = new Vector(0, this.speed.y * step);
  var newPos = this.pos.plus(motion);
  var obstacle = level.obstacleAt(newPos, this.size);
  if (obstacle) {
    level.playerTouched(obstacle);
    if (keys.up && this.speed.y > 0)
      this.speed.y = -jumpSpeed;
    else
      this.speed.y = 0;
  } else {
    this.pos = newPos;
  }
};

Player.prototype.act = function(step, level, keys) {
  this.moveX(step, level, keys);
  this.moveY(step, level, keys);

  var otherActor = level.actorAt(this);
  if (otherActor)
    level.playerTouched(otherActor.type, otherActor);

  // Losing animation
  if (level.status == "lost") {
    this.pos.y += step;
    this.size.y -= step;
  }
};

Level.prototype.playerTouched = function(type, actor) {
  //if (type == "lava" || type == "Lava" && this.status === null) { //DOESN'T SEEM TO WORK, FIND OUT WHY MASS DAMAGE
  if (type == "lava" && this.status === null || type == "lava-flood" && this.status === null) {
    this.status = "lost";
	life -= 1;
	console.log(life);
    expo = 0;
    document.getElementById("expo").innerHTML = ("Points: " + expo);
	document.getElementById("life").innerHTML = ("Lives left: " + life);
  if(life < 0) {	
	sessionStorage.setItem("reloading", "true");
	document.location.reload();
  }
    this.finishDelay = 1;
  } else if (type == "coin") {
	  expo += 1;
      document.getElementById("expo").innerHTML = ("Points: " + expo);
    this.actors = this.actors.filter(function(other) {
      return other != actor;
    });
    if (!this.actors.some(function(actor) {
      return actor.type == "coin";
    })) {
	  life += 1;
	  document.getElementById("life").innerHTML = ("Lives left: " + life);
      this.status = "won";
      this.finishDelay = 1;
    }
  }
};

var arrowCodes = {37: "left", 38: "up", 39: "right"};

function trackKeys(codes) {
  var pressed = Object.create(null);
  function handler(event) {
    if (codes.hasOwnProperty(event.keyCode)) {
      var down = event.type == "keydown";
      pressed[codes[event.keyCode]] = down;
      event.preventDefault();
    }
  }
  addEventListener("keydown", handler);
  addEventListener("keyup", handler);
  return pressed;
}

function runAnimation(frameFunc) {
  var lastTime = null;
  function frame(time) {
    var stop = false;
    if (lastTime !== null) {
      var timeStep = Math.min(time - lastTime, 100) / 1000;
      stop = frameFunc(timeStep) === false;
    }
    lastTime = time;
    if (!stop)
      requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
}

var arrows = trackKeys(arrowCodes);

function runLevel(level, Display, andThen) {
  var display = new Display(document.body, level);
  runAnimation(function(step) {
    level.animate(step, arrows);
    display.drawFrame(step);
    if (level.isFinished()) {
      display.clear();
      if (andThen)
        andThen(level.status);
      return false;
    }
  });
}


var lives = function() {
	ctx.font = "20px Courier";
	ctx.fontFamily = "monospace";
	ctx.fillStyle = "#666";
	ctx.textAlign = "left";
	ctx.textBaseline = "top";
	ctx.fillText("Lives left: " + life, 10, 10);
};


function runGame(plans, Display) {
  function startLevel(n) {
    runLevel(new Level(plans[n]), Display, function(status) {
      if (status == "lost") {
        startLevel(n);							
	  } else if (n < plans.length - 1)
        startLevel(n + 1);
      else
        alert("You win!");
    });
  }
  startLevel(0);
}

runGame(LEVELS, DOMDisplay);
body {
  background: #222;
}

h2 {
  color: #666;
  font-family: monospace;
  text-align: center;
}

.background {
  table-layout: fixed;
  border-spacing: 0;
}

.background td {
  padding: 0;
}

.lava, .actor {
  background: #e55;	
}

.wall {
  background: #444;
  border: solid 3px #333;
  box-sizing: content-box;
}

.actor {
  position: absolute;
}

.coin {
  background: #e2e838;
  border-radius: 50%;
}

.player {
  background: #335699;
  box-shadow: none;
}

.lost .player {
  background: #a04040;
}

.won .player {
  background: green;
}

.game {
  position: relative;
  overflow: hidden;
}

#life, #expo {
  font-size: 16px;
  font-family: monospace;
  color: #666;
  text-align: left;
  baseline: top;
  margin-left: 30px;
  font-weight: bold;
}

input {
  margin-left: 30px; 
}
<h2>Simple JavaScript Game</h2>
  
  <div id="life"></div>  
  <div id="expo"></div>

If you need clarification or help for anything, please feel free to ask.

Tabber answered 14/3, 2019 at 22:17 Comment(0)
T
1

Everything did not fit in one post, so here is the rest:

To make the lava rise through the objects, just reverse the speed and remove the obstacleAt if statement. To make the lava appear like it is behind objects, just add a css class lava-flood and set the z-index to something like -1. If you want the lava to rise in front of the objects, just remove the z-index property from the lava-flood class in CSS.

Full code - lava rising behind objects

var LEVELS = [
  ["                                  x  x",
   "                                  xx x",
   "                      xxx         x  x",
   "                     xx!xx        x ox",
   "                     x!!!x        x xx",
   "                     xx!xx        x  x",
   "  x                   xvx         x  x",
   "  x                               xx x",
   "  x                               x  x",
   "  xx                              x  x",
   "  x                               x xx",
   "  x                                  x",
   "  x @                xxxxx     o     x",
   "  xxxxxx     xxxxxxxxx   xxxxxxxxxxxxx",
   "       x     x                        ",
   "       x!!!!!x                        ",
   "       x!!!!!x                        ",
   "       xxxxxxx                        ",
   "--------------------------------------"]
   ];

//set variables (HP and EXP)
var life = 3;
var expo = 0;
document.getElementById("life").innerHTML = ("Lives left: " + life);
document.getElementById("expo").innerHTML = ("Points: " + expo);

//set the playzone
function Vector(x, y) {
	this.x = x; this.y = y;
}

Vector.prototype.plus = function(other) {
	return new Vector(this.x + other.x, this.y + other.y);
};

Vector.prototype.times = function(scale) {
	return new Vector(this.x * scale, this.y * scale);
};

// Note: uppercase words are used that means constructor are values
var actorchars =  {
	"@": Player,
	"o": Coin,
	"=": Lava,
	"|": Lava,
	"v": Lava,
	"#": Lava,
	"-": LavaFlood
};

function Player(pos) {
	this.pos = pos.plus(new Vector(0, -.5));
	this.size = new Vector(.5, 1);
	this.speed = new Vector(0, 0);
}
Player.prototype.type = "player";

function Lava(pos, ch) {
	this.pos = pos;
	this.size = new Vector(1, 1);
	if (ch === "=")
		this.speed = new Vector(2, 0);
	else if (ch === '|')
		this.speed = new Vector(0, 2);
	else if (ch === 'v'){
		this.speed = new Vector(0, 5);	   
		this.repeatPos = pos;
	} else if (ch === '#')
  	this.speed = new Vector(0, 10);
}
Lava.prototype.type = "lava"

function LavaFlood(pos, ch) {
  this.pos = pos;
  this.size = new Vector(1, 1);
  this.trueSize = new Vector(1, 1);
  if  (ch === '-') {
    this.speed = new Vector(0, -1);
    this.repeatPos = pos;  //will be removed in the future 
  }
}
LavaFlood.prototype.type = "lava-flood"

//Lava.prototype.type = "Lava";

// function Wall(pos, ch) {
	// this.pos = pos;
	// this.size = new Vector(1, 1);
	// if (ch === "z")
		// this.speed = new Vector(0, 1);
// }
// Wall.prototype.type = "wall"

function Coin(pos) {
	this.basePos = this.pos = pos;
	this.size = new Vector(.6, .6);
	// take a look back
	this.wobble = Math.random() * Math.PI * 2;
}
Coin.prototype.type = "coin";

Level.prototype.isFinished = function() {
  return this.status !== null && this.finishDelay < 0;
};

function Level(plan) {
	this.width = plan[0].length;
	this.height = plan.length;
	this.grid = [];
	this.actors = [];
	
	for (var y = 0; y < this.height; y++) {
		var line = plan[y],  gridLine = [];
		for (var x = 0; x < this.width; x++) {
			var ch = line[x], fieldType = null;
			var Actor = actorchars[ch];
			if (Actor)
				this.actors.push(new Actor(new Vector(x, y), ch));
			else if (ch === "x")
				fieldType = "wall";
			else if (ch === "z")
				fieldType = "wall";
			else if (ch === "!")
				fieldType = "lava";
			else if (ch === "|")
				fieldType = "lava";
			else if (ch === "=")
				fieldType = "lava";
			else if (ch === "#")
				fieldType = "lava";
      else if (ch === "-")
        fieldType = "lava-flood";
			else if (ch === "v"){
				fieldType = "lava";
				console.log(fieldType);
			}
			gridLine.push(fieldType);
		}
		this.grid.push(gridLine);
	}
	this.player = this.actors.filter(function(actor) {
		return actor.type === "player";
	})[0];	
	this.status = this.finishDelay = null;
}

function element(name, className) {
	var elem = document.createElement(name);
	if(className) elem.className = className;
	return elem;
}

function DOMDisplay(parent, level) {
	this.wrap = parent.appendChild(element("div", "game"));
	this.level = level;
	
	this.wrap.appendChild(this.drawBackground());
	this.actorLayer = null;
	this.drawFrame();
}


var scale = 15;


DOMDisplay.prototype.drawBackground = function() {
	var table = element("table", "background");
	table.style.width = this.level.width * scale + "px";
	table.style.height = this.level.height * scale + "px";
	this.level.grid.forEach(function(row) {
  var rowElement = table.appendChild(element("tr"));
		rowElement.style.height = scale + "px";
		row.forEach(function(type) {
			rowElement.appendChild(element("td", type));
		});
	});
	return table;
};

DOMDisplay.prototype.drawActors = function() {
	var wrap = element("div");
	this.level.actors.forEach(function(actor) {
		var rect = wrap.appendChild(element("div", "actor " + actor.type));
		rect.style.width = actor.size.x * scale + "px";
		rect.style.height = actor.size.y * scale + "px";
		rect.style.left = actor.pos.x * scale + "px";
		rect.style.top = actor.pos.y * scale + "px";
	});
	return wrap;
};

DOMDisplay.prototype.drawFrame = function() {
	if (this.actorLayer)
		this.wrap.removeChild(this.actorLayer);
	this.actorLayer = this.wrap.appendChild(this.drawActors());
	this.wrap.className = "game " + (this.level.status || "");
	this.scrollPlayerIntoView();
};


// clear it later
DOMDisplay.prototype.scrollPlayerIntoView = function() {
  var width = this.wrap.clientWidth;
  var height = this.wrap.clientHeight;
  var margin = width / 3;

  // The viewport
  var left = this.wrap.scrollLeft, right = left + width;
  var top = this.wrap.scrollTop, bottom = top + height;

  var player = this.level.player;
  var center = player.pos.plus(player.size.times(0.5))
                 .times(scale);

  if (center.x < left + margin)
    this.wrap.scrollLeft = center.x - margin;
  else if (center.x > right - margin)
    this.wrap.scrollLeft = center.x + margin - width;
  if (center.y < top + margin)
    this.wrap.scrollTop = center.y - margin;
  else if (center.y > bottom - margin)
    this.wrap.scrollTop = center.y + margin - height;
};

DOMDisplay.prototype.clear = function() {
	this.wrap.parentNode.removeChild(this.wrap);
};

Level.prototype.obstacleAt = function(pos, size) {
  var xStart = Math.floor(pos.x);
  var xEnd = Math.ceil(pos.x + size.x);
  var yStart = Math.floor(pos.y);
  var yEnd = Math.ceil(pos.y + size.y);

  if (xStart < 0 || xEnd > this.width || yStart < 0)
    return "wall";
  if (yEnd > this.height)
    return "lava", "lava-flood";
  for (var y = yStart; y < yEnd; y++) {
    for (var x = xStart; x < xEnd; x++) {
      var fieldType = this.grid[y][x];
      if (fieldType) return fieldType;
    }
  }
};

Level.prototype.actorAt = function(actor) {
  for (var i = 0; i < this.actors.length; i++) {
    var other = this.actors[i];
    if (other != actor &&
        actor.pos.x + actor.size.x > other.pos.x &&
        actor.pos.x < other.pos.x + other.size.x &&
        actor.pos.y + actor.size.y > other.pos.y &&
        actor.pos.y < other.pos.y + other.size.y)
      return other;
  }
};

var maxStep = 0.05;

Level.prototype.animate = function(step, keys) {
  if (this.status !== null)
    this.finishDelay -= step;

  while (step > 0) {
    var thisStep = Math.min(step, maxStep);
    this.actors.forEach(function(actor) {
      actor.act(thisStep, this, keys);
    }, this);
    step -= thisStep;
  }
};


Lava.prototype.act = function(step, level) {
  var newPos = this.pos.plus(this.speed.times(step));
  if (!level.obstacleAt(newPos, this.size))
    this.pos = newPos;
  else if (this.repeatPos)
    this.pos = this.repeatPos;
  else
    this.speed = this.speed.times(-1);
};

LavaFlood.prototype.act = function(step, level) {
  this.pos = this.pos.plus(this.speed.times(step));
  this.size = this.size.plus(this.speed.times(-step));
};

var wobbleSpeed = 8, wobbleDist = 0.07;

Coin.prototype.act = function(step) {
  this.wobble += step * wobbleSpeed;
  var wobblePos = Math.sin(this.wobble) * wobbleDist;
  this.pos = this.basePos.plus(new Vector(0, wobblePos));
};


var playerXSpeed = 10;

Player.prototype.moveX = function(step, level, keys) {
  this.speed.x = 0;
  if (keys.left) this.speed.x -= playerXSpeed;
  if (keys.right) this.speed.x += playerXSpeed;

  var motion = new Vector(this.speed.x * step, 0);
  var newPos = this.pos.plus(motion);
  var obstacle = level.obstacleAt(newPos, this.size);
  if (obstacle)
    level.playerTouched(obstacle);
  else
    this.pos = newPos;
};

var gravity = 30;
var jumpSpeed = 17;

Player.prototype.moveY = function(step, level, keys) {
  this.speed.y += step * gravity;
  var motion = new Vector(0, this.speed.y * step);
  var newPos = this.pos.plus(motion);
  var obstacle = level.obstacleAt(newPos, this.size);
  if (obstacle) {
    level.playerTouched(obstacle);
    if (keys.up && this.speed.y > 0)
      this.speed.y = -jumpSpeed;
    else
      this.speed.y = 0;
  } else {
    this.pos = newPos;
  }
};

Player.prototype.act = function(step, level, keys) {
  this.moveX(step, level, keys);
  this.moveY(step, level, keys);

  var otherActor = level.actorAt(this);
  if (otherActor)
    level.playerTouched(otherActor.type, otherActor);

  // Losing animation
  if (level.status == "lost") {
    this.pos.y += step;
    this.size.y -= step;
  }
};

Level.prototype.playerTouched = function(type, actor) {
  //if (type == "lava" || type == "Lava" && this.status === null) { //DOESN'T SEEM TO WORK, FIND OUT WHY MASS DAMAGE
  if (type == "lava" && this.status === null || type == "lava-flood" && this.status === null) {
    this.status = "lost";
	life -= 1;
	console.log(life);
    expo = 0;
    document.getElementById("expo").innerHTML = ("Points: " + expo);
	document.getElementById("life").innerHTML = ("Lives left: " + life);
  if(life < 0) {	
	sessionStorage.setItem("reloading", "true");
	document.location.reload();
  }
    this.finishDelay = 1;
  } else if (type == "coin") {
	  expo += 1;
      document.getElementById("expo").innerHTML = ("Points: " + expo);
    this.actors = this.actors.filter(function(other) {
      return other != actor;
    });
    if (!this.actors.some(function(actor) {
      return actor.type == "coin";
    })) {
	  life += 1;
	  document.getElementById("life").innerHTML = ("Lives left: " + life);
      this.status = "won";
      this.finishDelay = 1;
    }
  }
};

var arrowCodes = {37: "left", 38: "up", 39: "right"};

function trackKeys(codes) {
  var pressed = Object.create(null);
  function handler(event) {
    if (codes.hasOwnProperty(event.keyCode)) {
      var down = event.type == "keydown";
      pressed[codes[event.keyCode]] = down;
      event.preventDefault();
    }
  }
  addEventListener("keydown", handler);
  addEventListener("keyup", handler);
  return pressed;
}

function runAnimation(frameFunc) {
  var lastTime = null;
  function frame(time) {
    var stop = false;
    if (lastTime !== null) {
      var timeStep = Math.min(time - lastTime, 100) / 1000;
      stop = frameFunc(timeStep) === false;
    }
    lastTime = time;
    if (!stop)
      requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
}

var arrows = trackKeys(arrowCodes);

function runLevel(level, Display, andThen) {
  var display = new Display(document.body, level);
  runAnimation(function(step) {
    level.animate(step, arrows);
    display.drawFrame(step);
    if (level.isFinished()) {
      display.clear();
      if (andThen)
        andThen(level.status);
      return false;
    }
  });
}


var lives = function() {
	ctx.font = "20px Courier";
	ctx.fontFamily = "monospace";
	ctx.fillStyle = "#666";
	ctx.textAlign = "left";
	ctx.textBaseline = "top";
	ctx.fillText("Lives left: " + life, 10, 10);
};


function runGame(plans, Display) {
  function startLevel(n) {
    runLevel(new Level(plans[n]), Display, function(status) {
      if (status == "lost") {
        startLevel(n);							
	  } else if (n < plans.length - 1)
        startLevel(n + 1);
      else
        alert("You win!");
    });
  }
  startLevel(0);
}

runGame(LEVELS, DOMDisplay);
body {
  background: #222;
}

h2 {
  color: #666;
  font-family: monospace;
  text-align: center;
}

.background {
  table-layout: fixed;
  border-spacing: 0;
}

.background td {
  padding: 0;
}

.lava, .actor {
  background: #e55;	
}

.lava-flood {
  z-index: -1;	
}

.wall {
  background: #444;
  border: solid 3px #333;
  box-sizing: content-box;
}

.actor {
  position: absolute;
}

.coin {
  background: #e2e838;
  border-radius: 50%;
}

.player {
  background: #335699;
  box-shadow: none;
}

.lost .player {
  background: #a04040;
}

.won .player {
  background: green;
}

.game {
  position: relative;
  overflow: hidden;
}

#life, #expo {
  font-size: 16px;
  font-family: monospace;
  color: #666;
  text-align: left;
  baseline: top;
  margin-left: 30px;
  font-weight: bold;
}

input {
  margin-left: 30px; 
}
<h2>Simple JavaScript Game</h2>
  
  <div id="life"></div>  
  <div id="expo"></div>

If you need clarification or help for anything, please feel free to ask.

Tabber answered 14/3, 2019 at 22:29 Comment(2)
First of, sorry for the late reply, I was ill and unable to even launch my PC, therefore not even check web. Second of, thanks for suggestions, will keep that in mind. :) The answer, you gave me, is just what I needed and it will result in some extra time for me to add few more levels to my game :DMcconaghy
@Mcconaghy That's ok, glad to know that you are better now. Don't put off rewriting the code into classes for too long. Right now, you will be able to finish the rewrite within 20 minutes, but I can tell you from experience that it will become a headache as you add more features. And please let me know if you need any help. This is a great game and I would love to see it being developed further.Tabber

© 2022 - 2024 — McMap. All rights reserved.