diff --git a/166 - Retro Mario Game/index.html b/166 - Retro Mario Game/index.html new file mode 100644 index 00000000..9692cd72 --- /dev/null +++ b/166 - Retro Mario Game/index.html @@ -0,0 +1,18 @@ + + + + + + + Retro Mario Game + + + + + +

Retro Mario Game

+ + + + + diff --git a/166 - Retro Mario Game/readme.md b/166 - Retro Mario Game/readme.md new file mode 100644 index 00000000..74767d91 --- /dev/null +++ b/166 - Retro Mario Game/readme.md @@ -0,0 +1,4 @@ +## Retro Mario Game + +- Use Arrow key to move Blue Block. +- Avoid the Red Lava and take All the Yellow Points. diff --git a/166 - Retro Mario Game/script.js b/166 - Retro Mario Game/script.js new file mode 100644 index 00000000..acd94355 --- /dev/null +++ b/166 - Retro Mario Game/script.js @@ -0,0 +1,504 @@ +var LEVELS = [ + [" ", + " ", + " ", + " ", + " ", + " ", + " xxx ", + " xx xx xx!xx ", + " o o xx x!!!x ", + " xx!xx ", + " xxxxx xvx ", + " xx ", + " xx o o x ", + " x o x ", + " x xxxxx o x ", + " x xxxx o x ", + " x @ x x xxxxx x ", + " xxxxxxxxxxxx xxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxxxx ", + " x x x x ", + " x!!!x x!!!!!x ", + " x!!!x x!!!!!x ", + " xxxxx xxxxxxx ", + " ", + " "], + [" x!!x xxxxxxx x!x ", + " x!!x xxxx xxxx x!x ", + " x!!xxxxxxxxxx xx xx x!x ", + " xx!!!!!!!!!!xx xx xx x!x ", + " xxxxxxxxxx!!x x o o o x!x ", + " xx!x x o o xx!x ", + " x!x x xxxxxxxxxxxxxxx!!x ", + " xvx x x x !!!!!!!!!!!!!!xx ", + " xx | | | xx xxxxxxxxxxxxxxxxxxxxx ", + " xx!!!!!!!!!!!xx v ", + " xxxx!!!!!xxxx ", + " x x xxxxxxx xxx xxx ", + " x x x x x x ", + " x x x x ", + " x x xx x ", + " xx x x x ", + " x x o o x x x x ", + " xxxxxxx xxx xxx x x x x x x ", + " xx xx x x x x xxxxxx x x xxxxxxxxx x ", + " xx xx x o x x xx x x x x ", + " @ x x x x x x x x x x ", + " xxx x x x x x x x xxxxx xxxxxx x ", + " x x x x xx o xx x x x o x x x ", + "!!!!x x!!!!!!x x!!!!!!xx xx!!!!!!!!xx x!!!!!!!!!! x = x x x ", + "!!!!x x!!!!!!x x!!!!!xx xxxxxxxxxx x!!!!!!!xx! xxxxxxxxxxxxx xx o o xx ", + "!!!!x x!!!!!!x x!!!!!x o xx!!!!!!xx ! xx xx ", + "!!!!x x!!!!!!x x!!!!!x xx!!!!!!xx ! xxxxxxx ", + "!!!!x x!!!!!!x x!!!!!xx xxxxxxxxxxxxxx!!!!!!xx ! ", + "!!!!x x!!!!!!x x!!!!!!xxxxxxxxx!!!!!!!!!!!!!!!!!!xx ! ", + "!!!!x x!!!!!!x x!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!xx ! "], + [" ", + " ", + " ", + " ", + " ", + " o ", + " ", + " x ", + " x ", + " x ", + " x ", + " xxx ", + " x x !!! !!! xxx ", + " x x !x! !x! ", + " xxx xxx x x ", + " x x x oooo x xxx ", + " x x x x x!!!x ", + " x x xxxxxxxxxxxx xxx ", + " xx xx x x x ", + " x xxxxxxxxx xxxxxxxx x x ", + " x x x x!!!x ", + " x x x xxx ", + " xx xx x ", + " x x= = = = x xxx ", + " x x x x!!!x ", + " x x = = = =x o xxx xxx ", + " xx xx x x!!!x ", + " o o x x x x xxv xxx ", + " x x x x x!!!x ", + " xxx xxx xxx xxx o o x!!!!!!!!!!!!!!x vx ", + " x xxx x x xxx x x!!!!!!!!!!!!!!x ", + " x x xxxxxxxxxxxxxxxxxxxxxxx ", + " xx xx xxx ", + " xxx x x x x!!!x xxx ", + " x x x xxx x xxx x x ", + " x x xxx xxxxxxx xxxxx x ", + " x x x x x x ", + " x xx x x x x x ", + " x x |xxxx| |xxxx| xxx xxx x ", + " x xxx o o x x xxx x ", + " x xxxxx xx x xxx x!!!x x x ", + " x oxxxo x xxx x x x xxx xxx x ", + " x xxx xxxxxxxxxxxxx x oo x x oo x x oo xx xx xxx x ", + " x @ x x x!!x x!!!!x x!!!!x xx xx x x ", + " xxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ", + " ", + " "], + [" xxx x ", + " x ", + " xxxxx ", + " x ", + " x xxx ", + " o x x x ", + " o o oxxx x ", + " xxx x ", + " ! o ! xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx ", + " x x x x x x x x x x x x x x x ", + " x= o x x xxx x xxx x xxx x xxx x xxx x xxx x xxxxx ", + " x x x x x x x x x x x x x x x ", + " ! o ! o xxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxxxx ", + " ", + " o xxx xx ", + " ", + " ", + " xx ", + " xxx xxx ", + " ", + " o x x ", + " xx xx ", + " xxx xxx xxx x x ", + " ", + " || ", + " xxxxxxxxxxx ", + " x x o xxxxxxxxx o xxxxxxxxx o xx x ", + " x x x x x x x || x x ", + " x @ xxxxx o xxxxx o xxxxx ", + " xxxxxxx xxxxx xx xx xxx ", + " x= = =x x xxx ", + " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x!!!!!!!!!!!!!!!!!!!!!xxx!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", + " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + " "] +]; + + +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 +}; + +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, 3); + this.repeatPos = pos; + } +} +Lava.prototype.type = "Lava"; + +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 === "!") + fieldType = "lava"; + else if (ch === "|") + fieldType = "lava"; + else if (ch === "=") + fieldType = "lava"; + 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"; + 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); +}; + +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" && this.status == null) { + this.status = "lost"; + this.finishDelay = 1; + } else if (type == "coin") { + this.actors = this.actors.filter(function(other) { + return other != actor; + }); + if (!this.actors.some(function(actor) { + return actor.type == "coin"; + })) { + 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; + } + }); +} + +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); \ No newline at end of file diff --git a/166 - Retro Mario Game/style.css b/166 - Retro Mario Game/style.css new file mode 100644 index 00000000..d28f45d8 --- /dev/null +++ b/166 - Retro Mario Game/style.css @@ -0,0 +1,55 @@ +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; +} \ No newline at end of file