var g_maxImg=40, g_imglist=[], g_doc=document, g_rnd=Math.random, g_frameDelay=50, g_deadCannon, g_frame, g_score, g_generateTargets=0, g_paused=0, g_targetList=0, g_cityList=0, g_cityListLen=0, g_idling=0, g_active, g_none=-1; var imgpre=''; var imggrid={ lores: [ [ 5, 5, 'b'], // boom [71, 22, 'c'], // city [71, 22, 'd'], // dead city [ 7, 14, 'm'], // missile [30, 13, 't'], // target [71, 14, 'm'], // launcher (cannon) [ 5, 5, 'g'] // game over banner ] , hires: [ [ 5, 5, 'b'], [71, 22, 'c'], [71, 22, 'd'], [38, 50, 'm'], [54, 13, 't'], [71, 14, 'l'], [ 5, 5, 'g'] ] }; // utility functions function setimg(t,s) { t.imgnum=s; var i = imggrid[imgpre][s]; t.obj.width = i[0]; t.obj.height = i[1]; s = i[2]; t.obj.src = imgpre + '/' + s + '.gif'; } function getimg(t,s) { t.id = g_none; // find a free slot in the imglist for (var i in g_imglist) if (g_imglist[i]==g_none) { t.id = i; break; } // if we didn't find one to re-use, add on to the end of the list if (t.id==g_none) if (g_imglist.length<g_maxImg) t.id = g_imglist.length; else return 0; // Cannot allocate new img // set up this img item t.obj = g_doc.getElementById('img.'+t.id); setimg(t,s); t.dead = 0; return 1; } function setrad(t, myFrame, halfFrame, x, y) { var d=halfFrame*halfFrame - (myFrame-halfFrame)*(myFrame-halfFrame); var myobj = t.obj; myobj.width=d*x; myobj.height=d*y; myobj.style.top=t.y-(myobj.height/2)+'px'; myobj.style.left=t.x-(myobj.width/2)+'px'; show(t,1); return t.obj.height; } function show(t,b) { if (!t) t=this; t.obj.style.visibility=b?'visible':'hidden'; if (!b) g_imglist[t.id]=g_none; } function interp() { var t=this; var param = (g_frame-t.startFrame) / (t.endFrame-t.startFrame); if (param>1) param=1; t.x = (t.endX-t.startX)*param + t.startX; t.y = (t.endY-t.startY)*param + t.startY; var myobj = t.obj; myobj.style.left = t.x - (myobj.width / 2) +'px'; myobj.style.top = t.y - (myobj.height / 2) +'px'; show(t, 1); if (param >= 1) t.draw = t.onArrival; } function setInterp(t, startX, startY, endX, endY, frameCount, onArrival) { t.x=startX; t.y=startY; t.startX=startX; t.startY=startY; t.endX=endX; t.endY=endY; t.startFrame = g_frame; t.endFrame = g_frame + frameCount; t.draw = interp; t.onArrival = onArrival; } function none() {} ///// city object function city_die() { var t=this; if (!t.dead) { t.dead=1; setimg(t,2); // imgnum 2 is city if (--g_cityListLen < 1) { new GameOverBanner(); } else { // remove from linked list of cities if (t.next) t.next.prev = t.prev; if (t.prev) t.prev.next = t.next; if (g_cityList==t) g_cityList = t.next; } if(t.isTheCannon && !g_deadCannon) { show(t,0); g_deadCannon=1; // dead cannon } } } function setGT() { g_generateTargets=1; // start generating targets this.draw=none; //window.status = ""; } function City(x,y,isTheCannon) { var t=this; // methods t.die=city_die; // members setInterp(t, x, 0, x, y, 20, setGT); t.isTheCannon=isTheCannon; // add to linked list t.next = g_cityList; t.prev = 0; if (g_cityList) g_cityList.prev = t; g_cityList = t; // get img if(t.isTheCannon) getimg(t,5); // imgnum 5 is 'launcher' (cannon) else getimg(t,1); // imgnum 1 is 'city' this.draw(); show(t,1); // register g_imglist[t.id]=t; g_cityListLen++; // increment city count } ///// target object -- incoming alien bad guy function target_die() { var t=this; show(t,0); new Boom(t.x, t.y); g_score+=g_cityListLen; // win points for target dying (one point for each city) // remove from linked list of targets if (t.next) t.next.prev = t.prev; if (t.prev) t.prev.next = t.next; if (g_targetList==t) g_targetList = t.next; } function Target() { var t=this; // methods t.die=target_die; // get img if (getimg(t,4) && g_cityListLen>0) // imgnum 4 is 'target' { // pick a target var c=g_rnd()*g_cityListLen; var i=g_cityList; for(; c>=1; i=i.next) --c; // head towards target setInterp(t, g_rnd()*600, 0, i.x, i.y, g_rnd()*80+60-g_frame*.05, target_die); // add to linked list t.next = g_targetList; t.prev = 0; if (g_targetList) g_targetList.prev = t; g_targetList = t; // register g_imglist[t.id]=t; } } ///// missile object function missile_die() { show(this, 0); new Boom(this.x,this.y); } function Missile(x,y) { var t=this; // methods t.die=missile_die; // get img // don't launch if not gen targets // See if the cannon is dead if(g_generateTargets && !g_deadCannon && getimg(t,3)) // imgnum 3 is 'missile' { // members var startX = 340; var startY = 427; setInterp(t, startX, startY, x, y, Math.sqrt((startX-x)*(startX-x) + (startY-y)*(startY-y))*.05, missile_die); // register g_imglist[t.id]=t; g_score--; // lose a point for firing a missile } } ///// boom object (explosions) function boom_draw() { var t=this; var myFrame=g_frame-t.startFrame; var halfFrame=13; // frame count for half-explosion var d=setrad(t, myFrame, halfFrame, .5, .5); var xd, yd, i; // collision detect var r2=d*d/4; for (i = g_targetList; i; i=i.next) { xd=i.x-t.x; yd=i.y-t.y; if (xd*xd+yd*yd < r2) i.die(); } if (!t.ck && myFrame >= halfFrame) { t.ck = 1; for (i = g_cityList; i; i=i.next) { xd=i.x-t.x; yd=i.y-t.y; if (xd*xd+yd*yd < r2) i.die(); } } if (myFrame>halfFrame*2) show(t,0); } function Boom(x,y) { var t=this; // methods t.draw=boom_draw; // get img if (getimg(t,0)) // imgnum 0 is 'boom' { // members t.x=x; t.y=y; t.startFrame=g_frame; t.ck=0; // killed any cities yet? // register g_imglist[t.id]=t; } } ///// game over object (GameOverBanner) function gameOver_draw() { var t=this; var myFrame=g_frame-t.startFrame; var dur=20; // duration of grow if (myFrame>dur) myFrame=dur; setrad(t, myFrame, dur, 1.4, .2); } function GameOverBanner() { var t=this; // methods t.draw=gameOver_draw; // get img if (getimg(t,6)) // imgnum 6 is a game over banner { // members t.startFrame=g_frame; t.dead=1; t.x=350; t.y=200; // register g_imglist[t.id]=t; } } ///// score object function score_draw() { if (this.ent.value != g_score) this.ent.value=g_score; } function Score() { var t=this; t.obj=g_doc.getElementById("sform"); t.obj.width=300; t.obj.height=50; t.ent=g_doc.getElementById("sbox"); setInterp(t, 300, 0, 300, 500, 20, score_draw); t.dead=1; t.imgnum=0; t.id=0; g_imglist[t.id] = t; show(t,1); } ///// main functions var synchErr=0, synchA=0, synchB=0; var nextTargetFrame=0; function idle() { // set g_idling to zero to stop the event loop // NOTE: Use g_paused for pausing, not g_idling if (!g_idling) return; // Try to detect slow runtime engine synchA=g_frame-g_frameDelay/50; if (synchA > synchB) { //if (!synchErr) window.status = "SYNCH"; synchErr=1; } if (!synchErr) setTimeout('idle()',g_frameDelay); if (!g_paused) // skip if game is paused { // draw cities, missiles, and booms var deadcount=0; g_active=0; for (var i in g_imglist) if (g_imglist[i]!=g_none) { ++g_active; deadcount += g_imglist[i].dead; g_imglist[i].draw(); } if (deadcount >= g_active) { if (!g_active) { g_idling = 0; // everything's dead and gone return; } g_generateTargets=0; // stop generating targets for (var j in g_imglist) if (g_imglist[j]!=g_none) { g_imglist[j].dead = 0; setInterp(g_imglist[j], g_imglist[j].x, g_imglist[j].y, g_imglist[j].x, -20, 50, show); } } // generate targets if (g_generateTargets && g_frame >= nextTargetFrame) { new Target(); nextTargetFrame = g_frame+(g_rnd()*30); } // increment frame g_frame += g_frameDelay / 50; } if (synchErr) setTimeout('idle()',60); else setTimeout('synchB=' + g_frame,5); } function click(e) { if (!g_paused) // skip if game is paused { if (e) new Missile(e.clientX,e.clientY); else new Missile(event.x,event.y); } } function set_hires(t) { imgpre=t?'hires':'lores'; for (var i in g_imglist) if (g_imglist[i] != g_none && g_imglist[i].imgnum) setimg(g_imglist[i], g_imglist[i].imgnum); } var typed=''; function dokey(e) { var key = e ? e.which : event.keyCode; key = String.fromCharCode(key); //window.status = "active: " + g_active + ", key: " + key; if (g_active) { if (key == 'P') g_paused = !g_paused; // toggle paused if (key == 'H') set_hires(1); if (key == 'L') set_hires(0); } else { typed += key; typed = typed.substring(typed.length-6); if (typed == 'KILLEM') { set_hires(0); startgame(); } /* commented out the following if block for the5k contest */ if (typed == 'MATROX') { set_hires(1); startgame(); } } } function startgame() { g_deadCannon=0; // long live the cannon g_score=0; g_frame=0; g_doc.onmousedown=click; new Score(); new City( 80,450,0); new City(210,450,0); new City(340,450,0); new City(340,427,1); // the cannon new City(470,450,0); new City(600,450,0); if (!g_idling) { g_idling = 1; idle(); } } function afterload(go, type) { g_doc.onkeydown = dokey; if (go) { set_hires(type||0); startgame(); } } /* function foo() { var str = ''; for (var i in g_imglist) { str += i + ": " + g_imglist[i] + "\n"; } alert (str); } */ function gamehtml() { var s=' style="position:absolute;visibility:hidden"'; for (var i=0; i<g_maxImg; ++i) { g_doc.write('<img id="img.' + i + '"' + s + ' />'); } g_doc.write('<form id="sform"'+s+'>Score: <input id="sbox" size="10"></form>'); } gamehtml();