todo - smoke
A Pen by Dealga McArdle on CodePen.
<div class='hawthorn'> | |
<canvas width="600px" height="600px"></canvas> | |
<br> | |
<button class='start'>Start!!!</button> | |
<button class='end'>End!!!</button> | |
<input id='debug_checkbox' type="checkbox" name="debug_stats" value="debug" checked>Display Debug Info | |
<input type="radio" id="easy" name="difficulty" value="Easy" checked/> Easy | |
<input type="radio" id="medium" name="difficulty" value="Medium" /> Medium | |
<input type="radio" id="harder" name="difficulty" value="Harder" /> Harder | |
</br> | |
<div class='instructions'><div class='inner'> | |
<ul> | |
<li> | |
<span class='kb'>W</span>, <span class='kb'>S</span> for upper jet bursts | |
</li> | |
<li> | |
<span class='kb'>A</span>, <span class='kb'>D</span> for lower rocket direction | |
</li> | |
<li> | |
<span class='kb'>E</span> cut engines, | |
<span class='kb'>Up</span> throttle up, | |
<span class='kb'>Down</span> throttle down | |
</li> | |
</ul> | |
</div> | |
</div> | |
</div> |
todo - smoke
A Pen by Dealga McArdle on CodePen.
A Pen by Dealga McArdle on CodePen.
### | |
Done for educational purposes only. This is not my idea of efficient or pretty code. | |
If this message is still here, the code hasn't been refactored much. | |
Cleanups, additions | |
[ ] horizontal delta velocity considered during landing | |
[ ] topple animation, detonation of combustables | |
[ ] smoke / steam on water/rocket proximity | |
[ ] switch for widescreen mode (current hardcoded 600*600 ) | |
### | |
landed = false | |
tick = 0 | |
show_text = true | |
show_debug = true | |
timerVar = {} | |
difficulty = 'Easy' | |
## ------------- util functions ----------------------------- ## | |
color = (r,g,b) -> 'rgb(' + [r, g, b].toString() + ')' | |
colorA = (r,g,b,a) -> 'rgba(' + [r, g, b, a].toString() + ')' | |
drawPolygonStroke = (ctx, loc, co, col, lw) -> | |
ctx.strokeStyle = col | |
ctx.beginPath() | |
ctx.lineWidth = lw | |
ctx.moveTo(co[0].x + loc.x, co[0].y + loc.y) | |
for i in [1...co.length] | |
ctx.lineTo(co[i].x + loc.x, co[i].y + loc.y) | |
ctx.stroke() | |
drawPolygonFill = (ctx, loc, co, col) -> | |
ctx.fillStyle = col | |
ctx.beginPath() | |
ctx.moveTo(co[0].x + loc.x, co[0].y + loc.y) | |
for i in [1...co.length] | |
ctx.lineTo(co[i].x + loc.x, co[i].y + loc.y) | |
ctx.fill() | |
## ------- objects ------------------------------------ ## | |
Meteo = -> | |
@.update = -> | |
a = Math.sin(@.PI) * 3 | |
b = Math.sin(@.PI * 4) * 3 | |
c = Math.sin(@.PI * 10) * 3 | |
d = Math.sin(@.PI * 2) * 2 | |
@.force = ((a + b + c + d) * 4) | |
if Math.random() > 0.7 | |
@.PI += 0.01 | |
if @.PI > (30 * Math.PI) | |
@.PI = 0.0 | |
@.reset = -> | |
@.PI = Math.random() * 30 * Math.PI | |
@.force = 0 | |
@.reset() | |
return @ | |
Rocket = (wind) -> | |
@.x = 0 | |
@.y = -50 | |
@.angle = 1.2 | |
@.delta_vector2d = {x: 0, y: 0} | |
@.max_thrust = 24 | |
@.stabilizer_steps = 122 | |
@.stabilizer_step = 0 | |
@.update = -> | |
old_x = @.vector2d.x | |
old_y = @.vector2d.y | |
@.angle = Math.abs(Math.cos(@.rotation)) | |
if @.rocket_fuel <= 0 | |
@.thrust = 0 | |
@.rocket_fuel = 0 | |
else | |
@.rocket_fuel -= @.thrust | |
@.jet_left_tick -= 1 if (@.jet_left_tick > 0) | |
@.jet_right_tick -= 1 if (@.jet_right_tick > 0) | |
amp = @.thrust * 0.463 | |
upper_jets = ((@.jet_left_tick - @.jet_right_tick) * 0.007) | |
@.rotation += (@.thrust_direction * 0.014) + upper_jets | |
# ever present downwards force | |
downwards = 7.74 | |
if difficulty == 'Easy' | |
[force_amp, rotation_amp] = [73, 0.03] | |
else if difficulty == 'Medium' | |
[force_amp, rotation_amp] = [43, 1.23] | |
else if difficulty == 'Harder' | |
[force_amp, rotation_amp] = [33, 2.93] | |
horizontal = ((wind.force/force_amp) * Math.abs(Math.cos(@.rotation)) * rotation_amp) | |
@.orientation2d = { | |
x: Math.cos(@.rotation - (Math.PI/2)), | |
y: Math.sin(@.rotation - (Math.PI/2)) | |
} | |
horizontal += (@.orientation2d.x * amp) | |
downwards += (@.orientation2d.y * amp) | |
@.vector2d = {x: @.vector2d.x + horizontal, y: @.vector2d.y + downwards} | |
if @.vector2d.y > 700 | |
@.reset() | |
@.delta_vector2d = {x: (@.vector2d.x - old_x), y: (@.vector2d.y - old_y)} | |
if @.stabilizer_step == 48 | |
@.deploy_stabilizers = false | |
if @.deploy_stabilizers | |
@.stabilizer_step += 2 | |
@.reset = -> | |
tick = 0 | |
@.stabilizer_step = 0 | |
@.deploy_stabilizers = false | |
landed = false | |
@.orientation2d = {x:0, y:1} # y=vertical x=horizontal | |
@.vector2d = {x:0, y:1.64} # y=vertical x=horizontal | |
@.rotation = (Math.random() - 0.5) * 0.6 | |
if 0.0 <= @.rotation <= 0.1 | |
@.rotation = 0.2 + (Math.random() * 0.2) | |
if -0.1 <= @.rotation < 0.0 | |
@.rotation = -0.2 - (Math.random() * 0.2) | |
@.rocket_fuel = 2300 | |
@.thrust = 8 | |
@.thrust_direction = 0.00 | |
@.jet_fuel = 4000 | |
@.jet_duration = 8 | |
@.jet_left_tick = 0 | |
@.jet_right_tick = 0 | |
# will optimize this bullshit. | |
@.update_thrust_direction = (dir) -> | |
ext = 8.9 | |
if -ext <= @.thrust_direction <= ext | |
@.thrust_direction += (dir * 0.9) | |
else | |
@.thrust_direction = ext if (@.thrust_direction > ext) | |
@.thrust_direction = -ext if (@.thrust_direction < -ext) | |
@.update_thrust = (key) -> | |
if key == 101 | |
@.thrust = 0 | |
return | |
@.thrust = if key == 38 then @.thrust + 1 else @.thrust - 1 | |
@.thrust = 0 if @.thrust < 0 # off | |
@.thrust = @.max_thrust if @.thrust > @.max_thrust # limit | |
@.update_jet_direction = (dir) -> | |
if dir == -1 | |
@.jet_left_tick = @.jet_duration | |
else | |
@.jet_right_tick = @.jet_duration | |
@.reset() | |
return @ | |
## ---- role one out of the factory! ----------------------------- ## | |
wind = new Meteo() | |
rocket = new Rocket(wind) | |
## -----------------drawing routines ----------------------------- ## | |
drawStabilizers = (ctx, obj, w) -> | |
ctx.strokeStyle = color(195, 195, 195) | |
ctx.beginPath() | |
ctx.lineWidth = 1 | |
theta = Math.PI * 2 / obj.stabilizer_steps | |
y = 80 | |
offset = Math.PI / 2 | |
arm1 = 11.2 | |
arm2 = 18.6 | |
a_ = -Math.sin(theta * obj.stabilizer_step) * arm1 | |
y_ = Math.cos(theta * obj.stabilizer_step) * arm1 | |
B = arm2 | |
A = Math.abs(a_) | |
C = Math.sqrt( (B*B) - (A*A) ) | |
G = [[0,0], [a_, y_], [0, y_ + C]] | |
# left stabilizer | |
ctx.moveTo(obj.x + G[0][0] - w, 80 + obj.y - G[0][1]) | |
ctx.lineTo(obj.x + G[1][0] - w, 80 + obj.y - G[1][1]) | |
ctx.lineTo(obj.x + G[2][0] - w, 80 + obj.y - G[2][1]) | |
# right stabilizer | |
ctx.moveTo(obj.x - G[0][0] + w, 80 + obj.y - G[0][1]) | |
ctx.lineTo(obj.x - G[1][0] + w, 80 + obj.y - G[1][1]) | |
ctx.lineTo(obj.x - G[2][0] + w, 80 + obj.y - G[2][1]) | |
ctx.stroke() | |
DrawRocket = (ctx, obj, wind) -> | |
ctx.save() | |
ctx.translate(250 + obj.vector2d.x, obj.vector2d.y) | |
ctx.rotate(obj.rotation) | |
w = 3 # rocket width | |
# bg...tricolor | |
sections = [ | |
{start: 2, end: 32, col: color(115, 115, 115)}, | |
{start: 32, end: 52, col: color(175, 175, 175)}, | |
{start: 52, end: 82, col: color(105, 105, 105)} | |
] | |
ctx.lineWidth = 6 | |
for s in sections | |
ctx.strokeStyle = s.col | |
ctx.beginPath() | |
ctx.moveTo(obj.x, obj.y + s.start) | |
ctx.lineTo(obj.x, obj.y + s.end) | |
ctx.stroke() | |
# draw gradient | |
grd = ctx.createLinearGradient(obj.x - w, 0, obj.x + w,0) | |
grd.addColorStop(0, colorA(47, 47, 47, 0.5)) | |
grd.addColorStop(1, colorA(147, 147, 147, 0.8)) | |
ctx.fillStyle = grd | |
ctx.fillRect(obj.x - w, obj.y, 2*w, 80) | |
ctx.strokeStyle = color(155, 155, 155) | |
ctx.beginPath() | |
ctx.lineWidth = 1 | |
# start bottom left, up, over, down, bottom | |
ctx.moveTo(obj.x - w, obj.y + 80) | |
ctx.lineTo(obj.x - w, obj.y + 10) | |
ctx.lineTo(obj.x - (w - 1), obj.y + 1) | |
ctx.lineTo(obj.x, obj.y) | |
ctx.lineTo(obj.x + (w - 1), obj.y + 1) | |
ctx.lineTo(obj.x + w, obj.y + 10) | |
ctx.lineTo(obj.x + w, obj.y + 80) | |
ctx.stroke() | |
drawStabilizers(ctx, obj, w) | |
# thrust | |
if not landed | |
coords = [ | |
{x: -w, y: 80}, | |
{x:obj.thrust_direction, y: 96 + (obj.thrust/2)}, | |
{x: w, y: 80} | |
] | |
drawPolygonFill(ctx, {x: obj.x, y: obj.y}, coords, color(225, 188, 92)) | |
draw_angled = (x_start, y_start, theta, jet_tick) -> | |
col = color(225,225,225) | |
x_dest = Math.cos(theta) * jet_tick * 1.3 | |
y_dest = Math.sin(theta) * jet_tick * 1.3 | |
coords = [{x:0, y:0}, {x:x_dest, y:y_dest}] | |
drawPolygonStroke(ctx, {x: x_start, y: y_start}, coords, col, 2) | |
draw_angled(obj.x - w, obj.y + 7, Math.PI + 0.4, rocket.jet_left_tick) | |
draw_angled(obj.x + w, obj.y + 7, -0.4, rocket.jet_right_tick) | |
ctx.lineWidth = 1 | |
# left darkest Stroke | |
ctx.strokeStyle = color(30,30,30) | |
ctx.beginPath() | |
ctx.moveTo(obj.x - w, obj.y + 80) | |
ctx.lineTo(obj.x - w, obj.y + 10) | |
ctx.lineTo(obj.x - (w-1), obj.y + 1) | |
ctx.stroke() | |
ctx.restore() | |
drawBg = (ctx) -> | |
grd = ctx.createLinearGradient(0,0,0,600) | |
grd.addColorStop(0, color(117, 122, 142)) | |
grd.addColorStop(0.41, color(110, 122, 145)) | |
grd.addColorStop(0.45, color(90, 102, 135)) | |
grd.addColorStop(1, color(28, 43, 98)) | |
ctx.fillStyle = grd | |
ctx.fillRect(0,0,600,600) | |
DrawText = (ctx, location, msg, size, align, col = '#efefef') -> | |
textColor = col | |
textSize = size.toString() | |
fontFace = "Open Sans" | |
ctx.textAlign = align | |
ctx.fillStyle = textColor | |
ctx.font = textSize + "px " + fontFace | |
ctx.fillText(msg, location.x, location.y) | |
DrawBarge = (ctx) -> | |
gamma = Math.PI*2/300 * tick | |
x = 250 + Math.sin(gamma) * 2 | |
y = 510 + Math.sin(gamma) * 3 | |
w = 39 | |
ctx.fillStyle = color(42, 42, 42) | |
ctx.beginPath() | |
ctx.moveTo(x - w, y) | |
ctx.lineTo(x - (w+4), y + 18) | |
ctx.lineTo(x + (w+4), y + 18) | |
ctx.lineTo(x + w, y) | |
ctx.fill() | |
ctx.fillStyle = color(22, 22, 72) | |
ctx.beginPath() | |
ctx.moveTo(x - w, y + 18) | |
ctx.lineTo(x - (w-4), y + 23) | |
ctx.lineTo(x + (w-4), y + 23) | |
ctx.lineTo(x + w, y + 18) | |
ctx.fill() | |
coords = [] | |
theta = Math.PI * 2 / 23 | |
for j in [0..23] | |
coords.push({x: Math.sin(theta*j) * 26, y: Math.cos(theta*j) * 6}) | |
drawPolygonStroke(ctx, {x: x+0, y: y+9}, coords, color(150, 150, 60), 1) | |
return {x: x, y: y, w: w} | |
DrawMeteoData = (ctx, wind) -> | |
ypos = 280 | |
coords = [{x:0, y:-8}, {x:wind.force, y:-8}] | |
drawPolygonStroke(ctx, {x: 300, y: ypos}, coords, color(150, 250, 160), 4) | |
for i in [-10..10] | |
drawPolygonStroke( | |
ctx, | |
{x: 300 + (7 * i), y: ypos-6}, | |
[{x:0,y:0},{x:0, y: if i%3==0 then 10 else 4}], | |
color(150, 250, 160), 1 | |
) | |
updateTicks = () -> | |
tick += 1 | |
if tick > 300 | |
tick = 0 | |
if tick > 23 | |
show_text = false | |
updateText = (ctx) -> | |
if show_text | |
DrawText(ctx, {x:300, y:60}, 'R T F M!', 32, 'center') | |
if show_debug | |
lineheight = 20 | |
line_1 = 'rotation: ' + rocket.rotation.toFixed(3) | |
line_2 = 'rot cos: ' + rocket.angle.toFixed(3) | |
line_3 = 'rocket fuel: ' + rocket.rocket_fuel | |
for line, i in [line_1, line_2, line_3] | |
DrawText(ctx, {x:20, y:30 + lineheight*i}, line, 18, 'left') | |
if i==1 and (0.9994 <= rocket.angle <= 1.0006) | |
ctx.fillRect(5, 19 + (lineheight * i), 10, 10) | |
ypos = 30 + lineheight*4 | |
DrawText(ctx, {x:20, y: ypos}, 'orien 2d', 18, 'left') | |
x1 = rocket.orientation2d.x * 14 | |
y1 = rocket.orientation2d.y * 14 | |
coords = [{x:0, y:0}, {x:x1, y:y1}] | |
xypos = {x: 120, y: ypos-4} | |
drawPolygonStroke(ctx, xypos, coords, '#efefef', 2) | |
coords = [] | |
theta = Math.PI * 2 / 23 | |
for j in [0..23] | |
coords.push({x: Math.sin(theta*j) * 15, y: Math.cos(theta*j) * 15}) | |
drawPolygonStroke(ctx, xypos, coords, '#efefef', 1) | |
line5 = 'thrust: ' + rocket.thrust | |
DrawText(ctx, {x:20, y: lineheight*7}, line5, 18, 'left') | |
line6 = 'difficulty: ' + difficulty | |
DrawText(ctx, {x:20, y: lineheight*8}, line6, 18, 'left') | |
check_if_landed = (rocket, limits, ctx) -> | |
rx = rocket.vector2d.x | |
ry = rocket.vector2d.y | |
w = limits.w - 5 | |
if (0.9994 <= rocket.angle <= 1.0006) | |
if (limits.x - w) <= (rx + 250) <= (limits.x + w) | |
if (limits.y+6) <= (ry + 40) <= (limits.y + 15) | |
console.log(rocket.delta_vector2d) | |
if rocket.delta_vector2d.y < Math.abs(0.6) # 1.6 .... 0.2 | |
msg = 'soft landing, power down. party time.' | |
else | |
msg = 'hard landing, structure fractured..' | |
DrawText(ctx, {x:300, y: 300}, msg, '20', 'center') | |
return true | |
return false | |
Draw = (ctx) -> | |
drawBg(ctx) | |
limits = DrawBarge(ctx) | |
landed = check_if_landed(rocket, limits, ctx) | |
DrawRocket(ctx, rocket, wind) | |
DrawMeteoData(ctx, wind) | |
if landed | |
window.clearInterval timerVar | |
updateTicks() | |
if tick == 13 | |
console.log('deploy!!!') | |
rocket.deploy_stabilizers = true | |
updateText(ctx) | |
rocket.update() | |
wind.update() | |
removeTimer = -> | |
rocket.reset() | |
wind.reset() | |
window.clearInterval timerVar | |
addTimer = (ctx) -> | |
removeTimer() # remove any existing timer | |
tick = 0 | |
show_text = true | |
timerVar = setInterval((-> | |
Draw(ctx) | |
return | |
), 50) | |
$(document).ready( -> | |
ctx = $("canvas")[0].getContext('2d'); | |
DrawText(ctx, {x:300, y:100}, 'Occupy Mars!', '65', 'center', '#555555') | |
DrawText(ctx, {x:300, y:150}, 'Play first stage landing game', '30', 'center', '#555555') | |
$('.start').click( -> addTimer(ctx) ) | |
$('.end').click( -> removeTimer() ) | |
$( "body" ).keypress( (event) -> | |
if (event.which == 97 ) # A | |
rocket.update_thrust_direction(-1) | |
else if (event.which == 100) # D | |
rocket.update_thrust_direction(1) | |
else if (event.which == 119) # W | |
rocket.update_jet_direction(-1) | |
else if (event.which == 115) # S | |
rocket.update_jet_direction(1) | |
else if (event.which == 101) # up / down | |
rocket.update_thrust(event.which) | |
) | |
$( "body" ).keydown( (event) -> | |
if event.which in [38, 40] | |
rocket.update_thrust(event.which) | |
) | |
$('input#debug_checkbox').change ( -> | |
show_debug = $( this )[0].checked | |
) | |
$('input:radio[name="difficulty"]').change( -> | |
difficulty = $(this).val() | |
) | |
) | |
## ------------------------------ END --------------------------------- ## |
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> |
@import url(http://fonts.googleapis.com/css?family=Pacifico|Open+Sans:300) | |
@link-font: "Open Sans" | |
body | |
font-family: Open Sans | |
.kb | |
border: 1px solid #dddddd | |
background-color: #666666 | |
color: #363636 | |
padding: 2px 8px 3px 8px | |
background: #ffffff | |
background: -moz-linear-gradient(left, #ffffff 0%, #f6f6f6 47%, #ededed 100%) | |
background: -webkit-gradient(linear, left top, right top, color-stop(0%, #ffffff), color-stop(47%, #f6f6f6), color-stop(100%, #ededed)) | |
background: -webkit-linear-gradient(left, #ffffff 0%, #f6f6f6 47%, #ededed 100%) | |
background: -o-linear-gradient(left, #ffffff 0%, #f6f6f6 47%, #ededed 100%) | |
background: -ms-linear-gradient(left, #ffffff 0%, #f6f6f6 47%, #ededed 100%) | |
background: linear-gradient(to right, #ffffff 0%, #f6f6f6 47%, #ededed 100%) | |
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ededed',GradientType=1 ) | |
ul | |
margin: 0 | |
padding-left: 0 | |
li | |
margin: 6px 0 11px 0 | |
list-style-type: none | |
body | |
background-color: #dedede | |
.instructions | |
width: 600px | |
padding: 0px | |
margin-top: 10px | |
background-color: #fefefe | |
.inner | |
padding: 10px |