表現力豊かなJavaScript:プロジェクト:プラットフォームゲーム

内容







私たちの人生はゲームです。

Iyen Banks、プレイヤー



初めて、コンピューターゲームを通じて、ほとんどの子供たちと同様にコンピューターに興味を持つようになりました。 制御可能なシミュレーション世界の世界に引き込まれ、物語が語られました-彼らが提供した本当の可能性よりも、想像力の範囲が与えられたからです。



ゲームプログラマーとしてのキャリアを希望しません。 音楽業界のように、そこに行きたい若者の数と彼らに対する本当の需要との不一致が不健康な環境を作り出しています。 しかし、楽しみのためにゲームを書くのは素晴らしいことです。



この章では、単純なプラットフォーマーの実装について説明します。 プラットフォーマー(または「ジャンプして実行」)では、プレイヤーは、側面から見える(通常)2次元の世界を横切ってフィギュアを移動し、多くの場合異なるものを飛び越える必要があります。



ゲーム



私たちのゲームは、トーマス・パレフによるダークブルーのゲームにほぼ基づいています。 面白くてミニマルであり、最小限のコードで作成できるため、私はそれを選んだ。 次のようになります。



黒い長方形は、赤いセクション(溶岩?)を避けて、黄色い正方形(コイン)を収集するタスクを持つプレーヤーを表します。 プレイヤーがすべてのコインを収集すると、レベルは終了します。



プレーヤーはキーを左右に動かして、ジャンプすることができます。 ジャンプはキャラクターの得意分野です。 彼は自分の身長よりも数倍高くジャンプし、空中での移動方向を変えることができます。 これはあまり現実的ではありませんが、プレーヤーが画面上のアバターを完全にコントロールできるようになります。



ゲームにはグリッドの形で固定された背景があり、移動する要素が背景に重ねられます。 各グリッドセルは空、壁、または溶岩です。 動く要素は、プレイヤー、コイン、溶岩の一部です。 第7章の生命のシミュレーションとは異なり、これらの要素の位置はグリッドに関連付けられていません。 それらの座標は分数であり、スムーズな動きを提供します。



技術



グラフィックにはブラウザーDOMを使用し、ユーザー入力を読み取り、キーボードイベントを処理します。



画面とキーボードに関連するコードは、ゲームを作成するために必要な作業のほんの一部です。 すべてが色付きの正方形で構成されているため、描画は簡単です。DOM要素を作成し、スタイルを使用して背景色、サイズ、および場所を設定します。



背景は一定の正方形のグリッドであるため、背景を表として提示します。 自由に移動する要素は、絶対配置を使用して上部に重ね合わせることができます。



ユーザーのアクションに遅滞なく応答するグラフィックアニメーションを備えたゲームやその他のインタラクティブプログラムでは、効率が非常に重要です。 DOMは高速グラフィックスを表示するようには設計されていませんが、これは予想以上にうまく処理されます。 第13章では、少しアニメーションを見ました。 現代のコンピューターでは、最適化についてそれほど心配する必要がない場合でも、このようなシンプルなゲームはうまくいきます。



次の章では、別のブラウザテクノロジー、つまり、より伝統的な描画方法を提供し、DOM要素の代わりにシェイプとピクセルを操作するタグを検討します。



レベル





第7章では、文字列配列を使用して2次元格子を記述しました。 ここでも同じことができます。 これにより、最初にレベルエディタを作成せずにレベルを開発できます。



単純なレベルは次のようになります。



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







固定グリッドと移動要素が含まれています。 xは壁を表し、スペースは空きスペースを表し、感嘆符は固定溶岩を表します。



@は、プレーヤーが開始する場所を示します。 o-コイン、等号=移動する溶岩のブロックを意味し、前後に水平に移動します。 これらの位置のラティスには空のスペースが含まれ、別のデータ構造がこれらの移動要素の位置を追跡するために使用されることに注意してください。



さらに2種類の溶岩をサポートします。垂直線| -垂直に移動する破片の場合、およびv溶岩を滴下する場合。 彼女は下に移動しますが、跳ね返るのではなく、床に着いたら開始位置にジャンプします。



ゲームは、完了する必要があるいくつかのレベルで構成されています。 すべてのコインが収集されると、レベルが完了します。 プレイヤーが溶岩に触れると、現在のレベルは元の状態に戻り、プレイヤーは再び起動します。



読解レベル



次のコンストラクターは、レベルオブジェクトを作成します。 引数は、レベルを指定する文字列の配列でなければなりません。



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







簡潔にするために、コードは受信データをチェックしません。 彼は、レベル計画が受け入れ可能であること、プレーヤーの開始位置と他の必要なものがあることを提案します。



レベルは、幅と高さ、さらに2つのアレイを保持します-1つはグリル用、もう1つは可動部用です。 ラティスは配列の配列です。ネストされた各配列は水平線を表し、各正方形には空の正方形の場合はヌル、または正方形のタイプを反映する文字列(「壁」または「溶岩」)が含まれます。



アクター配列には、動的要素の位置と状態を追跡するオブジェクトが含まれます。 それぞれには、位置(左上隅の座標)を含むposプロパティ、サイズを含むサイズプロパティ、およびタイプ(「lava」、「coin」、または「player」)を説明する行を含むtypeプロパティが必要です。



ラティスを構築した後、フィルターメソッドを使用して、levelプロパティに格納されているプレーヤーオブジェクトを見つけます。 statusプロパティは、プレーヤーが勝ったか負けたかを追跡します。 これが発生すると、finishDelayが使用されます。これにより、レベルがしばらくアクティブになり、単純なアニメーションが表示されます。 (ただちにレベルの状態を復元するか、次のレベルを開始します-見苦しいです) このメソッドは、レベルが完全であるかどうかを調べるために使用できます。



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







俳優(俳優)



アクターの位置とサイズを保存するために、x座標とy座標をオブジェクトにグループ化する真のVectorタイプに戻ります。



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







timesメソッドは、指定された量でベクトルを乗算します。 この時間内に移動した距離を見つけるために、速度ベクトルに時間間隔を掛ける必要がある場合に便利です。



前のセクションでは、LevelコンストラクターはactorCharsオブジェクトを使用して、文字をコンストラクター関数に関連付けました。 オブジェクトは次のようになります。



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







3つの文字は溶岩を指します。 Levelコンストラクターは、アクターの元の文字を2番目の引数としてコンストラクターに渡し、Lavaコンストラクターはそれを使用して動作を調整します(水平方向にジャンプ、垂直方向にジャンプ、点滴)。



プレーヤータイプは、次のコンストラクターによって構築されます。 現在の速度を保存する速度プロパティがあり、運動量と重力をシミュレートするのに役立ちます。



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







プレーヤーの身長は1.5平方なので、彼の初期位置は、「@」記号が配置されている位置の半分上に設定されます。 したがって、その底部は、それが現れる正方形の底部と一致します。



動的なLavaオブジェクトを作成するときは、キャラクターに応じてオブジェクトを初期化する必要があります。 動的溶岩は、障害物に遭遇するまで所定の速度で移動します。 次に、彼女にrepeatPosプロパティがある場合、開始位置に戻ります(滴下)。 そうでない場合は、速度を反転させ、反対方向に移動し続けます(バウンス)。 コンストラクターは、必要なプロパティのみを設定します。 後で、ムーブメント自体を扱うメソッドを作成します。



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







コインは簡単に実装できます。 彼らはただじっと座っています。 しかし、ゲームを復活させるために、彼らは震えて、わずかに垂直に前後に動きます。 これを追跡するために、コインオブジェクトはメインポジションとウォブルプロパティを保存し、動きのフェーズを追跡します。 一緒に、コインの位置を決定します(posプロパティに格納されます)。

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







第13章では、Math.sinが円上の点のy座標を与えることを見ました。 円の中を移動しながら滑らかな波の形で前後に移動するため、正弦関数は波の動きのモデリングに適しています。



すべてのコインが同期して移動する場合を回避するため、それぞれの初期段階はランダムになります。 波の位相はMath.sinで、波長は2πです。 Math.randomによって返された値にこの数を掛けて、コインに波の中でランダムな初期位置を与えます。



これで、レベルの状態を表すために必要なすべてを記述しました。



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







これらのレベルを画面に表示し、それらの中の時間と動きをシミュレートする必要があります。



カプセル化の負担



ほとんどの場合、この章のコードはカプセル化を気にしません。 まず、カプセル化には余分な労力が必要です。 プログラムは大きくなり、より多くの概念とインターフェースが必要になります。 そして、読者の目があまりにも多くのコードから目を向けるので、私はプログラムを小さく保つようにしました。



第二に、ゲームのさまざまな要素が非常に結びついているため、それらの1つの動作が変更された場合、残りのコードが変更されない可能性はほとんどありません。 要素間にインターフェイスを作成すると、ゲームの仕組みについて多くの仮定を使用することになります。 そして、それらは効果的ではありません-システムの一部を変更すると、他の部分にどのように影響するかを考える必要があります。それらのインターフェースは新しい状況をカバーしないからです。



システムの一部の部分は、厳密に定義されたインターフェイスで細かく分割するのに適していますが、そうでない部分もあります。 明確な境界を持たないものをカプセル化しようとすると、多くのエネルギーを消費することが保証されます。 このような間違いを犯すと、インターフェイスが大きくなりすぎて詳細になり、プログラムの進化中に頻繁に変更しなければならないことがわかります。



私たちはまだ一つのことをカプセル化しています-描画サブシステム。 これは特に、次の章で同じゲームを異なる方法で表示できるようにするために行われます。 インターフェースの背後に図面を隠したので、同じプログラムをダウンロードして、新しい画面出力モジュールを接続するだけです。



描画



画面にレベルを表示する表示オブジェクトを導入して、描画用のコードをカプセル化します。 定義する画面のタイプは、DOM要素を使用してレベルを表示するため、DOMDisplayと呼ばれます。



スタイルシートを使用して、ゲームを構成する要素の色やその他の固定プロパティを設定します。 作成時にスタイルプロパティを介して要素にスタイルを直接割り当てることは可能ですが、この場合のプログラムは不必要に冗長になります。



次のヘルパー関数を使用すると、クラスを割り当てて要素を簡単に作成できます。



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







接続する親要素とレベルオブジェクトを渡すことで画面を作成します。



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







appendChildが追加された要素を返すという事実を使用して、周囲のラッパー要素を作成し、それをwrapプロパティに保存します。



一定レベルの背景が1回描画されます。 アクターは、画面が更新されるたびに再描画されます。 actorLayerプロパティはdrawFrameで使用され、アクターを含む要素を追跡します。これにより、アクターを簡単に削除および置換できます。



座標と寸法は、グリッドのサイズを基準にした単位で測定されるため、1つの距離はグリッドの1つの要素を意味します。 ピクセル単位で寸法を設定する場合、座標をスケーリングする必要があります。1つのピクセルで1つの正方形を指定すると、ゲームは非常に浅くなります。 スケール変数は、1つのグリッド要素が占めるピクセル数を示します。



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







すでに述べたように、背景は要素を通して描かれます
 .     ,       –        ( ).         ().  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>






















All Articles