diff --git a/2048.html b/2048.html new file mode 100644 index 0000000..99f1c9a --- /dev/null +++ b/2048.html @@ -0,0 +1,24 @@ + + + Mainpage + + + Go Home + + + + + diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..b435fbd --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +www.xenongaming.xyz diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1c4172e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Teddy Cross + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd203c8 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +GET OFF MEH GITHUB diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..bd5dfe9 --- /dev/null +++ b/TODO.md @@ -0,0 +1,8 @@ +* Redo turret targeting. +* Confirm selling turrets. +* Optimize collision detection/tiling, e.g. only check edges, + more. +* Better icon and promo graphics. +* Audio effects. +* Random map generator. +* Canvas TD Server. +* Offline support. diff --git a/breakout.html b/breakout.html new file mode 100644 index 0000000..6b4933d --- /dev/null +++ b/breakout.html @@ -0,0 +1,245 @@ + + + + Basic Breakout HTML Game + + + + + + + + diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..13c55d6 --- /dev/null +++ b/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# $ bash build.sh outdir ga-id + +SRCDIR="$( cd "$( dirname "$0" )" && pwd )" +OUTDIR=$1 +GAID=$2 + +rm -rf $OUTDIR +cp -R $SRCDIR $OUTDIR +cd $OUTDIR + +mv images/favicon.ico favicon.ico + +sed -i '' -e "s/##GAID##/$GAID/" index.html +sed -i '' -e '/\.js"><\/script>/d' -e 's/ - - - + +
+ + Mainpage + + + + Play Space Invaders + diff --git a/mario.html b/mario.html new file mode 100644 index 0000000..3f5e89e --- /dev/null +++ b/mario.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + +
SCORE
0
COINS
0
WORLD
1-1
TIME
283
LIVES
2

super

MARIO HTML5

Arrow/WASD keys move
Shift to fire/sprint
P/M to pause/mute

diff --git a/pacman.html b/pacman.html new file mode 100644 index 0000000..bd3afde --- /dev/null +++ b/pacman.html @@ -0,0 +1,1356 @@ + + + + + + + Mainpage + + + Go Home + + + Pacman + + + + +
shim for font face
+
+ + + + + + diff --git a/papas.html b/papas.html new file mode 100644 index 0000000..168b4dc --- /dev/null +++ b/papas.html @@ -0,0 +1 @@ +
Play more games at Flipline Studios diff --git a/pong.html b/pong.html new file mode 100644 index 0000000..1e05704 --- /dev/null +++ b/pong.html @@ -0,0 +1,222 @@ + + + + + Mainpage + + + Go Home + + + WASD for player 1, Arrow keys for player2 + + + + Pong + + + + + + + + diff --git a/scripts/defs.js b/scripts/defs.js new file mode 100644 index 0000000..941ad72 --- /dev/null +++ b/scripts/defs.js @@ -0,0 +1,269 @@ +var Defs = {}; + + +/////////////////////////////////////////////////////////////////////////////// +// Maps +/////////////////////////////////////////////////////////////////////////////// +Defs.maps = { + Loopy: [ + { x: 0, y: 70 }, + { x: 730, y: 70 }, + { x: 730, y: 430 }, + { x: 70, y: 430 }, + { x: 70, y: 160 }, + { x: 640, y: 160 }, + { x: 640, y: 340 }, + { x: 160, y: 340 }, + { x: 160, y: 250 }, + { x: 800, y: 250 } + ], + + Backtrack: [ + { x: 0, y: 170 }, + { x: 120, y: 170 }, + { x: 120, y: 415 }, + { x: 460, y: 415 }, + { x: 460, y: 185 }, + { x: 230, y: 185 }, + { x: 230, y: 70 }, + { x: 345, y: 70 }, + { x: 345, y: 300 }, + { x: 700, y: 300 }, + { x: 700, y: 0 } + ], + + Dash: [ + { x: 0, y: 250 }, + { x: 800, y: 250 } + ] +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Turrets +/////////////////////////////////////////////////////////////////////////////// +Defs.turrets = {}; + +Defs.turrets.upgrades = [25, 40, 75, 150, 250, 400, 500, 700, 900, 1000]; + +Defs.turrets.Laser = { + cost: 15, + damage: 10, + rate: 40, + range: 80, + upgrades: [ + { damage: 15, rate: 38, range: 85 }, + { damage: 25, rate: 36, range: 90 }, + { damage: 50, rate: 34, range: 95 }, + { damage: 75, rate: 32, range: 100 }, + { damage: 100, rate: 30, range: 105 }, + { damage: 150, rate: 28, range: 110 }, + { damage: 200, rate: 26, range: 120 }, + { damage: 400, rate: 25, range: 130 }, + { damage: 600, rate: 24, range: 140 }, + { damage: 1000, rate: 22, range: 160 } + ], + shoot: function (creeps) { + var creep = creeps[0]; + var _hp = creep.hp; + var turret = this; + + if ((creep.hp -= turret.damage) <= 0 && _hp > 0) { + turret.kills++; + } + + if (turret.levels.full && Math.rand(9) === 0) { + var start = game.map[0]; + creep.x = start.x; + creep.y = start.y; + creep.nextpoint = 0; + } + + game.run.push({ what: function () { + canvas.lineCap = "round"; + canvas.lineWidth = 2; + canvas.strokeStyle = "#EE82EE"; + canvas.beginPath(); + canvas.moveTo(turret.x, turret.y); + canvas.lineTo(creep.x, creep.y); + canvas.stroke(); + }, until: 6 }); + } +}; + +Defs.turrets.Missile = { + cost: 25, + damage: 15, + rate: 60, + range: 120, + upgrades: [ + { damage: 20, rate: 57, range: 125 }, + { damage: 30, rate: 54, range: 130 }, + { damage: 40, rate: 51, range: 135 }, + { damage: 80, rate: 48, range: 140 }, + { damage: 120, rate: 45, range: 145 }, + { damage: 220, rate: 42, range: 150 }, + { damage: 320, rate: 40, range: 160 }, + { damage: 450, rate: 38, range: 170 }, + { damage: 600, rate: 36, range: 180 }, + { damage: 800, rate: 33, range: 200 } + ], + cell: 0, + shoot: function (creeps) { + var creep = creeps[Math.rand(creeps.length - 1)]; + var cell = this.cell % 4; + var missile = { x: this.x + (cell % 2 === 0 ? -5 : 5), y: this.y + (cell < 2 ? -5 : 5) }; + var turret = this; + + game.run.push({ what: function () { + if (creep.hp <= 0) { + var creeps = game.creeps.filter(function () { return true; }); + + if (creeps.length) { + creep = creeps[Math.rand(creeps.length - 1)]; + } else { + return false; + } + } + + if (Math.move(missile, creep, 3)) { + if (turret.levels.full) { + game.creeps.forEach(function (c) { + if (Math.inRadius(creep, c, 20)) { + var _hp = c.hp; + if ((c.hp -= turret.damage) <= 0 && _hp > 0) { + turret.kills++; + } + } + }); + + game.run.push({ what: function () { + canvas.fillStyle = "#FF0"; + canvas.beginPath(); + canvas.moveTo(creep.x, creep.y); + canvas.arc(creep.x, creep.y, 20, 0, Math.PI * 2, true); + canvas.fill(); + }, until: 3 }); + } else { + var _hp = creep.hp; + if ((creep.hp -= turret.damage) <= 0 && _hp > 0) { + turret.kills++; + } + } + + return false; + } else { + canvas.fillStyle = "#FFF"; + canvas.fillRect(missile.x - 2, missile.y - 2, 4, 4); + } + }, until: Infinity }); + + turret.cell++; + } +}; + +Defs.turrets.Tazer = { + cost: 40, + damage: 1, + rate: 40, + range: 60, + upgrades: [ + { damage: 5, rate: 38, range: 62 }, + { damage: 10, rate: 36, range: 64 }, + { damage: 15, rate: 34, range: 66 }, + { damage: 25, rate: 32, range: 68 }, + { damage: 50, rate: 30, range: 70 }, + { damage: 100, rate: 29, range: 75 }, + { damage: 200, rate: 28, range: 80 }, + { damage: 300, rate: 27, range: 85 }, + { damage: 400, rate: 26, range: 90 }, + { damage: 500, rate: 24, range: 100 } + ], + shoot: function (creeps) { + var creep = creeps.sort(function (a, b) { return b.speed - a.speed; })[0]; + var _hp = creep.hp; + var turret = this; + var speed = 0.9 - (turret.damage / 1000); + var slowfor = 60 + turret.damage; + + if ((creep.hp -= turret.damage) <= 0 && _hp > 0) { + turret.kills++; + } + + creep.speed = creep.speed > speed ? speed : creep.speed; + creep.slowfor = turret.levels.full ? Infinity : (creep.slowfor < slowfor ? slowfor : creep.slowfor); + + game.run.push({ what: function () { + canvas.lineCap = "round"; + canvas.lineWidth = 3; + canvas.strokeStyle = "#00F"; + canvas.beginPath(); + canvas.moveTo(turret.x, turret.y); + canvas.lineTo(creep.x, creep.y); + canvas.stroke(); + canvas.strokeStyle = "#FFF"; + canvas.lineWidth = 2; + canvas.beginPath(); + canvas.moveTo(turret.x, turret.y); + canvas.lineTo(creep.x, creep.y); + canvas.stroke(); + }, until: 6 }); + } +}; + +Defs.turrets.Mortar = { + cost: 60, + damage: 50, + rate: 120, + range: 200, + upgrades: [ + { damage: 75, rate: 115, range: 205 }, + { damage: 100, rate: 110, range: 210 }, + { damage: 150, rate: 105, range: 215 }, + { damage: 250, rate: 100, range: 220 }, + { damage: 400, rate: 96, range: 225 }, + { damage: 600, rate: 92, range: 230 }, + { damage: 800, rate: 88, range: 235 }, + { damage: 1000, rate: 84, range: 240 }, + { damage: 1200, rate: 80, range: 245 }, + { damage: 1500, rate: 75, range: 250 } + ], + shoot: function (creeps) { + var creep = creeps[0]; + var turret = this; + var target = { x: creep.x / 1, y: creep.y / 1 }; + var shell = { x: turret.x / 1, y: turret.y / 1 }; + var radius = 25 + (turret.damage / 150); + + game.run.push({ what: function () { + if (Math.move(shell, target, 1.5)) { + game.creeps.forEach(function (creep) { + if (Math.inRadius(creep, target, radius)) { + var _hp = creep.hp; + + if ((creep.hp -= turret.damage) <= 0 && _hp > 0) { + turret.kills++; + } + + if (turret.levels.full && !creep.burning) { + creep.burning = turret; + } + } + }); + + game.run.push({ what: function () { + canvas.fillStyle = "#FF0"; + canvas.beginPath(); + canvas.moveTo(target.x, target.y); + canvas.arc(target.x, target.y, radius, 0, Math.PI * 2, true); + canvas.fill(); + }, until: 3 }); + + return false; + } else { + canvas.fillStyle = "#808080"; + canvas.fillRect(shell.x - 3, shell.y - 3, 6, 6); + } + }, until: Infinity }); + } +}; \ No newline at end of file diff --git a/scripts/game.js b/scripts/game.js new file mode 100644 index 0000000..13530d5 --- /dev/null +++ b/scripts/game.js @@ -0,0 +1,230 @@ +var game = { + ticks: 0, + _ticks: 0, + _tick: 0, + ticker: -1, + run: [], + fast: false, + paused: true, + + wave: 0, + _wave: 0, + + creeps: [], + hp: 1, + hpinc: 1.3, + lives: 10, + + turrets: [], + spent: 0, + kills: 0, + cash: 35, + selection: false, + + tiles: {}, + + tick: function () { + /////////////////////////////////////////////////////////////////////////////// + // fps + /////////////////////////////////////////////////////////////////////////////// + if (game.ticks - game._ticks === 60) { + ui.fps.textContent = Math.round(60000 / (Date.now() - game._tick)); + game._tick = Date.now(); + game._ticks = game.ticks; + } + + + /////////////////////////////////////////////////////////////////////////////// + // wave + /////////////////////////////////////////////////////////////////////////////// + if ((game.ticks - game._wave) % 30 === 29) { + ui.timer.style.opacity = 1 - (((game.ticks - game._wave) / 60) * 0.05); + } + + if (game._wave + 1200 === game.ticks) { + ui.wave.textContent = ++game.wave; + + game.hpinc = { 10: 1.2, 25: 1.1, 50: 1.06, 100: 1.04, 150: 1.02, 200: 1.01 }[game.wave] || game.hpinc; + game.hp *= game.hpinc; + + for (var i = 1; i <= 10; i++) { + game.creeps.push({ + x: -(i * 20) - 10, + y: game.map[0].y, + offset: Math.rand(14), + nextpoint: 0, + speed: 1, + slowfor: 0, + hp: game.hp, + _hp: game.hp, + burning: false, + cash: game.wave + }); + } + + game._wave = game.ticks; + } + + + /////////////////////////////////////////////////////////////////////////////// + // map + /////////////////////////////////////////////////////////////////////////////// + canvas.fillStyle = "#000"; + canvas.fillRect(0, 0, 800, 500); + + var map = game.map.slice(1), start = game.map[0]; + canvas.lineWidth = 40; + canvas.strokeStyle = "#00F"; + canvas.beginPath(); + canvas.moveTo(start.x, start.y); + map.forEach(function (cur, i) { + canvas.lineTo(cur.x, cur.y); + }); + canvas.stroke(); + canvas.lineWidth = 30; + canvas.strokeStyle = "#004"; + canvas.beginPath(); + canvas.moveTo(start.x, start.y); + map.forEach(function (cur, i) { + canvas.lineTo(cur.x, cur.y); + }); + canvas.stroke(); + + + /////////////////////////////////////////////////////////////////////////////// + // creeps + /////////////////////////////////////////////////////////////////////////////// + game.creeps.forEach(function (creep, i, a) { + var _hp = creep.hp; + var burning = creep.burning; + + if (burning) { + creep.hp -= 30; + } + + if (creep.hp <= 0) { + if (_hp > 0) { + burning.kills++; + } + + game.kills++; + game.cash += creep.cash; + + delete a[i]; + + ui.action.refresh(); + } else if (creep.nextpoint === game.map.length) { + delete a[i]; + + ui.lives.textContent = --game.lives; + + if (!game.lives) { + game.end(); + } + } else { + if (--creep.slowfor <= 0) { + creep.speed = 1; + } + + var waypoint = game.map[creep.nextpoint]; + var hue = (creep.speed < 1 || burning) ? (burning ? (creep.speed < 1 ? 300 : 33) : 240) : 0; + var sat = 100 * (creep.hp / creep._hp); + + if (Math.move(creep, { x: waypoint.x - 7 + creep.offset, y: waypoint.y - 7 + creep.offset }, creep.speed)) { + creep.nextpoint++; + } + + canvas.fillStyle = "hsl(" + hue + "," + sat + "%,50%)"; + canvas.fillRect(creep.x - 5, creep.y - 5, 10, 10); + } + }); + + + /////////////////////////////////////////////////////////////////////////////// + // turrets + /////////////////////////////////////////////////////////////////////////////// + game.turrets.forEach(function (turret) { + if (turret.lastshot + turret.rate <= game.ticks) { + var creeps = game.creeps.filter(function (creep) { + return Math.inRadius(creep, turret, turret.range); + }); + + if (creeps.length > 0) { + turret.shoot(creeps); + turret.lastshot = game.ticks; + } + } + + canvas.drawImage(turret.img, turret.x - 12.5, turret.y - 12.5); + }); + + var selection = game.selection; + var turret = selection.turret; + if (selection) { + canvas.beginPath(); + canvas.fillStyle = selection.status === "selected" || selection.placeable ? "rgba(255, 255, 255, .3)" : "rgba(255, 0, 0, .3)"; + canvas.arc(turret.x, turret.y, turret.range, 0, Math.PI * 2, true); + canvas.fill(); + + canvas.drawImage(turret.img, turret.x - 12.5, turret.y - 12.5); + } + + + /////////////////////////////////////////////////////////////////////////////// + // finish + /////////////////////////////////////////////////////////////////////////////// + game.run.forEach(function (something, i, a) { + if (something.what() === false || --something.until === 0) { + delete a[i]; + } + }); + + game.ticks++; + }, + start: function () { + game._ticks = game.ticks; + game._tick = Date.now(); + game.ticker = window.setInterval(game.tick, 1000 / (game.fast ? 180 : 60)); + game.paused = false; + game.tick(); + }, + pause: function () { + window.clearInterval(game.ticker); + game.paused = true; + }, + end: function () { + game.pause(); + document.removeEventListener("keydown", ui.handleshortcuts, false); + window.removeEventListener("beforeunload", ui.handleunload, false); + + var map = game.map.name; + var kills = game.kills; + var spent = game.spent; + var score = kills * spent; + var text = score + " (" + kills + " kills, $" + spent + " spent)"; + var top = JSON.parse(localStorage.scores || '{"Loopy":[],"Backtrack":[],"Dash":[]}'); + var topmap = top[map]; + + if (score > (topmap.length === 5 && topmap[4].score)) { + topmap.splice(4, 1); + topmap.push({ score: score, kills: kills, spent: spent, date: Date.now() }); + topmap.sort(function (a, b) { return b.score - a.score; }); + localStorage.scores = JSON.stringify(top); + ui.action.scores(); + } + + $("control-score-text").textContent = text; + $("control-score-tweet").setAttribute("href", + "https://twitter.com/?status=" + window.encodeURIComponent("I scored " + text + " on " + map + " in #canvastd http://canvas-td.tkaz.ec/")); + + ui.panel("score"); + $("pages-overlay").style.display = "block"; + + _gaq.push(["_trackEvent", "Game", "End", map]); + _gaq.push(["_trackEvent", "Game", "Creeps killed", map, kills]); + _gaq.push(["_trackEvent", "Game", "Money spent", map, spent]); + _gaq.push(["_trackEvent", "Game", "Money available", map, game.cash]); + _gaq.push(["_trackEvent", "Game", "Turrets placed", map, game.turrets.length]); + _gaq.push(["_trackEvent", "Game", "Last FPS", map, Number(ui.fps.textContent)]); + } +}; \ No newline at end of file diff --git a/scripts/ui.js b/scripts/ui.js new file mode 100644 index 0000000..bc52be8 --- /dev/null +++ b/scripts/ui.js @@ -0,0 +1,429 @@ +/////////////////////////////////////////////////////////////////////////////// +// Global +/////////////////////////////////////////////////////////////////////////////// +document.addEventListener("dragstart", function (evt) { + if (evt.target.tagName === "IMG") { + evt.preventDefault(); + } +}, false); + +ui.bind("click", document.querySelectorAll("[data-page]"), function (evt) { + ui.page(this.getAttribute("data-page")); +}); + +ui.handleshortcuts = function (evt) { + if (!game.paused) { + switch (evt.keyCode) { + case 49: { + if (game.selection) { + ui.action.upgrade("damage"); + } else { + ui.action.build("Laser"); + } + break; + } + case 50: { + if (game.selection) { + ui.action.upgrade("rate"); + } else { + ui.action.build("Missile"); + } + break; + } + case 51: { + if (game.selection) { + ui.action.upgrade("range"); + } else { + ui.action.build("Tazer"); + } + break; + } + case 52: { + if (game.selection) { + ui.action.move(); + } else { + ui.action.build("Mortar"); + } + break; + } + case 56: { + if (evt.shiftKey && game.selection) { + ui.action.sell(); + } + break; + } + case 187: { + $("control-fast").click(); + break; + } + case 27: { + if (game.selection) { + ui.action.deselect(); + } else { + $("control-pause").click(); + } + break; + } + case 13: { + game._wave = game.ticks - 1200; + break; + } + } + } else { + if (evt.keyCode === 27) { + $("control-pause").click(); + } + } +}; + +ui.handleunload = function (e) { + return "A game is currently running, are you sure you want to close it?"; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Actions +/////////////////////////////////////////////////////////////////////////////// +ui.action.scores = function () { + var list = JSON.parse(localStorage.scores || "{}"); + + for (var map in list) { + var out = ""; + + list[map].forEach(function (r) { + out += '
  • ' + + '' + new Date(r.date).toDateString() + ' ' + + r.score + ' ☠' + r.kills + ' $' + r.spent + + '
  • '; + }); + + $("pages-scores-local-" + map.toLowerCase()).innerHTML = out; + } +}; + +ui.action.build = function (type) { + var tdata = Defs.turrets[type]; + var turret = { + x: -1000, + y: -1000, + levels: { + range: 0, + rate: 0, + damage: 0, + full: false + }, + kills: 0, + lastshot: 0, + img: document.querySelector("#control-turrets [data-name=" + type + "] img"), + id: game.turrets.length + }; + + for (var k in tdata) { + turret[k] = tdata[k]; + } + + game.selection = game.cash - tdata.cost >= 0 ? { + status: "placing", + turret: turret, + placeable: false + } : false; +}; + +ui.action.upgrade = function (stat) { + var turret = game.selection.turret; + var levels = turret.levels; + var level = levels[stat]; + var cost = Defs.turrets.upgrades[level]; + + if (game.selection.status === "selected" && cost && game.cash - cost >= 0) { + levels[stat]++; + turret[stat] = turret.upgrades[level][stat]; + levels.full = levels.damage === 10 && levels.rate === 10 && levels.range === 10; + turret.cost += cost; + game.cash -= cost; + game.spent += cost; + ui.action.refresh(); + } +}; + +ui.action.move = function () { + if (game.selection.status === "selected" && game.cash - 90 >= 0) { + var turret = game.selection.turret; + + game.selection = { + status: "moving", + turret: turret, + placeable: true + }; + + turret._x = turret.x; + turret._y = turret.y; + + var tx = (turret._x + 2.5) / 5, ty = (turret._y + 2.5) / 5; + for (var i = 5; i--;) { + for (var ii = 5; ii--;) { + game.tiles[(tx + i - 2) + "," + (ty + ii - 2)] = false; + } + } + + delete game.turrets[turret.id]; + } +}; + +ui.action.sell = function () { + var turret = game.selection.turret, value = Math.round(turret.cost * 0.7); + game.cash += value; + game.spent -= value; + + var tx = (turret.x + 2.5) / 5, ty = (turret.y + 2.5) / 5; + for (var i = 5; i--;) { + for (var ii = 5; ii--;) { + game.tiles[(tx + i - 2) + "," + (ty + ii - 2)] = false; + } + } + + ui.panel("turrets"); + game.selection = false; + delete game.turrets[turret.id]; + ui.action.refresh(); +}; + +ui.action.refresh = function () { + ui.cash.textContent = game.cash; + + if (game.selection) { + var turret = game.selection.turret; + var levels = turret.levels; + var costs = Defs.turrets.upgrades; + + ["Damage", "Rate", "Range"].forEach(function (proper) { + var id = proper.toLowerCase(); + var level = levels[id]; + var cost = costs[level] || ""; + $("control-manage-" + id).innerHTML = proper + " (" + level + ")
    " + (cost && "$" + cost); + }); + + $("control-manage-sell").innerHTML = "Sell
    $" + Math.round(turret.cost * 0.7); + + $("control-manage-stats").innerHTML = turret.kills + " kills
    " + (((turret.kills / game.kills) || 0) * 100).toFixed(2) + "% of ∑"; + } +}; + +ui.action.deselect = function () { + if (game.selection.status === "moving") { + var turret = game.selection.turret; + game.turrets[turret.id] = turret; + + turret.x = turret._x; + turret.y = turret._y; + + var tx = (turret.x + 2.5) / 5, ty = (turret.y + 2.5) / 5; + for (var i = 5; i--;) { + for (var ii = 5; ii--;) { + game.tiles[(tx + i - 2) + "," + (ty + ii - 2)] = turret; + } + } + } + + game.selection = false; + + ui.panel("turrets"); +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Canvas +/////////////////////////////////////////////////////////////////////////////// +var canvas = $("pages-canvas").getContext("2d"); + +$("pages-canvas").addEventListener("mousemove", function (evt) { + var selection = game.selection; + var turret = selection.turret; + + if (selection && selection.status !== "selected") { + var tx = Math.ceil((evt.pageX - this.offsetLeft) / 5); + var ty = Math.ceil((evt.pageY - this.offsetTop) / 5); + + turret.x = (tx * 5) - 2.5; + turret.y = (ty * 5) - 2.5; + selection.placeable = tx >= 3 && tx <= 158 && ty >= 3 && ty <= 98; + + for (var i = 5; i--;) { + for (var ii = 5; ii--;) { + if (game.tiles[(tx + i - 2) + "," + (ty + ii - 2)]) { + selection.placeable = false; + return; + } + } + } + } +}, false); + +$("pages-canvas").addEventListener("click", function (evt) { + var selection = game.selection; + var turret = selection.turret; + var tile = game.tiles[Math.ceil((evt.pageX - this.offsetLeft) / 5) + "," + Math.ceil((evt.pageY - this.offsetTop) / 5)]; + + if (selection.status === "moving") { + if (selection.placeable && game.cash - 90 >= 0) { + game.cash -= 90; + game.turrets[turret.id] = turret; + + var tx = (turret.x + 2.5) / 5, ty = (turret.y + 2.5) / 5; + for (var i = 5; i--;) { + for (var ii = 5; ii--;) { + game.tiles[(tx + i - 2) + "," + (ty + ii - 2)] = turret; + } + } + + ui.panel("turrets"); + game.selection = false; + ui.action.refresh(); + } + } else if (selection.status === "placing") { + if (selection.placeable) { + game.cash -= turret.cost; + game.spent += turret.cost; + game.turrets.push(turret); + + var tx = (turret.x + 2.5) / 5, ty = (turret.y + 2.5) / 5; + for (var i = 5; i--;) { + for (var ii = 5; ii--;) { + game.tiles[(tx + i - 2) + "," + (ty + ii - 2)] = turret; + } + } + + game.selection = false; + ui.action.refresh(); + } + } else if (typeof tile === "object") { + game.selection = { + status: "selected", + turret: tile + }; + + ui.action.refresh(); + ui.panel("manage"); + } else { + ui.action.deselect(); + } +}, false); + + +/////////////////////////////////////////////////////////////////////////////// +// Control panel +/////////////////////////////////////////////////////////////////////////////// +$("control").addEventListener("click", function (evt) { + if (evt.target.id === "control") { + ui.action.deselect(); + } +}, false); + +ui.bind("click", $("control-turrets").children, function (evt) { + if (!game.paused) { + ui.action.build(this.getAttribute("data-name")); + } +}); + +ui.bind("click", $("control-manage").getElementsByTagName("a"), function (evt) { + var action = evt.target.id.split("-")[2]; + + if (!game.paused) { + (ui.action[action] || ui.action.upgrade)(action); + } +}); + +$("control-timer").addEventListener("click", function (evt) { + if (!game.paused) { + game._wave = game.ticks - 1200; + } +}, false); + +$("control-fast").addEventListener("click", function (evt) { + if (!game.paused) { + this.style.backgroundColor = (game.fast = !game.fast) ? "#97D164" : "#85ADE6"; + game.pause(); + game.start(); + } +}, false); + +$("control-pause").addEventListener("click", function (evt) { + this.textContent = game.paused ? (game.start(), "Pause") : (game.pause(), "Start"); +}, false); + + +/////////////////////////////////////////////////////////////////////////////// +// Init +/////////////////////////////////////////////////////////////////////////////// +ui.bind("click", $("pages-start-maps").children, function (evt) { + var name = this.textContent; + game.map = Defs.maps[name]; + game.map.name = name; + + game.map.map(function (p) { + return { x: p.x, y: p.y }; + }).forEach(function (cur, i, a) { + var next = a[i + 1] || cur, dx = next.x - cur.x, dy = next.y - cur.y; + + if (Math.abs(dx) > Math.abs(dy)) { + cur.x += dx < 0 ? 21 : -16; + var m = dy / dx, b = cur.y - m*cur.x; + dx = dx < 0 ? -1 : 1; + + while (cur.x !== next.x) { + cur.x += dx; + + for (var i = -3; i <= 4; i++) { + game.tiles[Math.round(cur.x / 5) + "," + ((Math.round(m*cur.x + b) / 5) + i)] = true; + } + } + } else if (dy !== 0) { + cur.y += dy < 0 ? 21 : -16; + var m = dx / dy, b = cur.x - m*cur.y; + dy = dy < 0 ? -1 : 1; + + while (cur.y !== next.y) { + cur.y += dy; + + for (var i = -3; i <= 4; i++) { + game.tiles[((Math.round(m*cur.y + b) / 5) + i) + "," + Math.round(cur.y / 5)] = true; + } + } + } + }); + + document.addEventListener("keydown", ui.handleshortcuts, false); + window.addEventListener("beforeunload", ui.handleunload, false); + + game.start(); + ui.panel("turrets"); + ui.page("canvas"); + + _gaq.push(["_trackEvent", "Game", "Start", name]); +}); + +ui.handletweets = function (data) { + var maps = { + loopy: $("pages-scores-twitter-loopy"), + backtrack: $("pages-scores-twitter-backtrack"), + dash: $("pages-scores-twitter-dash") + }; + + data.results.forEach(function (tweet) { + var m = tweet.text.match(/I scored (\d+) \((\d+) kills, \$(\d+) spent\) on (Loopy|Backtrack|Dash) in #canvastd/i); + + if (m) { + var map = maps[m[4].toLowerCase()]; + + if (m[1] == m[2] * m[3] && map.children.length < 31) { + var url = "https://twitter.com/" + tweet.from_user + "/status/" + tweet.id_str, + title = "@" + tweet.from_user + " on " + tweet.created_at, + a = '@' + tweet.from_user + ' '; + + map.innerHTML += '
  • ' + a + m[1] + ' ☠' + m[2] + ' $' + m[3] + '
  • '; + } + } + }); +}; + +ui.action.scores(); \ No newline at end of file diff --git a/scripts/utils.js b/scripts/utils.js new file mode 100644 index 0000000..3912179 --- /dev/null +++ b/scripts/utils.js @@ -0,0 +1,71 @@ +/////////////////////////////////////////////////////////////////////////////// +// Math +/////////////////////////////////////////////////////////////////////////////// +Math.inRadius = function (target, obj, rad) { + return (obj.x - target.x)*(obj.x - target.x) + (obj.y - target.y)*(obj.y - target.y) < rad*rad; +}; + +Math.move = function (obj, target, speed) { + var distx = target.x - obj.x; + var disty = target.y - obj.y; + var angle = Math.atan2(disty, distx); + + obj.x += speed * Math.cos(angle); + obj.y += speed * Math.sin(angle); + + return (distx < 0 ? -distx : distx) + (disty < 0 ? -disty : disty) < 2; +}; + +Math.rand = function (max) { + return Math.floor(Math.random() * (max + 1)); +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Elements +/////////////////////////////////////////////////////////////////////////////// +var $ = function (id) { + return document.getElementById(id); +}; + +window.ui = { + timer: $("control-timer"), + cash: $("control-cash"), + lives: $("control-lives"), + wave: $("control-wave"), + fps: $("control-fps"), + + nav: ["start"], + action: {}, + + bind: function (evt, elems, fn) { + Array.prototype.slice.call(elems).forEach(function (elem) { + elem.addEventListener(evt, fn, false); + }); + }, + page: function (name) { + if (name) { + ui.nav.unshift(name); + } else { + ui.page(ui.nav[1]); + return; + } + + Array.prototype.slice.call($("pages").children).forEach(function (elem) { + if (elem.id !== "pages-overlay") { + elem.style.display = "none"; + } + }); + + $("pages-" + name).style.display = "block"; + + _gaq.push(["_trackPageview", "/" + name]); + }, + panel: function (name) { + Array.prototype.slice.call($("control-left").children).forEach(function (elem) { + elem.style.display = "none"; + }); + + $("control-" + name).style.display = "block"; + } +}; \ No newline at end of file diff --git a/snake.html b/snake.html new file mode 100644 index 0000000..5e79edf --- /dev/null +++ b/snake.html @@ -0,0 +1,169 @@ + + + + Basic Snake HTML Game + + + + + + + + diff --git a/space.html b/space.html new file mode 100644 index 0000000..ab4e808 --- /dev/null +++ b/space.html @@ -0,0 +1,1643 @@ + + + + Space Invaders + + + + + +
    + + diff --git a/td.html b/td.html new file mode 100644 index 0000000..10a92b4 --- /dev/null +++ b/td.html @@ -0,0 +1,155 @@ + + + + + Canvas TD + + + + + +
    + + +
    + +
    +
    + Loopy + Backtrack + Dash +
    + +
    +

    A fully featured tower defense game built with <canvas>.

    + +

    Instructions

    +

    Build and upgrade turrets (which costs money and increases score) to kill creeps (which generates money and increases score). Experiment with the turrets; each has a special ability when fully upgraded.

    + +

    Shortcuts

    +
    +
    1
    +
    Build Laser / Upgrade damage
    + +
    2
    +
    Build Missile / Upgrade rate
    + +
    3
    +
    Build Tazer / Upgrade range
    + +
    4
    +
    Build Mortar / Move
    + +
    *
    +
    Sell
    + +
    =
    +
    Fast forward
    + +
    Esc
    +
    Deselect / Cancel / Pause
    + +
    Enter
    +
    Send wave
    +
    + + +
    +
    + +
    +

    Loopy

    +

    Backtrack

    +

    Dash

    + +
      +
        +
          + +
            +
              +
                + + +
                + +
                +

                Canvas TD was originally created for 10k Apart 2010, a challenge to build a web app in 10 KiB or less. Finished in less than twelve days, it failed to receive any prizes, but was ranked high in community voting and generated a significant amount of Twitter activity. While lacking much in the way of instructions, it was fully featured and quite enjoyable, and can still be played here.

                + +

                After the contest ended, work was started on a rewrite. Completed in June 2011, it featured far better code, many UI improvements, new gameplay elements, and some real instructions. Technologies used include <canvas>, @font-face, the Twitter Search API, Web Storage, and many other HTML5/CSS3/ES5 features.

                + +

                The endless, increasingly difficult gameplay (and many other core elements) is inspired by Onslaught TD. The retro look is heavily based on the excellent BIT.TRIP games and Vector TD. I'd also like to thank Alex K. and David S. for their plentiful ideas, testing, feedback, and motivation.

                + + + + +
                +
                + +
                +
                + + +
                + + + + Move
                $90
                + +
                +
                + +
                + Game over! You scored .

                + Play again or view scores + (maybe tweet yours?) +
                +
                + +
                +
                + + +
                + +
                + Cash $35
                + Lives 10
                + Wave #1 +
                + +
                + 60FPS
                + Pause
                + Reset +
                +
                +
                + + + + + + + diff --git a/tetris.html b/tetris.html new file mode 100644 index 0000000..2ba726e --- /dev/null +++ b/tetris.html @@ -0,0 +1,340 @@ + + + + + + + Mainpage + + + Go Home + + + + + + + + + +