Skip to content

Instantly share code, notes, and snippets.

@Bolloxim
Created July 24, 2014 16:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Bolloxim/c0ca4a015e52ede1a10f to your computer and use it in GitHub Desktop.
Save Bolloxim/c0ca4a015e52ede1a10f to your computer and use it in GitHub Desktop.
A Pen by Andi Smithers.
j<body>
<canvas id='test'></canvas>
</body>

ping radar - sweep and refresh

radar refreshes every 10 seconds. randomly moves ships about. locations only refresh when the scanner passes over those new and old locations. This gives a nice smooth fade exchange.

added more programmer art still liking the text, might keep it

working on making the scanner a playable game of TREK

A Pen by Andi Smithers on CodePen.

License.

// constant options
const focalDepth = 80;
const focalPoint = 256;
// variables
var centreX;
var centreY;
var mouseX=0;
var mouseY=0;
var frameCount=0;
var gameStart;
var context;
var canvas;
var fontsize = 20;
// test multiple groups
var boardPieces = [];
var mapScale = {x:0, y:0};
var galaxyMapSize = {x:16, y:8};
var shipLocation = {x:0, y:0};
var shipPosition = {x:0, y:0};
var shipPing = {x:0, y:0};
var pingRadius = 100;
var lastCycle = 0;
var cycleScalar = 1; // 3 = 30 seconds.
var targetBase = 0;
var warpLocation = {x:0, y:0};
var warpAnim = 0;
var warpLocked = false;
// board pieces
function GetBoardPiece(x, y, useKnown)
{
if (useKnown)
{
for (var i=0; i<boardPieces.length; i++)
{
if (boardPieces[i].lastknown.x == x && boardPieces[i].lastknown.y==y) return i;
}
}
else
{
for (var i=0; i<boardPieces.length; i++)
{
if (boardPieces[i].location.x == x && boardPieces[i].location.y==y) return i;
}
}
return -1;
}
function GetBoardPieceScreen(sx, sy, lastKnown)
{
// convert sx and sy into board-coords
var scaleX = canvas.width/(galaxyMapSize.x+2);
var scaleY = canvas.height/(galaxyMapSize.y+2);
x = Math.floor(sx/scaleX) - 1;
y = Math.floor(sy/scaleY) - 1;
return GetBoardPiece(x, y, lastKnown);
}
// creates a piece
function BoardPiece(x, y, type)
{
this.init(x, y, type);
}
// initializer
BoardPiece.prototype.init = function(fx, fy, type)
{
// type 0 = base
// type 1 = patrol
// type 2 = group
// type 3 = fleet
// status = 0 dead
// status = 1 alive
this.type = type;
this.laststate = 0;
this.lastknown = {x:fx, y:fy};
this.location = {x:fx, y:fy};
this.status = 2;
this.moveRate = type==3 ? 8 : type;
this.nextMove = this.moveRate;
this.numTargets = type==0 ? 0 : type+1;
}
// render pieces
BoardPiece.prototype.render = function(x, y, radius)
{
var trailRad2 = radius*radius*0.25;
var leadRad2 = radius*radius*4;
var pos = this.screen(this.lastknown.x, this.lastknown.y);
var dx = pos.x - x;
var dy = pos.y - y;
var distance2 = dx*dx+dy*dy;
var fade=1;
if (distance2<leadRad2) // update lastknow
{
fade = (distance2 - trailRad2) / (leadRad2-trailRad2);
if (fade>0 && this.laststate!=0)
{
this.drawitem(pos, fade);
}
}
else if (this.laststate!=0)
{
this.drawitem(pos, 1);
}
var pos2 = this.screen(this.location.x, this.location.y);
var dx2 = pos2.x-x;
var dy2 = pos2.y-y;
var distance22 = dx2*dx2+dy2*dy2;
if (distance22<leadRad2 && this.status>0) // update lastkno
{
fade = ((distance22 - trailRad2) / (leadRad2-trailRad2))+1.0;
this.drawitem(pos2, fade);
}
else if (this.status == 1)
{
this.drawitem(pos2, fade);
}
}
// update to known location
BoardPiece.prototype.updateKnowns = function()
{
if(this.status==2) this.status=1;
this.lastknown.x = this.location.x;
this.lastknown.y = this.location.y;
this.laststate = this.status;
}
// game piece movement logic - pretty rough randomizer
BoardPiece.prototype.move = function(gameCycle)
{
this.updateKnowns();
// scale game cycle time (radar beats every 10s)
aiCycle = Math.floor(gameCycle/cycleScalar);
if (this.nextMove<=aiCycle && this.type > 0) // new position except bases
{
var x, y;
var target = boardPieces[targetBase].location;
// bee-line
var dx = target.x - this.location.x;
var dy = target.y - this.location.y;
x = Math.max(Math.min(dx, 1), -1) + this.location.x;
y = Math.max(Math.min(dy, 1), -1) + this.location.y;
var p = GetBoardPiece(x, y);
if ((p>=0 || x<0 || y<0 || x>=galaxyMapSize.x || y>=galaxyMapSize.y) && p.type!=0)
{
if (dx==0) x = Math.floor(Math.random()*2)*2-1 + this.location.x;
else y = Math.floor(Math.random()*2)*2-1 + this.location.y;
p = GetBoardPiece(x, y);
// off board or occupied
if (p>=0 || x<0 || y<0 || x>=galaxyMapSize.x || y>=galaxyMapSize.y) return;
}
// update movement time
this.nextMove += this.moveRate;
this.location.x = x;
this.location.y = y;
this.status = 2;
}
}
// help functoin to convert from map to screen space
BoardPiece.prototype.screen = function(x, y)
{
var scaleX = canvas.width/(galaxyMapSize.x+2);
var scaleY = canvas.height/(galaxyMapSize.y+2);
return {x:x*scaleX+scaleX*1.5, y:y*scaleY+scaleY*1.5};
}
// DRAW Board piece item with label text
BoardPiece.prototype.drawitem = function( pos, fading)
{
var ascii = ["starbase", "patrol", "group", "fleet"];
var light = (fading-1)*255;
light = Math.floor(Math.max(Math.min(light, 255), 0));
font = fontsize + (((fading-1.5)*40));
if (this.status < 2 || fading <=1.5) font = fontsize;
font*=0.75;
context.globalAlpha = fading>1 ? 1.0: fading;
context.font = font + 'pt Calibri';
context.fillStyle = 'rgb('+light+',255,'+light+')';
context.textAlign = "center";
context.fillText(ascii[this.type], pos.x, pos.y+font*1.5);
pos.y-=font;
switch(this.type)
{
case 0: // base
context.beginPath();
context.arc(pos.x, pos.y, font, 0, Math.PI, true);
context.moveTo(pos.x+font, pos.y);
context.quadraticCurveTo(pos.x, pos.y+font*0.75, pos.x-font, pos.y);
context.fill();
context.strokeStyle='#80ff80';
context.stroke();
break;
case 1: // patrol fighter
context.beginPath();
context.arc(pos.x, pos.y, font*0.75, 0, Math.PI*2, false);
context.fill();
context.moveTo(pos.x-font, pos.y-font);
context.lineTo(pos.x-font, pos.y+font);
context.moveTo(pos.x+font, pos.y-font);
context.lineTo(pos.x+font, pos.y+font);
context.strokeStyle='#80ff80';
context.stroke();
break;
case 2: // group cruiser
context.beginPath();
context.moveTo(pos.x-font, pos.y);
context.lineTo(pos.x-font/2, pos.y-font/2);
context.lineTo(pos.x+font*2, pos.y);
context.lineTo(pos.x-font/2, pos.y+font/2);
context.closePath();
context.fill();
context.lineTo(pos.x+font*2, pos.y);
context.strokeStyle='#80ff80';
context.stroke();
context.beginPath();
context.arc(pos.x-font*1.5, pos.y-font*0.9, font*0.25, 0, Math.PI*2, false);
context.arc(pos.x-font*2, pos.y+font*0.75, font*0.25, 0, Math.PI*2, false);
context.fill();
break;
case 3: // fleet / basestar
context.beginPath();
context.moveTo(pos.x-font, pos.y-font/2);
context.quadraticCurveTo(pos.x, pos.y-font, pos.x+font, pos.y-font/2);
context.lineTo(pos.x-font, pos.y+font/2);
context.quadraticCurveTo(pos.x, pos.y+font, pos.x+font, pos.y+font/2);
context.closePath();
context.fill();
context.strokeStyle='#80ff80';
context.stroke();
break;
}
}
function CountHostiles(loc)
{
var count = 0;
for (var i=-1; i<2; i++)
{
for (var j=-1; j<2; j++)
{
var p = GetBoardPiece(loc.x+i, loc.y+j);
if (p>=0 && p.type!=0) count++;
}
}
return count;
}
function BoardSetup()
{
// clear board
targetBase = 0;
boardPieces = [];
var x, y, border = 1;
for (var i =0; i<4; i++) // types
{
for (var j=0; j<4; j++) // counts
{
do
{
x = Math.floor(Math.random() * (galaxyMapSize.x-border*2))+border;
y = Math.floor(Math.random() * (galaxyMapSize.y-border*2))+border;
var p = GetBoardPiece(x, y);
} while(p>=0);
boardPieces.push(new BoardPiece(x, y, i));
}
border = 0;
}
}
function SetShipLocation(x, y)
{
shipLocation.x = Math.floor(x);
shipLocation.y = Math.floor(y);
shipPosition.x = x;
shipPosition.y = y;
}
function SetWarpPoint(x, y, lock)
{
if (warpLocked == true && lock == false) return;
warpLocked = lock;
warpLocation.x = x;
warpLocation.y = y;
warpAnim = 0;
}
function ClearWarpPoint()
{
warpLocked = false;
}
// many thanks to http://www.sonic.net/~nbs/star-raiders/docs/v.html
var distanceTable =[100,130,160,200,230,500,700,800,900,1200,1250,1300,1350,1400,1550,1700,1840,2000,2080,2160,2230,2320,2410, 2500,9999];
function ShipCalculateWarpEnergy(sx, sy)
{
// convert sx and sy into board-coords
var scaleX = canvas.width/(galaxyMapSize.x+2);
var scaleY = canvas.height/(galaxyMapSize.y+2);
var x = (sx/scaleX) - 1;
var y = (sy/scaleY) - 1;
var dx = shipPosition.x - x;
var dy = shipPosition.y - y;
// location
var distance = Math.sqrt(dx*dx+dy*dy);
var di = Math.floor(distance);
if (di>23) di=23;
var energy = distanceTable[di];
energy+= (distanceTable[di+1]-energy) * (distance-di);
return Math.floor(energy);
}
// initialization
function init()
{
// setup canvas and context
canvas = document.getElementById('test');
context = canvas.getContext('2d');
// set canvas to be window dimensions
resize();
// create event listeners
canvas.addEventListener('mousemove', mouseMove);
canvas.addEventListener('click', mouseClick);
window.addEventListener('resize', resize);
// initialze variables
SetShipLocation(galaxyMapSize.x*0.5, galaxyMapSize.y*0.5);
// initial ping
shipPing.x = shipPosition.x;
shipPing.y = shipPosition.y;
// populate map
BoardSetup();
var d = new Date();
gameStart = d.getTime();
}
// long range scan
// galactic scan
function renderGalacticScanner()
{
var scaleX = canvas.width/(galaxyMapSize.x+2);
var scaleY = canvas.height/(galaxyMapSize.y+2);
context.beginPath();
for (var i=0; i<=galaxyMapSize.x; i++)
{
context.moveTo(scaleX*(i+1), scaleY);
context.lineTo(scaleX*(i+1), scaleY*(galaxyMapSize.y+1));
}
context.strokeStyle = '#c0c0c0';
context.lineWidth = 4;
context.stroke();
context.beginPath();
for (var j=0; j<=galaxyMapSize.y; j++)
{
context.moveTo(scaleX, scaleY*(j+1));
context.lineTo(scaleX*(galaxyMapSize.x+1), scaleY*(j+1));
}
context.strokeStyle = '#c0c0c0';
context.lineWidth = 4;
context.stroke();
// ping every 5 seconds
var d = new Date();
var currentTime = d.getTime();
var distance = ((currentTime - gameStart)%10000)/10000;
pingRadius = distance * canvas.width/2;
context.globalCompositeOperation='source-over';
var shipX = shipPing.x * scaleX + scaleX;
var shipY = shipPing.y * scaleY + scaleY;
// update map with locations of ships
for (var b=0; b<boardPieces.length; b++)
{
// fade in and out board pieces as the ping passes over them
boardPieces[b].render(shipX, shipY, pingRadius);
}
context.globalCompositeOperation='lighter';
context.globalAlpha = 1;
context.beginPath();
var gradient = context.createRadialGradient(shipX, shipY, pingRadius*0.5, shipX, shipY, pingRadius*2);
gradient.addColorStop(0, 'rgba(128, 255, 128,0)');
gradient.addColorStop(1, 'rgba(128, 255, 128,'+(1-distance)+')');
context.fillStyle = gradient;
context.arc(shipX, shipY, pingRadius*2, Math.PI*2, false);
context.fill();
// render hyperspace location
context.beginPath();
clampX = Math.max(Math.min(warpLocation.x, scaleX*(galaxyMapSize.x+1)), scaleX);
clampY = Math.max(Math.min(warpLocation.y, scaleY*(galaxyMapSize.y+1)), scaleY);
context.moveTo(clampX, scaleY)
context.lineTo(clampX, scaleY*(galaxyMapSize.y+1));
context.moveTo(scaleX, clampY);
context.lineTo(scaleX*(galaxyMapSize.x+1), clampY);
if (warpLocked)
{
warpAnim=(warpAnim+1)%(Math.sqrt(scaleX*scaleX+scaleY*scaleY)*0.5);
context.arc(clampX, clampY, warpAnim, 0, Math.PI*2, false);
}
context.strokeStyle = '#c0ffc0';
context.lineWidth = 1;
context.stroke();
}
// input functions
function mouseMove(event)
{
var rect = canvas.getBoundingClientRect();
mouseX = event.clientX - rect.left;
mouseY = event.clientY - rect.top;
SetWarpPoint(mouseX, mouseY, false);
}
function mouseClick()
{
var scaleX = canvas.width/(galaxyMapSize.x+2);
var scaleY = canvas.height/(galaxyMapSize.y+2);
var x = (mouseX/scaleX)-1;
var y = (mouseY/scaleY)-1;
//SetShipLocation(x, y);
// lock in warp point
if (!warpLocked)
SetWarpPoint(mouseX, mouseY, true);
else
ClearWarpPoint();
}
function resize()
{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// compute centre of screen
centreX = canvas.width/2;
centreY = canvas.height/2;
fontsize = 21;
// resize font based on canvas size
mapScale.x = canvas.width/(galaxyMapSize.x+2);
mapScale.y = canvas.height/(galaxyMapSize.y+2);
do
{
fontsize-=0.1;
context.font = fontsize + 'pt Calibri';
var fits = context.measureText('group');
}while((fits.width+20>mapScale.x || mapScale.y<fontsize*4) && fontsize >5);
}
function renderInformation()
{
var scaleX = canvas.width/(galaxyMapSize.x+2);
var scaleY = canvas.height/(galaxyMapSize.y+2);
var i = GetBoardPieceScreen(mouseX, mouseY);
var targets = 'empty';
if (i>0)
{
targets = boardPieces[i].numTargets;
if (targets == 0) targets = 'starbase';
}
context.globalAlpha = 1.0;
context.font = '20pt Calibri';
context.fillStyle = 'rgb(255,255,0)';
context.textAlign = "left";
context.fillText('Targets: ' + targets, mapScale.x, canvas.height-15);
var fuel = ShipCalculateWarpEnergy(mouseX, mouseY);
context.font = '20pt Calibri';
context.fillStyle = 'rgb(255,255,0)';
context.textAlign = "center";
context.fillText('Warp Energy: ' + fuel, canvas.width/2, canvas.height-15);
if (warpLocked)
{
var clampX = Math.max(Math.min(warpLocation.x, mapScale.x*(galaxyMapSize.x+1)), mapScale.x);
var clampY = Math.max(Math.min(warpLocation.y, mapScale.y*(galaxyMapSize.y+1)), mapScale.y);
var fuel = ShipCalculateWarpEnergy(clampX, clampY);
context.textAlign = "centre";
context.font = '11pt Calibri';
context.fillText('Energy: ' + fuel, clampX, clampY-12);
}
// render our ship
context.beginPath();
var x = shipPosition.x*scaleX+scaleX;
var y = shipPosition.y*scaleY+scaleY;
context.arc(x, y, fontsize*0.5, 0, Math.PI*2);
context.fill();
context.moveTo(x, y-fontsize*1.5);
context.lineTo(x-fontsize, y+fontsize*1.5);
context.lineTo(x, y);
context.lineTo(x+fontsize, y+fontsize*1.5);
context.closePath();
context.fill();
}
function renderStarDate()
{
var d = new Date();
var currentTime = d.getTime();
var gameTime = currentTime - gameStart;
var decimalTime = gameTime / 60000;
context.font = '20pt Calibri';
context.fillStyle = 'rgb(255,255,0)';
context.textAlign = "right";
leadingzero = decimalTime<10 ? '0':'';
context.fillText('StarDate: ' + leadingzero + decimalTime.toFixed(2), canvas.width-mapScale.x, canvas.height-15);
}
// rendering functions
function render()
{
context.fillStyle = 'black';
context.clearRect(0, 0, canvas.width, canvas.height);
renderGalacticScanner();
renderInformation();
renderStarDate();
context.globalAlpha = 1.0;
context.font = '20pt Calibri';
context.fillStyle = 'rgb(255,255,255)';
context.textAlign = "center";
context.fillText('Galactic Scanner', canvas.width/2, 30);
}
// movement functions
function update()
{
var d = new Date();
var currentTime = d.getTime();
var gameCycle = Math.floor((currentTime - gameStart)/10000);
if (gameCycle!=lastCycle)
{
for (var b=0; b<boardPieces.length; b++)
{
boardPieces[b].move(gameCycle);
}
shipPing.x = shipPosition.x;
shipPing.y = shipPosition.y;
}
lastCycle = gameCycle;
// check starbase health and trigger destruction
var base = boardPieces[targetBase];
var count = CountHostiles(base.location);
if (count>=4) // trigger kaboom
{
if (base.nextMove==0) base.nextMove = gameCycle + 2;
if (base.nextMove<gameCycle)
{
targetBase++;
base.status = 0;
boardPieces.push(new BoardPiece(base.location.x, base.location.y, 1));
}
}
else
{
base.nextMove = 0;
}
if (targetBase == 4 ) // zylons win
{
BoardSetup();
}
CheckButtons(mouseX, mouseY);
}
// per frame tick functions
function animate()
{
frameCount++;
// movement update
update();
// render update
render();
// trigger next frame
requestAnimationFrame(animate);
}
// array to hold all buttons
var buttons = [];
// checks hits against the button list
function CheckButtons(x, y)
{
for (var i=0; i<buttons.length; i++)
{
buttons[i].hit(x,y);
}
}
// renders the buttons
function RenderButtons()
{
for (var i=0; i<buttons.length; i++)
{
buttons[i].render();
}
}
function Button(x, y, w, h, name, callback)
{
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.name = name;
this.callback = callback;
// add button to stack
buttons.push(this);
}
Button.prototype.render = function()
{
// fill a rect
context.beginPath();
context.rect(this.x, this.y, this.w, this.h);
context.fillStyle = 'rgba(0, 0, 0, 0.5)';
context.fill();
context.lineWidth = 2;
context.strokeStyle = 'rgba(120, 120, 120, 0.5)';
context.stroke();
context.moveTo(this.x, this.y);
context.font = '20pt Calibri';
context.fillStyle = 'rgba(255,255,255, 1)';
context.textAlign = "center";
context.fillText(this.name, this.x+this.w/2, this.y+this.h-9);
}
Button.prototype.hit = function(x, y)
{
if (x>=this.x && x<this.x+this.w && y>this.y && y<this.y+this.h)
{
// hit
return this.callback();
}
return false;
}
// entry point
init();
animate();
body{
background:#000000
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment