Created
April 21, 2012 03:15
-
-
Save basicxman/2433562 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Asteroid extends GameObject | |
constructor: (engine) -> | |
super(engine) | |
@updateOffScene = true | |
@drawOffScene = true | |
@production = Math.ceil(Math.random() * 20) | |
@fuel = Math.ceil(Math.random() * 20) | |
@research = Math.ceil(Math.random() * 20) | |
@width = 99 | |
@height = 100 | |
@angle = 0 | |
@speed = Math.random() * 0.1 | |
@indicator = new Indicator("rgba(255, 255, 255, 0.3)", this) | |
if Math.round(Math.random()) | |
@x = Math.floor(Math.random() * @engine.gW) | |
@y = if Math.floor(Math.random()) then @height else @engine.gH | |
else | |
@y = Math.floor(Math.random() * @engine.gH) | |
@x = if Math.floor(Math.random()) then @width else @engine.gW | |
rise = @engine.gH / 2 - @y + Math.floor(Math.random() * 150) - 300 | |
run = @engine.gW / 2 - @x + Math.floor(Math.random() * 150) - 300 | |
@speedX = run * 0.001 | |
@speedY = rise * 0.001 | |
@points = [] | |
@points.push [48, 72] | |
@points.push [24, 72] | |
@points.push [12, 54] | |
@points.push [0, 36] | |
@points.push [12, 18] | |
@points.push [24, 0] | |
@points.push [48, 0] | |
@points.push [60, 18] | |
@points.push [72, 36] | |
@points.push [60, 54] | |
for i in [0...10] | |
@points[i][0] -= 50 + Math.floor(Math.random() * 10) - 20 | |
@points[i][1] -= 50 + Math.floor(Math.random() * 10) - 20 | |
for i in [0...Math.floor(Math.random() * 3)] | |
index = Math.floor(Math.random() * @points.length) | |
@points.splice(index, 1) | |
save: -> | |
super "Asteroid" | |
update: -> | |
@vX = @x - Math.abs(@engine.dX) | |
@vY = @y - Math.abs(@engine.dY) | |
if @speedX > 0 | |
@angle += @speedX * @speed * 0.05 | |
else | |
@angle -= @speedX * @speed * 0.05 | |
@angle %= Math.PI * 2 | |
@x += @speedX * @speed | |
@y += @speedY * @speed | |
if @x > @engine.gW or @y > @engine.gH or @x < 0 or @y < 0 | |
return -1 | |
this.checkIfInScene() | |
a = Math.abs(@engine.gH / 2 - @y) | |
b = Math.abs(@engine.gW / 2 - @x) | |
c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)) | |
# Only display indicators for asteroids within 1500 pixels. | |
@outOfRange = c > 1500 | |
@engine.asteroidsInRange++ if not @outOfRange | |
if not @inScene and not @outOfRange | |
@indicator.update() | |
draw: (ctx) -> | |
ctx.save() | |
ctx.strokeStyle = "rgba(255, 255, 255, 0.8)" | |
ctx.fillStyle = "rgba(255, 255, 255, 0.3)" | |
if @inScene | |
ctx.translate(@vX + @width / 2, @vY + @height / 2) | |
ctx.rotate(@angle) | |
ctx.beginPath() | |
ctx.moveTo(@points[0][0], @points[0][1]) | |
for point in @points | |
ctx.lineTo(point[0], point[1]) | |
ctx.closePath() | |
ctx.stroke() | |
else if not @outOfRange | |
@indicator.draw(ctx) | |
ctx.restore() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
coffee --watch \ | |
--join game.js \ | |
--compile \ | |
game_object.coffee \ | |
indicator.coffee \ | |
asteroid.coffee \ | |
space_station.coffee \ | |
game.coffee \ | |
game_engine.coffee |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Game | |
constructor: (@engine) -> | |
if localStorage.data? | |
@storage = $.parseJSON(localStorage.data) | |
else | |
@storage = {} | |
if not @storage.games? | |
this.initStorage() | |
if @storage.games.length == 0 | |
$("#current-games").html("<h3 class='none-available'>No current games available.</h3>"); | |
else | |
for i in [0...@storage.games.length] | |
game = @storage.games[i] | |
tempGameSave = $("#game-save-template").clone().attr("id", i) | |
lastSaveTime = new Date(game.lastSaveTime) | |
tempGameSave.children(".screenshot").attr("src", game.screenshot) | |
tempGameSave.children(".commander-name").text(game.commanderName) | |
tempGameSave.children(".game-save-time").text(lastSaveTime.toDateString()) | |
tempGameSave.children(".faction").text(game.faction) | |
tempGameSave.appendTo($("#current-games")) | |
$("#start-game").click => | |
@gameIndex = @storage.games.length | |
commanderName = $("#commander-name").val() | |
faction = "Soviet" | |
@storage.games.push { | |
commanderName: $("#commander-name").val() | |
, faction: "Soviet" | |
, lastSaveTime: new Date().getTime() | |
, g: {} | |
, objects: [] | |
} | |
this.startGame() | |
$(".current-game").click -> | |
window.game.game.gameIndex = $(this).attr("id") | |
window.game.game.startGame() | |
$("#game-selection").fadeIn() | |
initStorage: -> | |
@storage.games = [] | |
save: -> | |
localStorage.data = JSON.stringify(@storage) | |
startGame: -> | |
@engine.storage = @storage.games[@gameIndex] | |
@engine.load() | |
if @engine.storage.objects.length == 0 | |
@engine.objects.push(new SpaceStation(@engine)) | |
$("#game-selection").fadeOut() | |
@engine.gameStarted = true | |
@engine.pan(0, 0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@mixin border-radius($radius) { | |
-webkit-border-radius: $radius; | |
-moz-border-radius: $radius; | |
border-radius: $radius; | |
} | |
@mixin box-shadow($args) { | |
-webkit-box-shadow: $args; | |
-moz-box-shadow: $args; | |
box-shadow: $args; | |
} | |
body, div, canvas, label, input, h3, h4 { | |
margin: 0px; | |
padidng: 0px; | |
} | |
.clear { clear: both; } | |
#c { | |
width: 1024px; | |
margin: 30px auto; | |
canvas { | |
position: absolute; | |
} | |
} | |
#bg { z-index: 1; } | |
#fg { z-index: 2; } | |
div#game-selection { | |
display: none; | |
font-family: Audiowide; | |
width: 1024px; | |
margin: 80px auto; | |
background-color: rgba(73, 110, 145, 0.8); | |
color: #fff; | |
padding: 40px; | |
@include border-radius(10px); | |
div#current-games { | |
padding: 0px 10px; | |
.current-game { | |
width: 220px; | |
float: left; | |
cursor: pointer; | |
&:hover { | |
.screenshot { | |
$shadow: -1px 1px 20px rgba(0, 0, 0, 0.8), 1px 1px 20px rgba(0, 0, 0, 0.8); | |
@include box-shadow($shadow); | |
} | |
} | |
.screenshot { | |
background-color: #000; | |
margin-bottom: 8px; | |
$shadow: -1px -1px 20px rgba(255, 255, 255, 0.3), 1px 1px 20px rgba(255, 255, 255, 0.3); | |
@include box-shadow($shadow); | |
} | |
.faction { | |
top: 130px; | |
margin-left: 10px; | |
position: absolute; | |
} | |
} | |
} | |
div#new-game { | |
padding: 10px; | |
input { | |
padding: 4px; | |
line-height: 18px; | |
height: 18px; | |
font-size: 13px; | |
border: 1px solid #ccc; | |
color: #555; | |
@include border-radius(3px); | |
} | |
} | |
div#game-save-template { display: none; } | |
} | |
div#container { | |
width: 100%; | |
z-index: 3; | |
position: absolute; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class GameEngine | |
constructor: -> | |
@bg = document.getElementById("bg").getContext("2d") | |
@fg = document.getElementById("fg").getContext("2d") | |
@gW = 5000 | |
@gH = 3750 | |
@vW = @fg.canvas.width | |
@vH = @fg.canvas.height | |
@dX = (@gW - @vW) / -2 | |
@dY = (@gH - @vH) / -2 | |
@decay = 1.05 | |
@panMultiplier = -8 | |
@deadband = 0.005 | |
@objects = [] | |
this.registerTimer("save", 5000) | |
this.registerTimer("spawnAsteroid", 30000) | |
this.addEventListeners() | |
this.initCanvas() | |
this.startAnimation() | |
@game = new Game(this) | |
# Game initialization. | |
addEventListeners: -> | |
@fg.canvas.addEventListener "mousedown", (e) => | |
this.mouseDown(e) | |
@fg.canvas.addEventListener "mousemove", (e) => | |
this.mouseMove(e) | |
for event in ["mouseup", "mouseout"] | |
@fg.canvas.addEventListener event, ((e) => this.resetDrag()) | |
initCanvas: -> | |
@bg.canvas.style.background = "#000" | |
startAnimation: -> | |
anim = => | |
this.update() | |
this.draw() | |
requestAnimFrame(anim) | |
anim() | |
load: -> | |
for property, value of @storage.g | |
this[property] = value | |
for object in @storage.objects | |
eval("obj = new #{object.class}(this)") | |
obj.load(object) | |
@objects.push(obj) | |
save: -> | |
@storage.screenshot = @fg.canvas.toDataURL() | |
@storage.g.dX = @dX | |
@storage.g.dY = @dY | |
@storage.objects = [] | |
for object in @objects | |
@storage.objects.push(object.save()) | |
@game.save() | |
@saveText = "Saved." | |
# Events | |
mouseDown: (e) -> | |
# Only the left mouse button should be used for dragging. | |
if e.button != 0 | |
return | |
# Regardless of whether we reach a drag event in 100mS, momentum should | |
# stop from any previous pan event. | |
@velX = @velY = undefined | |
@timeout = setTimeout((=> this.startDrag(e)), 100) | |
mouseMove: (e) -> | |
if not @isDragging | |
return | |
# Store a circular buffer of three movement events, using the current event | |
# and previous event isn't representative enough for velocity calculations. | |
this.bufferAdvance(@bufDragTime, e.timeStamp) | |
this.bufferAdvance(@bufDragX, e.offsetX) | |
this.bufferAdvance(@bufDragY, e.offsetY) | |
if @bufDragX[2]? and @bufDragX[1]? | |
this.pan(@bufDragX[1] - @bufDragX[2], @bufDragY[1] - @bufDragY[2]) | |
startDrag: -> | |
@isDragging = true | |
this.bufferReset() | |
resetDrag: -> | |
if @bufDragTime? and @bufDragTime[0]? and @bufDragTime[2]? | |
t = @bufDragTime[2] - @bufDragTime[0] | |
rise = @bufDragY[2] - @bufDragY[0] | |
run = @bufDragX[2] - @bufDragX[0] | |
@velX = run / t | |
@velY = rise / t | |
clearTimeout(@timeout) | |
@isDragging = false | |
this.bufferReset() | |
pan: (x, y) -> | |
# Change position offset and ensure map boundaries are met. | |
@dX = Math.min(Math.max(@dX - x, -(@gW - @vW)), 0) | |
@dY = Math.min(Math.max(@dY - y, -(@gH - @vH)), 0) | |
# Update current viewport coordinates and ask objects to update their draw | |
# state. | |
@vX1 = -@dX | |
@vY1 = -@dY | |
@vX2 = @vX1 + @vW | |
@vY2 = @vY1 + @vH | |
for obj in @objects | |
obj.checkIfInScene() | |
panMomentum: -> | |
if @velX == undefined or @velY == undefined | |
return | |
this.pan(@panMultiplier * @velX, @panMultiplier * @velY) | |
@velX /= @decay | |
@velY /= @decay | |
if (Math.abs(@velX) < @deadband) | |
@velX = undefined | |
if (Math.abs(@velY) < @deadband) | |
@velY = undefined | |
# Utilities | |
registerTimer: (func, interval) -> | |
if not @timers? | |
@timers = [] | |
@timers.push [func, interval, new Date().getTime()] | |
checkTimers: -> | |
return if not @gameStarted | |
curTime = new Date().getTime() | |
for timer in @timers | |
if curTime - timer[2] >= timer[1] | |
timer[2] = curTime | |
this[timer[0]]() | |
bufferReset: -> | |
@bufDragTime = [undefined, undefined, undefined] | |
@bufDragX = [undefined, undefined, undefined] | |
@bufDragY = [undefined, undefined, undefined] | |
bufferAdvance: (buffer, value) -> | |
buffer[0] = buffer[1] | |
buffer[1] = buffer[2] | |
buffer[2] = value | |
updateSaveText: -> | |
if @saveCountdown == 0 | |
@saveText = "" | |
@saveCountdown = undefined | |
if @saveText == "Saved." | |
if @saveCountdown? | |
@saveCountdown -= 1 | |
else | |
@saveCountdown = 45 | |
@saveOpacity = 1.0 | |
if @saveCountdown < 30 | |
@saveOpacity = @saveCountdown / 30.0 | |
spawnAsteroid: -> | |
@objects.push(new Asteroid(this)) | |
# Periodic calls. | |
update: -> | |
this.panMomentum() | |
@asteroidsInRange = 0 | |
for object in @objects | |
if object.inScene or object.updateOffScene | |
if object.update() == -1 | |
_len-- | |
@objects.splice(_i, 1) | |
this.checkTimers() | |
this.updateSaveText() | |
draw: -> | |
@fg.clearRect(0, 0, @vW, @vH) | |
for object in @objects | |
if object.inScene or object.drawOffScene | |
object.draw(@fg) | |
@fg.fillStyle = "#fff" | |
@fg.font = "14px Audiowide" | |
@fg.fillText("#{Math.floor(Math.abs(@dX))}, #{Math.floor(Math.abs(@dY))}", 5, 15) | |
@fg.fillText("Asteroids in Range: #{@asteroidsInRange}", 5, 34) | |
if @saveText? | |
@fg.fillStyle = "rgba(255, 255, 255, #{@saveOpacity})" | |
@fg.font = "18px Audiowide" | |
@fg.fillText(@saveText, @vW - @fg.measureText(@saveText).width - 20, @vH - 20) | |
window.game = new GameEngine |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class GameObject | |
constructor: (@engine) -> | |
@inScene = false | |
@updateOffScene = false | |
@drawOffScene = false | |
@blacklist = ["engine", "blacklist", "indicator", "inScene", "updateOffScene", "drawOffScene"] | |
checkIfInScene: -> | |
@inScene = (@x >= @engine.vX1 - @width / 2) and (@x < @engine.vX2 + @width / 2) and (@y >= @engine.vY1 - @height / 2) and (@y < @engine.vY2 + @height / 2) | |
save: (className) -> | |
obj = { class: className } | |
for property, value of this | |
if not (property in @blacklist) and typeof value isnt "function" | |
obj[property] = this[property] | |
return obj | |
load: (obj) -> | |
for property, value of obj | |
this[property] = value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Indicator | |
constructor: (@colour, @object) -> | |
update: -> | |
@minX = 64 | |
@maxX = @object.engine.vW - 64 | |
@minY = 0 | |
@maxY = @object.engine.vH | |
@vX = Math.min(Math.max(@object.vX, @minX), @maxX) | |
@vY = Math.min(Math.max(@object.vY, @minY), @maxY) | |
if @vX == @minX and @vY == @minY | |
@angle = 3 * Math.PI / 4 | |
@vY += 24 | |
@vX += 4 | |
else if @vX == @maxX and @vY == @minY | |
@angle = -3 * Math.PI / 4 | |
@vY += 70 | |
@vX += 40 | |
else if @vX == @maxX and @vY == @maxY | |
@angle = -Math.PI / 4 | |
@vY -= 24 | |
@vX -= 4 | |
else if @vX == @minX and @vY == @maxY | |
@angle = Math.PI / 4 | |
@vY -= 70 | |
@vX -= 40 | |
else if @vX == @minX | |
@angle = Math.PI / 2 | |
else if @vX == @maxX | |
@angle = -Math.PI / 2 | |
else if @vY == @minY | |
@angle = Math.PI | |
@vY += 64 | |
else | |
@angle = 0 | |
@vY -= 64 | |
draw: (ctx) -> | |
ctx.translate(@vX, @vY) | |
ctx.rotate(@angle) | |
ctx.fillStyle = @colour | |
ctx.beginPath() | |
ctx.arc(32, 20, 20, (5 * Math.PI) / 6, Math.PI / 5, false) | |
ctx.lineTo(32, 64) | |
ctx.closePath() | |
ctx.fill() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Space</title> | |
<link href='http://fonts.googleapis.com/css?family=Audiowide' rel='stylesheet' type='text/css'> | |
<link rel="stylesheet" type="text/css" href="game.css" /> | |
</head> | |
<body> | |
<div id="c"> | |
<canvas id="bg" width="1024" height="768"></canvas> | |
<canvas id="fg" width="1024" height="768">Your browser does not support this game.</canvas> | |
</div> | |
<div id="container"> | |
<div id="game-selection"> | |
<div id="current-games"> | |
</div> | |
<div class="clear"></div> | |
<div id="new-game"> | |
<label for="commander-name">Commander Name:</label><br /> | |
<input type="text" id="commander-name" /> | |
<br /> | |
<button type="button" id="start-game">Start</button> | |
</div> | |
<div id="game-save-template" class="current-game"> | |
<img class="screenshot" width="200" /> | |
<h3 class="commander-name"></h3> | |
<h4 class="game-save-time"></h4> | |
<div class="faction"> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> | |
<script type="text/javascript" src="requestAnimFrame.js"></script> | |
<script type="text/javascript" src="game.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sass: sass --watch game.scss:game.css | |
coffee: ./compile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SpaceStation extends GameObject | |
constructor: (engine) -> | |
super(engine) | |
@updateOffScene = true | |
@drawOffScene = true | |
@health = 1000 | |
@width = 100 | |
@height = 100 | |
@x = engine.gW / 2 | |
@y = engine.gH / 2 | |
@indicator = new Indicator("rgba(119, 187, 255, 0.5)", this) | |
save: -> | |
super "SpaceStation" | |
update: -> | |
@vX = @x - Math.abs(@engine.dX) | |
@vY = @y - Math.abs(@engine.dY) | |
if not @inScene | |
@indicator.update() | |
draw: (ctx) -> | |
ctx.save() | |
if @inScene | |
ctx.strokeStyle = "rgba(119, 187, 255, 0.5)" | |
ctx.lineWidth = 5 | |
ctx.translate(@vX, @vY) | |
ctx.rotate(Math.PI / 4) | |
ctx.beginPath() | |
ctx.arc(0, 0, 10, 0, Math.PI * 2, true) | |
ctx.stroke() | |
ctx.strokeRect(@width / -2, @height / -2, @width, @height) | |
else | |
@indicator.draw(ctx) | |
ctx.restore() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment