衚珟力豊かな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>

Source: https://habr.com/ru/post/J244405/


All Articles