πŸ“œ ⬆️ ⬇️

Expressive JavaScript: Project: Platformer game

Content




Our whole life is a game.
Iyen Banks, Player

For the first time I was fascinated by computers, like most children, through computer games. I was drawn into the universe of simulated worlds that could be controlled and told stories - it seems to me more because they gave my imagination more space than because of the real possibilities that they provided.
')
No one would have wished a career as a game programmer. As in the music industry, the discrepancy between the number of young people who want to get there and the real demand for them creates an unhealthy environment. But writing games for fun is great.

In this chapter, we will explore the implementation of a simple platformer. In platformers (or β€œjump and run”), the player is required to move the figure in a (usually) two-dimensional world, which we see from the side, and often jump over different things.

A game


Our game will be roughly based on the game Dark Blue by Thomas Palef. I chose it because it is both entertaining and minimalist, and it can be done with a minimum of code. It looks like this:

The black box represents the player whose task is to collect yellow squares (coins), avoiding the red areas (lava?). The level ends when the player has collected all the coins.

The player can go left and right with the keys and jump up with the key. Jumping is a specialty of our character. He can jump several times higher than his height and change the direction of movement in the air. This is not very realistic, but it helps the player to feel complete control over his screen avatar.

The game has a fixed background in the form of a grid, where moving elements are superimposed on the background. Each grid cell is either empty, either a wall or lava. The moving elements are player, coins, and some pieces of lava. Unlike the life simulation from chapter 7, the positions of these elements are not tied to the grid. Their coordinates can be fractional, providing smooth movement.

Technology


We use the browser's DOM for graphics, and read user input by serving keyboard events.

The code relating to the screen and the keyboard is a small part of the work that we have to do to create the game. Since everything consists of colored squares, drawing is simple: we create DOM elements and use styles to set the background color, size and location.

We present the background as a table, since this is a fixed grid of squares. Freely moving elements can be superimposed on top using absolute positioning.

In games and other interactive programs with graphic animation, which must respond to user actions without delay, efficiency is very important. Although the DOM was not designed to produce high-speed graphics, it does it better than you would expect. In chapter 13, you saw a little animation. On a modern computer, such a simple game is going well, even if you don’t suffer much with optimization.

In the next chapter, we will explore another browser technology, a tag that provides a more traditional way to draw, and works with shapes and pixels instead of DOM elements.

Levels



In chapter 7, we used arrays of strings to describe a two-dimensional lattice. We can do the same here. This will allow us to develop levels without first writing a level editor.

A simple level might look like this:

var simpleLevelPlan = [ " ", " ", " x = x ", " xoox ", " x @ xxxxx x ", " xxxxx x ", " x!!!!!!!!!!!!x ", " xxxxxxxxxxxxxx ", " " ]; 


Fixed grid and moving elements included. The x indicates walls, spaces are empty spaces, and exclamation marks indicate fixed lava.

@ marks the place where the player starts. o - coins, equal sign = means a block of moving lava that moves horizontally to and fro. Note that the grid at these positions will contain empty space, and another data structure is used to track the position of these moving elements.

We will support two more types of lava: vertical bar | - for pieces moving vertically, and v for dripping lava. She will move down, but not bounce back, but simply jump to the starting position to reach the floor.

The game consists of several levels that must be completed. The level is completed when all the coins are collected. If a player touches lava, the current level returns to its original state, and the player starts over.

Reading level

The following constructor creates a level object. The argument must be an array of strings specifying the level.

 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"; gridLine.push(fieldType); } this.grid.push(gridLine); } this.player = this.actors.filter(function(actor) { return actor.type == "player"; })[0]; this.status = this.finishDelay = null; } 


For brevity, the code does not validate incoming data. He assumes that the level plan is acceptable, that there is a player’s starting position and other necessary things.

The level retains its width and height and two more arrays - one for the grid, and one for moving parts. A grid is represented by an array of arrays, where each nested array represents a horizontal line, and each square contains either null for empty squares, or a line reflecting the type of square - β€œwall” or β€œlava”.

The actors array contains objects that track the positions and states of dynamic elements. Each of them should have a pos property containing a position (coordinates of the upper left corner), a size property with a size, and a type property with a line describing its type ("lava", "coin" or "player").

After building the grid, we use the filter method to find the player object stored in the level property. The status property tracks whether a player has won or lost. When this happens, finishDelay is used, which keeps the level active for a while to show a simple animation. (Just immediately restore the state of the level or start the next one - it looks ugly). You can use this method to find out if the level is complete:

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


Actors (actors)


To store the position and size of our actors, we will return to our correct type, Vector, which groups the x and y coordinates into an object.

 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(factor) { return new Vector(this.x * factor, this.y * factor); }; 


The times method multiplies the scaled vector by the specified amount. It will be convenient when we need to multiply the velocity vector by the time interval to find out the distance traveled during this time.

In the previous section, the Level constructor used the actorChars object to associate characters with constructor functions. The object looks like this:

 var actorChars = { "@": Player, "o": Coin, "=": Lava, "|": Lava, "v": Lava }; 


Three characters refer to Lava. The Level constructor passes the original character of the actor as the second argument of the constructor, and the Lava constructor uses it to adjust its behavior (jump horizontally, jump vertically, drip).

The player type is built by the following constructor. It has a speed property that stores its current speed, which will help us simulate momentum and gravity.

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


Since a player is one and a half square tall, his starting position is set half a square above the position where the β€œ@” character is located. Thus its bottom coincides with the bottom of the square in which it appears.

When creating a dynamic Lava object, we need to initialize the object depending on the character. Dynamic lava moves at a given speed until it encounters an obstacle. Then, if she has a repeatPos property, she will jump back to the starting position (dripping). If not, it inverts the speed and continues to move in the opposite direction (bounces off). The constructor sets only the necessary properties. Later we will write a method that deals with the movement itself.

 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"; 


Coins are easy to implement. They just sit still. But to revive the game, they will tremble, slightly moving vertically back and forth. To track this, the coin object stores the main position along with the wobble property, which tracks the movement phase. Together they determine the position of the coin (stored in the pos property).
 function Coin(pos) { this.basePos = this.pos = pos.plus(new Vector(0.2, 0.1)); this.size = new Vector(0.6, 0.6); this.wobble = Math.random() * Math.PI * 2; } Coin.prototype.type = "coin"; 


In Chapter 13, we saw that Math.sin gives the y coordinate of a point on a circle. It moves back and forth in the form of a smooth wave while we move in a circle, which makes the sine function suitable for modeling wave motion.

To avoid the case when all the coins move synchronously, the initial phase of each will be random. The phase of the wave is Math.sin and the wave width is 2Ο€. We multiply the value returned by Math.random by this number to set the coin to a random starting position in the wave.

Now we have written everything that is needed to represent the level state

 var simpleLevel = new Level(simpleLevelPlan); console.log(simpleLevel.width, "by", simpleLevel.height); // β†’ 22 by 9 


We have to display these levels on the screen and simulate the time and movement inside them.

Encapsulation burden


In most cases, the code in this chapter does not care about encapsulation. First, encapsulation requires extra effort. Programs are getting bigger, they require more concepts and interfaces. And since the reader's eyes are too glassy because of too much code, I tried to keep the program small.

Secondly, the various elements of the game are so connected together that if the behavior of one of them changes, it is unlikely that the remaining code would remain unchanged. Creating interfaces between elements would lead to using too many assumptions about how the game works. And then they would be inefficient - by changing one part of the system, you would have to think about how it affects other parts, because their interfaces would not cover the new situation.

Some parts of the system can be well divided into pieces with strictly defined interfaces, while others are not. In attempts to encapsulate something that does not have clear boundaries, you are guaranteed to spend a lot of energy. Having made such an error, you will see that the interfaces become too large and detailed, and that they need to be changed frequently in the process of program evolution.

One thing we still encapsulate - the drawing subsystem. This is done specifically so that in the next chapter we can display the same game in a different way. By hiding the drawing for the interface, we can simply load the same program and connect a new display module to it.

Drawing


We will encapsulate the drawing code by entering a display object that displays the level on the screen. The type of screen that we define is called DOMDisplay, because it uses DOM elements to show the level.

We use a style sheet to define the colors and other fixed properties of the elements that make up the game. It would be possible to directly assign a style to an element through the style property when it was created, but in this case the program would become unnecessarily wordy.

The following helper function provides an easy way to create an element with a class assignment.

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


We create the screen by passing to it the parent element to which you want to connect, and the level object.

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


Using the fact that appendChild returns the added element, we create the surrounding wrapper element and store it in the wrap property.

Unchanged background level is drawn once. Actors are redrawn every time the screen is updated. The actorLayer property is used in drawFrame to track the element containing the actor β€” so that they can be easily removed and replaced.

Coordinates and dimensions are measured in units relative to the size of the grid, so that a distance of one means one element of the grid. When we set the dimensions in pixels, we will need to scale the coordinates - the game would be very small if one square were set by one pixel. The variable scale gives the number of pixels that one grid element occupies.

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


As we mentioned, the background is drawn through the element.
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
. , – ( ). (). CSS :

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>
 .     ,       –        ( ).         ().  CSS       : 

.background { background: rgb(52, 166, 251); table-layout: fixed; border-spacing: 0; } .background td { padding: 0; } .lava { background: rgb(255, 100, 100); } .wall { background: white; }


(table-layout, border-spacing padding) . , , .

background . CSS (white) rgb(R, G, B), , 0 255. , rgb(52, 166, 251) 52, 166 251. , . , .lava – .

DOM , . scale, .

DOMDisplay.prototype.drawActors = function() { var wrap = elt("div"); this.level.actors.forEach(function(actor) { var rect = wrap.appendChild(elt("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; };

, . CSS actor absolute. . lava, , .

.actor { position: absolute; } .coin { background: rgb(241, 229, 89); } .player { background: rgb(64, 64, 64); }

drawFrame , , . DOM , . DOM, . , .

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(); };

wrapper , - , . CSS, , .

.lost .player { background: rgb(160, 64, 64); } .won .player { box-shadow: -4px -7px 8px white, 4px -7px 8px white; }

-, . , .

, . scrollPlayerIntoView – , , , . CSS , , . relative, .

.game { overflow: hidden; max-width: 600px; max-height: 450px; position: relative; }

scrollPlayerIntoView . , scrollLeft scrollTop, .

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; };

, Vector , , . , ( ) . , , .

, . , , . – DOM . scrollLeft -10, 0.

– . . «» , , .

, .

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

.

<link rel="stylesheet" href="css/game.css"> <script> var simpleLevel = new Level(simpleLevelPlan); var display = new DOMDisplay(document.body, simpleLevel); </script>

rel="stylesheet" CSS. game.css .


– . , – , , ( ), ( ).

. , . , . , . , , .

. , Β« Β», . , .

, , , . – . – , .

, . , . – . , , .

, ( ) - .

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; } } };

, Math.floor Math.ceil . , – 11 . , , .


, β€œwall” β€œlava” . . , , , .

(, ) . , ( ).

, , :

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; } };


animate Level . step . keys , .

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; } };

status , null ( , ), finishDelay, , , .

while . , maxStep. , 0.12 0.05 0.02

act, , level keys. Lava, key:

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); };

, . , . , . repeatPos, . ( -1), .

act, . , , act .

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)); };

wobble , , Math.sin , .

. , , – . .

var playerXSpeed = 7; 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; };

«» «». , playerTouched, . .

, .

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; } };

, . , . , .

. , . «», ( , -, ), . . , - .

act :

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; } };

, , playerTouched, . actor, , playerTouched , .

, ( ), , - ( ), player.

, :

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; } } };

, β€œlost”. , , – β€œwon”. , . , .


, keypress. , , ( )

, , . preventDefault, .

, , , . "keydown" "keyup", , .

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; }

, . type , , true ("keydown") false ("keyup").


requestAnimationFrame, 13, . – , , requestAnimationFrame .

, , runAnimation, , . frame false, .

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); }

100 (1/10 ). , requestAnimationFrame , . , lastTime , . ( animate).

, , .

runLevel Level, display, , – . document.body . ( ), runLevel , , andThen, .

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; } }); }

– . , . , . , ( ) display

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 console.log("You win!"); }); } startLevel(0); }

. runAnimation runLevel – , , 5. , , - , . – . , , .

. – , , , – , 17, 20.

GAME_LEVELS . runGame, .

<link rel="stylesheet" href="css/game.css"> <body> <script> runGame(GAME_LEVELS, DOMDisplay); </script> </body>

. , .



, , . , .

runGame, . .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runGame – ... 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 console.log("You win!"); }); } startLevel(0); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>


Esc.

, runLevel, , Esc.

, runAnimation – runLevel, .

, -. . arrows – , , . , . trackKeys, runLevel, , .

<link rel="stylesheet" href="css/game.css"> <body> <script> // runLevel – ... 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; } }); } runGame(GAME_LEVELS, DOMDisplay); </script> </body>

Source: https://habr.com/ru/post/244405/


All Articles