/*
termlib-invaders
a termlib.js application
(c) mass:werk (N. Landsteiner) 2008
based on JS/UIX invaders (c) mass:werk (N. Landsteiner) 2005
all rights reserved
v1.11
termlib-invaders is a simple text-mode invaders game.
requires termlib.js 1.4 or better .
requires a terminal set to at least 68 cols x 20 rows.
uses namespace 'invaders' in object 'env' of the termlib.js Terminal instance.
the entire code is contained inside the single global object 'TermlibInvaders'.
(the massive 'apply'-syntax is a bit awkward, but this is, which does it.)
example call from inside a termlib.js Terminal instance's handler:
if ( TermlibInvaders.start(this) ) {
return;
}
else {
// oops, terminal doesn't meet the requirements
this.write('Sorry, invaders failed.');
}
call with a max game screen of 80 cols x 25 rows:
TermlibInvaders.start(this, 80, 25);
*/
TermlibInvaders = {
version: '1.11 (original)',
start: function( termref, maxcols, maxrows ) {
if (!Terminal || !termref || parseFloat(termref.version)<1.4) {
// color support required
return false;
}
if (termref.conf.cols<68 || termref.conf.rows<20) {
// required min. dimensions: 68 x 20
return false;
}
if (parseFloat(termref.version)>=1.5) {
// backup the screen
termref.backupScreen();
}
var gc=TermlibInvaders.getStyleColorFromHexString;
termref.env.invaders= {
termref: termref,
maxCols: maxcols || 0,
maxRows: maxrows || 0,
charMode: termref.charMode,
paused: false,
moveAll: true,
// setup values
rows: 3,
cols: 5,
maxBombs: 3,
bombRate: 0.005,
timer: null,
delay: 50,
newWaveDelay: 1500,
textColor: gc(TermlibInvaders.textColor),
invaderColor: gc(TermlibInvaders.invaderColor),
invaderHitColor: gc(TermlibInvaders.invaderHitColor),
bombColor: gc(TermlibInvaders.bombColor),
blockColor: gc(TermlibInvaders.blockColor),
statusColor: gc(TermlibInvaders.statusColor),
shotColor: gc(TermlibInvaders.shotColor),
shipColor: gc(TermlibInvaders.shipColor),
shipHitColor: gc(TermlibInvaders.shipHitColor),
alertColor: gc(TermlibInvaders.alertColor),
frameColor: gc(TermlibInvaders.frameColor)
};
TermlibInvaders.init.apply(termref);
return true;
},
// color definitions (colors will match nearest webcolor)
textColor: '#00cc00',
invaderColor: '#00cc00',
invaderHitColor: '#66aa66',
bombColor: '#cccc00',
blockColor: '#bbbb00',
statusColor: '#00bb00',
shotColor: '#aacc00',
shipColor: '#aacc00',
shipHitColor: '#aaaaaa',
alertColor: '#ff9900',
// frame definitions
// the frame is only drawn, if the terminal is bigger
// than the game's max dimensions. if you do not want
// to draw any frames leave 'frameChar' empty ('').
frameChar: '*',
frameColor: '#777777',
// global assets
sprites: [
' ',' (^o^) ',' (^-^) ',' ( ) ',' ( )',
' (=^=) ',' ((.)) ',' ( . ) ','( (.) )',
' ( . ) ','( . )',' . ',' '
],
splashScreen: [
'%c(#0c0)%+i** T E R M L I B - I N V A D E R S **%-i',
'',
'',
'%c(#0c0)Instructions:',
'',
'%c(#0c0) use cursor LEFT and RIGHT to move',
'%c(#0c0) (or use vi movements alternatively)',
'%c(#0c0) press space to fire',
'%c(#0c0)',
'%c(#0c0) press "q" or "esc" to quit,',
'%c(#0c0) "p" to pause the game.',
'',
'',
'%c(#0c0)%+r press any key to start the game %-r',
'',
'',
'%c(#0c0)(c) mass:werk N.Landsteiner 2005-2008',
'%c(#0c0)based on JS/UIX-Invaders by mass:werk'
],
splashScreenWidth: 40, // width of splash-screen in chars
gameOverScreen: [
' ',
'%c(#f90) G A M E O V E R ! ',
' ',
'%c(#0c0) press any key to restart,',
'%c(#0c0) "q" or "esc" for quit. ',
' '
],
gameOverScreenWidth: 26,
invObject: function(y,x) {
this.x=x;
this.y=y;
this.status=1;
},
// handlers:
// 'this' refers to ther termlib.js Terminal instcance
// 'inv' refers to the TermlibInvaders instance
init: function() {
var inv=this.env.invaders;
// back up the terminal state
inv.termHandler=this.handler;
if (this.maxLines != this.conf.rows) {
inv.charBuf=new Array();
inv.styleBuf=new Array();
for (var r=this.conf.rows-1; r>=this.maxLines; r--) {
var cb=new Array();
var sb=new Array();
var tcb=this.charBuf[r];
var tsb=this.styleBuf[r];
for (var c=0; c=this.conf.cols; c++) {
cb[c]=tcb[c];
sb[c]=tsb[c];
}
inv.charBuf.push(cb);
inv.styleBuf.push(sb);
}
this.maxLines = this.conf.rows;
}
if (this.maxCols!=this.conf.cols) {
inv.termMaxCols=this.maxCols;
this.maxCols=this.conf.cols;
}
else {
inv.termMaxCols=-1;
}
inv.keyRepeatDelay1=this.keyRepeatDelay1;
inv.keyRepeatDelay2=this.keyRepeatDelay2;
this.keyRepeatDelay1=this.keyRepeatDelay2=inv.delay-1;
// output init-screen
this.clear();
TermlibInvaders.writeToCenter.apply(this, [TermlibInvaders.splashScreen, TermlibInvaders.splashScreenWidth]);
this.charMode=true;
this.lock=false;
this.handler=TermlibInvaders.splashScreenHandler;
},
splashScreenHandler: function() {
var key = this.inputChar;
if (key==this.termKey.ESC || key==113) {
TermlibInvaders.exit.apply(this);
return;
}
// setup the game
var inv=this.env.invaders;
TermlibInvaders.buildScreen.apply(this);
inv.maxRight=inv.width-7;
inv.wave=0;
inv.score=0;
var d=Math.floor(inv.width/5);
var d1=Math.floor((inv.width-3*d)/2);
inv.blockpos=new Array();
for (var i=0; i<4; i++) {
var x=d1+i*d;
inv.blockpos.push(x-1);
inv.blockpos.push(x);
inv.blockpos.push(x+1);
}
TermlibInvaders.newWave.apply(this);
},
newWave: function() {
this.clear();
var inv=this.env.invaders;
inv.wave++;
var s='W A V E # '+inv.wave;
var c=Math.floor((this.conf.cols-s.length)/2);
var r=Math.floor((this.conf.rows-3)/2)-4;
this.typeAt(r, c, s, 4 | inv.textColor);
this.typeAt(r+2, c, 'Get ready ...', inv.textColor);
inv.timer=setTimeout(function() { TermlibInvaders.waveStart.apply(inv.termref); }, inv.newWaveDelay);
this.lock=true;
},
waveStart: function() {
var inv=this.env.invaders;
clearTimeout(inv.timer);
this.clear();
TermlibInvaders.drawFrame.apply(this);
inv.smove=0;
inv.phase=1;
inv.dir=1;
inv.population=0;
inv.shot= inv.shotX= 0
inv.over=false;
inv.bombs=0;
inv.invrows=(inv.wave==2)? inv.rows+1:inv.rows;
inv.invcols=(inv.wave<=2)? inv.cols:inv.cols+1;
var changed=inv.changed=new Array();
inv.inv=new Array();
for (var r=0; r0), shotx=inv.shotX, shoty=inv.shipY-inv.shot;
var bomb= inv.bomb,block=inv.block, blocky=inv.blockY, isblockrow=false;
var sprites=TermlibInvaders.sprites, invclr=inv.invaderColor;
var moveAll=inv.moveAll;
if (shot && inv.shot>1) TermlibInvaders.drawSprite(term, shoty+1,shotx,' ',0);
for (var r=0; ri.x && shotx<(i.x+6)) {
i.status=2;
inv.population--;
inv.score+=50;
inv.shot=shot=0;
TermlibInvaders.drawSprite(term, i.y,i.x, sprites[3], inv.invaderHitColor);
}
else if (moveAll) {
TermlibInvaders.drawSprite(term, i.y,i.x, sprites[inv.phase], invclr );
if (i.yinv.shipX && b.x<(inv.shipX+6)) {
inv.over=true;
}
else {
b=bomb[n]=null;
inv.bombs--;
}
}
else if (shot) {
if ((b.y==shoty || b.y==shoty+1) && Math.abs(b.x-shotx)<2) {
b=bomb[n]=null;
inv.bombs--;
inv.score+=5;
inv.shot=shot=0
}
}
if (b) {
TermlibInvaders.drawSprite(term, b.y,b.x, 'V', inv.bombColor);
b.y++;
}
}
}
if (shot) {
if (shoty>0) {
if (shoty==blocky && inv.block[shotx]) {
inv.block[shotx]=false;
TermlibInvaders.drawSprite(term, blocky,shotx, ' ', 0);
inv.shot=0;
}
else {
TermlibInvaders.drawSprite(term, shoty,shotx, '|', inv.shotColor);
inv.shot++;
}
}
else {
inv.shot=0;
}
}
if (moveAll) {
inv.invbottom=bb;
}
else {
inv.invleft=bl;
inv.invright=br;
if (dir==-1 && bl==0) {
inv.dir=1;
}
else if (dir==1 && br==inv.maxRight) {
inv.dir=-1;
}
inv.phase=(inv.phase==1)? 2:1;
}
// restore any overwritten blocks
if (isblockrow) {
var blockpos=inv.blockpos;
for (var i=0; i=TermlibInvaders.sprites.length) {
TermlibInvaders.writeToCenter.apply(this, [TermlibInvaders.gameOverScreen, TermlibInvaders.gameOverScreenWidth]);
this.lock=false;
this.handler=TermlibInvaders.splashScreenHandler;
}
else {
TermlibInvaders.drawSprite(this, inv.shipY,inv.shipX, TermlibInvaders.sprites[inv.phase++], inv.shipHitColor);
this.redraw(inv.top+inv.shipY);
inv.timer=setTimeout(function() { TermlibInvaders.gameOver.apply(inv.termref); }, inv.delay*3);
}
},
keyHandler: function() {
var inv=this.env.invaders;
var key=this.inputChar;
if (key==this.termKey.ESC || key==113) {
// esc or q
TermlibInvaders.exit.apply(this);
}
else if (key==112 || inv.paused) {
// p or paused
TermlibInvaders.pause.apply(this);
}
// cursor movements
else if (key==this.termKey.LEFT || key==104) {
// left
if (inv.shipX>0) inv.smove=-1;
return;
}
else if (key==this.termKey.RIGHT || key==108) {
// right
if (inv.shipX0 && this.maxCols>inv.maxCols) {
inv.width = inv.maxCols;
inv.left= Math.floor((this.maxCols-inv.maxCols)/2);
inv.right= inv.left+inv.width;
}
else {
inv.width= inv.right= this.maxCols;
inv.left=0;
}
if (inv.maxRows>0 && this.maxLines>inv.maxRows) {
inv.height = inv.maxRows;
inv.top= Math.floor((this.maxLines-inv.maxRows)/2);
inv.bottom=inv.top+inv.height;
}
else {
inv.height= inv.bottom= this.maxLines;
inv.top=0;
}
inv.shipCenter=Math.floor((inv.width-3)/2);
inv.statusRow=inv.bottom-1;
inv.maxRight=inv.width-7;
inv.shipY=inv.height-3;
inv.bombMaxY=inv.height-7;
inv.blockY=inv.height-5;
},
drawFrame: function() {
var inv=this.env.invaders;
if (TermlibInvaders.frameChar) {
var r0, r1, i;
var c = TermlibInvaders.frameChar.charCodeAt(0);
var cc= inv.frameColor;
if (inv.height+1=1.5) {
// backup the screen
this.restoreScreen();
}
else {
// reset the terminal "manually"
this.clear();
this.handler=inv.termHandler;
if (inv.charBuf) {
for (var r=0; r=0) this.maxCols=inv.termMaxCols;
this.charMode=inv.charMode;
}
this.keyRepeatDelay1=inv.keyRepeatDelay1;
this.keyRepeatDelay2=inv.keyRepeatDelay2;
delete inv.termref;
this.lock=false;
// delete instance and leave with a prompt
delete this.env.invaders;
this.prompt();
},
getStyleColorFromHexString: function(clr) {
// returns a stylevector for the given color-string
var cc=Terminal.prototype.globals.webifyColor(clr.replace(/^#/,''));
if (cc) {
return Terminal.prototype.globals.webColors[cc]*0x10000;
}
return 0;
}
};
// eof