HylZee : Game « Page Components « JavaScript DHTML






HylZee

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta http-equiv="Content-Style-Type" content="text/css">
<meta http-equiv="content-language" content="en">
<title>HylZee</title>
<!--META HTTP-EQUIV="Expires" CONTENT="Fri, Jan 01 1900 00:00:00 GMT"-->
<!-- commented out, seems to affect images META HTTP-EQUIV="Pragma" CONTENT="no-cache"-->
<!--META HTTP-EQUIV="Cache-Control" CONTENT="no-cache"-->
<meta name="author" content="peter.schaefer@gmail.com">
<META HTTP-EQUIV="Reply-to" CONTENT="peter.schaefer@gmail.com">
<meta name="generator" content="Code Monkey 1970.01.18(tm Peter Schaefer)">
<META NAME="description" CONTENT="HylZee - A DHTML puzzle game">
<meta name="keywords" content="HylZee DHTML javascript puzzle game huelsi tal pyr">
<META NAME="Creation_Date" CONTENT="09.10.2004">
<meta name="revisit-after" content="30 days">

<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" src="favicon.ico">

<!-- copyright 2004 peter schaefer -->
<!-- 
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-->

<style type="text/css">

b {font-size:16px;}

.map {color:white;}
.controlembossed {
        background: #66CC33;
        border-top-style:solid;
        border-right-style:solid;
        border-bottom-style:solid;
        border-left-style:solid;
        border-top-width: 1px;
        border-right-width: 1px;
        border-bottom-width: 1px;
        border-left-width: 1px;
        border-right-color: #99FF66;
        border-top-color: #99FF66;
        border-left-color: #666666;
        border-bottom-color: #666666;
}
a:link { color: #99FF66; }
a:hover { color: #FFFF66; }
a:active { color: #FF9966; }
a:visited { color: #66FF33; }

table,tbody,th,td,img,span {
        margin-left: 0px;
        margin-top: 0px;
        margin-right: 0px;
        margin-bottom 0px;
        padding-left: 0px;
        padding-top: 0px;
        padding-right: 0px;
        padding-bottom: 0px;
}

</style>

<script language="JavaScript" type="text/JavaScript">
<!--

// placement of map window
var map_top= 18;
var map_left= 20;

// grid size of map tiles
var cell_height=32;
var cell_width= cell_height;

// size of bullet graphics
var bullet_width= 12;
var bullet_height= 12;
var status_font= 18;

//status bar minimum width
var minStatusbarWidth= 10*32;

var tileset="default"

// number of space units/tile
var step_unit= 100;

// number of ticks/1 tile movement
var ticks_unit= 420;

// ticks/processing of mainLoop
//FIXME: this doesn't work with 40/240;
var theTick= 42;

// frequency of mainloop
var mainDelay= 20;

// which level to load
var lastLevel= "maps/test/devel-001.html";

// show shrinking animations
var shrink= true;

//continue running
var mainProceed= true;

var debug= false;

//
// debugall switches on most annoying debug output
//
var debugall= true;

var hideDebug= true || debug;

//input constants
var key_fire= 70;
var key_right= 39;
var key_down= 40;
var key_up= 38;
var key_left= 37;

//map object
var map= null;

//log repeat functionality
var log_lastMsg="";
var log_count=0;
var log_threshold=2;
var log_limit=3000;

//which codes signal start and home
var code_start='A';
var code_home ='H';

//
// arguments.caller has been deprecated
// So the stacktrace will not work in new browsers
//
function funcname(f) {
 var s = f.toString().match(/function (\w*)/)[1];
 if ((s == null) || (s.length==0)) return "anonymous";
 return s;
}

function stacktrace() {
 if(!arguments.caller) {
        return "no stacktrace available";
 }

 var s = "";
 for (var a = arguments.caller; a !=null; a = a.caller) {
   s += "->"+funcname(a.callee) + "\n";
   if (a.caller == a) {s+="*"; break;}
 }
 return s;
}


function debugMsg(message) {
         if(debug) {
           //window.status= message;
           if(log_lastMsg!=message) {
              log_lastMsg=message;
              log_count=0;
              log_threshold=2;
              message="<br>"+message;
           }else {
               log_count++;
               if(log_count>=log_threshold) {
                  message= "<br>last message repeated "+log_count+" more times.";
                  log_threshold<<=1;
                  log_count=0;
               }else {
                  message="";
               }
           }

           if(message) {
               var debugDiv= document.getElementById("debugoutput");
               if(debugDiv){
                 var startpos= (debugDiv.innerHTML.length>log_limit)?(debugDiv.innerHTML.length-log_limit):0;
                 debugDiv.innerHTML= debugDiv.innerHTML.substring(startpos, debugDiv.innerHTML.length)+message;
               }else if(debug && debugall) {
                 alert("debugMsg "+log_lastMsg+" (page isn't loaded yet/no debug window found)");
                 debugall= false;
               }

           }
         }
}

function debugEval(test) {
         debugMsg(test+"="+eval(test));
}


function showLayer(layer) {
        var div= document.getElementById(layer);
        if(div)
         div.style.visibility = 'visible';
}

function hideLayer(layer) {
        var div= document.getElementById(layer);
        if(div)
         div.style.visibility = 'hidden';
}

// the fudge factor is used to work around math rounding errors
var fudge= 0.000001;


function toGridX(x) {
        return  Math.floor(x/step_unit+fudge);
}

function toGridY(y) {
        return  Math.floor(y/step_unit+fudge);
}

function toScreenX(x) {
        return  map_left+Math.floor(x*cell_width+fudge);
}

function toScreenY(y) {
        return  map_top+Math.floor(y*cell_height+fudge);
}

var protagonist=null;
var gameState=null;

var Coordinate_toString= function() {
        return '('+this.x+','+this.y+')';
}

function Coordinate_copy() {
        return new Coordinate(this.x,this.y);
}

function Coordinate_clip() {
        var clipped= false;
        if( this.x<-fudge ) {
          this.x=0;
          clipped= true;
        } else if( this.x-fudge>step_unit*(map.xmax-1) ) {
          this.x= step_unit*(map.xmax-1);
          clipped= true;
        }

        if( this.y<-fudge ) {
          this.y=0;
          clipped= true;
        } else if( this.y-fudge>step_unit*(map.ymax-1) ) {
          this.y= step_unit*(map.ymax-1);
          clipped= true;
        }
        return clipped;
}

function Coordinate_add(coordinate) {
        this.x+= coordinate.x;
        this.y+= coordinate.y;
}

function Coordinate_gridLock() {
        this.x= step_unit*Math.floor((this.x+step_unit/2+fudge)/step_unit);
        this.y= step_unit*Math.floor((this.y+step_unit/2+fudge)/step_unit);
}

function Coordinate(x,y) {
        this.x= x;
        this.y= y;
        //methods
        this.clip= Coordinate_clip;
        this.toString= Coordinate_toString;
        this.add= Coordinate_add;
        this.copy= Coordinate_copy;
        this.gridLock= Coordinate_gridLock;
}

function toName() {
        return 'Avatar of:'+this.name;
}

function Avatar(name,modifier,facing) {
        this.name= name;
        this.modifier= modifier;
        this.facing= facing;

        //methods
        this.toString= toName;
}

//
// image preloading
//
var myImages= new Array();
function loadImages() {
  for (i=0;i<loadImages.arguments.length;i++) {
          var pos= myImages.length;
          myImages[pos]=new Image();
          myImages[pos].src= loadImages.arguments[i];
  }
}

function imagePath(imageName) {
 return 'images/'+tileset+'/'+imageName;
}

var browsercaps= new Object();

//get internal browser representation/ordering of html
function fixHTML(html) {
        if(browsercaps.fixHTML) {
          var div= document.getElementById("scratchpad");
          div.innerHTML= html;
          return div.innerHTML;
        }else{
          return html;
        }
}

//
// Some browser, like gecko, covert innerHTML to canonic representation.
// this causes flickering
//
function debugHTMLrep(text){
  var fixed= fixHTML(text);
  if( fixed!= text) {
    debugMsg("txt:"+escape(text));
    debugMsg("fix:"+escape(fixed));
  }
}


function Direction_normalize(direction) {
        if(direction<0)
           direction= (direction- 360*Math.floor(direction/360+fudge))
        direction= (direction+360)%360;
        return direction;
}

//
// This function almost works for all agents
//
function Protagonist_mapToImage() {
        this.image="protagonist";
        if(this.mode) {
                this.image+= "_"+this.mode;
        }

        if(this.mode!="victorious") {
              if(this.facing>=0 && this.facing<=45)
                      this.image+= "_right";
              else if(this.facing>=45 && this.facing<=135)
                      this.image+= "_up";
              else if(this.facing>=135 && this.facing<=225)
                      this.image+= "_left";
              else if(this.facing>=225 && this.facing<=315)
                      this.image+= "_down";
              else if(this.facing>=315 && this.facing<=360)
                      this.image+= "_right";
              else {
                      var msg='mapToImage: ('+this.facing+', bad facing)';
                      debugMsg(msg);
              }
        }

        this.image+="_64.gif";

        return this.image;
}

var bullet_right= new Avatar('bullet_small','right',0);
var bullet_up= new Avatar('bullet_small','up',90);
var bullet_left= new Avatar('bullet_small','left',180);
var bullet_down= new Avatar('bullet_small','down',270);

var bird_right= new Avatar('phoenix_moving','right',0);
var bird_up= new Avatar('phoenix_moving','up',90);
var bird_left= new Avatar('phoenix_moving','left',180);
var bird_down= new Avatar('phoenix_moving','down',270);

var siren_right= new Avatar('chess-rook_moving','right',0);
var siren_up= new Avatar('chess-rook_moving','up',90);
var siren_left= new Avatar('chess-rook_moving','left',180);
var siren_down= new Avatar('chess-rook_moving','down',270);


function getSirenForDirection(direction) {
        direction= Direction_normalize(direction);

        if(direction>=0 && direction<=45)
                return siren_right;
        else if(direction>=45 && direction<=135)
                return siren_up;
        else if(direction>=135 && direction<=225)
                return siren_left;
        else if(direction>=225 && direction<=315)
                return siren_down;
        else if(direction>=315 && direction<=360)
                return siren_right;
        else {
                var avatar= 'getSiren: fuck up('+direction+', bad argument)';
                debugMsg(avatar);
                return null;
        }
}

function getBirdForDirection(direction) {
        direction= Direction_normalize(direction);

        if(direction>=0 && direction<=45)
                return bird_right;
        else if(direction>=45 && direction<=135)
                return bird_up;
        else if(direction>=135 && direction<=225)
                return bird_left;
        else if(direction>=225 && direction<=315)
                return bird_down;
        else if(direction>=315 && direction<=360)
                return bird_right;
        else {
                var avatar= 'getBird: fuck up('+direction+', bad argument)';
                debugMsg(avatar);
                return null;
        }
}

//FIXME: replace all the verbatim stuff with imagePath(imageName)
function mapCodeToImage(code){
        var src='';
        switch(code) {

                 case 'H':src='images/'+tileset+'/'+'house_64.gif'; break;
                 case 'h':src='images/'+tileset+'/'+'house_open_64.gif'; break;


                 case 'K':src='images/'+tileset+'/'+'ball_red_64.gif'; break;
                 case 'I':src='images/'+tileset+'/'+'ball_blue_64.gif'; break;
                 case 'J':src='images/'+tileset+'/'+'ball_green_64.gif'; break;

                case 'L':src='images/'+tileset+'/'+'box_blue_64.gif'; break;

                 case 'C':src='images/'+tileset+'/'+'bricks_red_64.gif'; break;
                 case 'g':src='images/'+tileset+'/'+'bricks_green_64.gif'; break;
                 case 'N':src='images/'+tileset+'/'+'bricks_grey_64.gif'; break;

                 case 'M':src='images/'+tileset+'/'+'centerstone-blue_64.gif'; break;
                 case 'O':src='images/'+tileset+'/'+'centerstone-grey_64.gif'; break;

                case 'R':src='images/'+tileset+'/'+'phoenix_sleeping_left_64.gif'; break;
                case 'S':src='images/'+tileset+'/'+'phoenix_sleeping_up_64.gif'; break;
                case 'T':src='images/'+tileset+'/'+'phoenix_sleeping_right_64.gif'; break;
                case 'U':src='images/'+tileset+'/'+'phoenix_sleeping_down_64.gif'; break;

                case 'r':src='images/'+tileset+'/'+'phoenix_awake_left_64.gif'; break;
                case 's':src='images/'+tileset+'/'+'phoenix_awake_up_64.gif'; break;
                case 't':src='images/'+tileset+'/'+'phoenix_awake_right_64.gif'; break;
                case 'u':src='images/'+tileset+'/'+'phoenix_awake_down_64.gif'; break;

                case 'P':src='images/'+tileset+'/'+'chess-rook_sleeping_left_64.gif'; break;
                case 'p':src='images/'+tileset+'/'+'chess-rook_awake_left_64.gif'; break;

                case 'P.right':src='images/'+tileset+'/'+'chess-rook_sleeping_right_64.gif'; break;
                case 'p.right':src='images/'+tileset+'/'+'chess-rook_awake_right_64.gif'; break;

                case 'p.moving.left':src='images/'+tileset+'/'+'chess-rook_moving_left_64.gif'; break;
                case 'p.moving.up':src='images/'+tileset+'/'+'chess-rook_moving_up_64.gif'; break;
                case 'p.moving.right':src='images/'+tileset+'/'+'chess-rook_moving_right_64.gif'; break;
                case 'p.moving.down':src='images/'+tileset+'/'+'chess-rook_moving_down_64.gif'; break;

                //FIXME: this should be handled more elegantly by checking for code.mapToImage and writing adding mapToImage function to avatar
                case bullet_right:
                case bullet_left:
                case bullet_up:
                case bullet_down:

                case siren_right:
                case siren_left:
                case siren_up:
                case siren_down:

                case bird_right:
                case bird_left:
                case bird_up:
                case bird_down:
                        src='images/'+tileset+'/'+code.name+'_'+code.modifier+'_64.gif';
                        //debugMsg('setting image:'+src);
                        break;

                case 'Z':src='images/'+tileset+'/'+'cone_up_64.gif'; break;
                case '[':src='images/'+tileset+'/'+'cone_down_64.gif'; break;

                case 'bullet_big':src='images/'+tileset+'/'+'bullet_big_right_64.gif'; break;

                case 'background':src='images/'+tileset+'/'+'background.gif'; break;

                 default: src='';
        }
        return src;
}

function mapCodeToHtml(code,width,height) {
             var box="";
             var imgsrc= mapCodeToImage(code);
             if(imgsrc) {
                box+='<img src="'+imgsrc+'" alt="'+code+'" title="'+code+'" style="margin: 0px;" border="0" height="'+Math.floor(height)+'" width="'+Math.floor(width)+'">';
                if(debug && debugall) {
                        debugHTMLrep(box);
                }
             } else {
                   switch( code ){
                          case '_':
                          case ' ':
                          case 'A':
                               box= "&nbsp;";
                               break;
                          default:
                            box= "&nbsp;"+code+"&nbsp;";
                   }
             }
             return box;
}

function Snapshot(what,pos,zoom,time) {
        this.what= what;
        this.pos= pos;
        this.zoom= zoom;
        this.time= time;
}

function Code_animate(t) {

        var completed= (t-this.a.time)/(this.b.time-this.a.time);
        var zoom = this.b.zoom*completed+this.a.zoom*(1.0-completed);
        var posx= this.b.pos.x*completed+this.a.pos.x*(1.0-completed);
        var posy= this.b.pos.y*completed+this.a.pos.y*(1.0-completed);

        var pos= new Coordinate(posx,posy);

        //note: clipping suffers badly from bad math which gives >xmax*step_
        if( pos.clip() ) {
                this.b.time=t;//FIXME:zayin ba debug!
        }

        if(this.updateObj && this.updateObj.update) {
                this.updateObj.update(pos);
        }

        var animDiv= document.getElementById(this.div);
        if(animDiv) {
         animDiv.style.left= toScreenX(pos.x/step_unit+(1.0-zoom)*0.5);
         animDiv.style.top=  toScreenY(pos.y/step_unit+(1.0-zoom)*0.5);

         var node= mapCodeToNode(this.a.what,cell_width*zoom,cell_height*zoom,animDiv.id);
         if(node!= animDiv.firstChild){
             clearChildren(animDiv);
             animDiv.appendChild(node);
         }
         //this redraw often isn't necessary => don't redraw
//          var text= mapCodeToHtml(this.a.what,cell_width*zoom,cell_height*zoom);
         //
         //text= fixHTML(text);
         // fixed already !?
//          if(text!=animDiv.innerHTML)
//               animDiv.innerHTML= text;
        } else {
          debugMsg("Error: no layer left for animations");
        }
}

var animation_divs= new Array();

function Animation_begin() {
        this.finished= false;
        this.hasChanged= true;
        for(var i=0; i<1000; ++i) {
                if(!animation_divs[i]) {
                        animation_divs[i]= "animation"+i;
                        this.divid= i;
                        this.div= animation_divs[this.divid];
                        //var animDiv= document.getElementById(this.div);
                        //animDiv.innerHTML="";
                        showLayer(this.div);
                        break;
                }
        }
}

function Animation_end() {

        if(this.div) {

          var animDiv= document.getElementById(this.div);
          animDiv.innerHTML="";
          hideLayer(this.div);
          animation_divs[this.divid]= null;
          this.div= null;
          this.hasChanged= true;
        }

        //avoid endless recursion
        if(!this.finished) {
          this.finished= true;
          if(this.updateObj && this.updateObj.finish) {
                  this.updateObj.finish();
          }
        }

}


function Animation(a,b,updateObj) {
        this.a= a;
        this.b= b;
        this.updateObj= updateObj;

        this.animate= Code_animate;

        this.begin= Animation_begin;
        this.end= Animation_end;
}



var mapRaw= new Array(
new Array('_','_','_','C','_','M','N','O','_','_','_','_'),
new Array('_',' ','A','L',' ',' ',' ',' ',' ',' ','P','_'),
new Array('_','K',' ','g','g',' ',' ',' ',' ',' ',' ','_'),
new Array(' ','I',' ',' ','g',' ',' ',' ','?,' ',' ','_'),
new Array('I','J',' ','N','N','N',' ',' ',' ',' ',' ','_'),
new Array('I',' ',' ','N','N','N',' ','Z',' ',' ','U','_'),
new Array(' ',' ',' ',' ',' ',' ',' ','[',' ',' ',' ','_'),
new Array(' ',' ','L',' ','Z',' ',' ',' ','T',' ',' ','_'),
new Array(' ','N','[',' ','L',' ',' ',' ','T',' ',' ','R'),
new Array('R','P','_','_','_','_','H','_','_','S','_','_')
);

var notnagel=0;

//
// scans the map for an object specified by 'code'
// function is notorious for hanging the program ..
//
function Map_scan(code,last) {
        if(Map_scan.arguments.length<2) {
                x= 0;
                y= 0;
        } else if(!last) {
                x= 0;
                y= 0;
        } else {
                x= toGridX(last.x)+1;
                y= toGridY(last.y);
                notnagel++;
//                 if(notnagel>100) {
//                         debugMsg("halting");
//                         gameProceed= false;
//                         sleep(1000);
//                         notnagel=0;
//                 }
                //debugMsg("last:"+last+" x "+x+" y"+y);
        }

        //for y for x
        while(true) {
                if(x>=this.xmax) {
                        x= 0;
                        y= y+1;
                  if(y>=this.ymax) {
                        return null;
                  }
                }

                if(this.map[y][x]==code) {
                        var c= new Coordinate(x*step_unit,y*step_unit);
                        return c;
                }

                x= x+1;
        }

}

function fromDirection(direction) {
        direction= Direction_normalize(direction);
        if(direction>=0 && direction<=45)
                return this.right;
        else if(direction>=45 && direction<=135)
                return this.up;
        else if(direction>=135 && direction<=225)
                return this.left;
        else if(direction>=225 && direction<=315)
                return this.down;
        else if(direction>=315 && direction<=360)
                return this.right;
        else {
                var msg= 'fuck up('+this.direction+', bad argument)';
                debugMsg(msg);
                return this.none;
        }
}


function Compass(scale) {
  if(!scale) {
        scale=1;
  }
  this.none= new Coordinate(0,0);

  this.right= new Coordinate(scale,0);
  this.up=  new Coordinate(0,-scale);
  this.left= new Coordinate(-scale,0);
  this.down= new Coordinate(0,scale);

  //methods
  this.fromDirection= fromDirection;
}

var compass= new Compass();
var stepcompass= new Compass(step_unit);


function scanTrap(code,direction,from,checkObstacle,checkForTarget) {
  var delta= stepcompass.fromDirection(direction);

  var pos= from.copy();

  pos.add(delta);

  while(!pos.clip()) {
        if( checkObstacle(code,pos) )
                return false;
        if( checkForTarget(code,pos,from) )
                return true;
        pos.add(delta);
  }
  return false;
}

function getXYCode(x,y) {
        x= Math.floor(x+fudge);
        y= Math.floor(y+fudge);
        if(!this.map[y]) {
                debugMsg("bad x,y in getXYCode x:"+x+" y:"+y);
        }
        return this.map[y][x];
}

function setXYCode(x,y,code) {
        x= Math.floor(x+fudge);
        y= Math.floor(y+fudge);
        this.mapHasChanged= true;
        this.map[y][x]=code;
}

function Map_addAnimation(animation) {
        var len= this.animations.length;
        for(var i=0; i<len+1; ++i) {
          if(!this.animations[i]) {
                this.animations[i]= animation;
                break;
          }
        }
        animation.begin();
}

function Map_removeAnimation(animation) {
        var len= this.animations.length;
        for(var i=0; i<len+1; ++i) {
          if(this.animations[i]==animation) {
                animation.end();
                this.animations[i]= null;
                break;
          }
        }
}

function Map_cleanAnimations(t) {
        var len= this.animations.length;
        for(var i=0; i<len; ++i) {
                if(this.animations[i]
                && this.animations[i].b
                && this.animations[i].b.time<=t) {
                        this.removeAnimation(this.animations[i]);
                }
        }
}

function GameState_addBullet(bullet) {
        map.addAnimation(bullet.animation);
        gameState.bullet= bullet;
}

function GameState_removeBullet(bullet) {
        map.removeAnimation(bullet.animation);
        this.bullet= null;
}

function GameState_addMover(mover) {
        map.addAnimation(mover.animation);
        this.mover= mover;
}

function GameState_removeMover(mover) {
        map.removeAnimation(mover.animation);
        this.mover= null;
}


function Map_distance(a,b){
        var dx= Math.abs(a.x-b.x);
        var dy= Math.abs(a.y-b.y);
        var dist= dx>dy?dx:dy;
        return dist;
};

function Map_clear() {
        var len= this.animations.length;
        for(var i=0; i<len; ++i) {
                if(this.animations[i]) {
                        this.animations[i].end();
                        this.animations[i]= null;
                }
        }
}

function Map_setMap(mapRaw) {
        this.ymax= mapRaw.length;
        this.xmax= mapRaw[0].length;
        this.map= mapRaw;
}


function Map(mapRaw) {
        this.animations= new Array();
        this.mapHasChanged= true;
        this.buffer=0;

        //methods
        this.scan= Map_scan;
        this.getXYCode= getXYCode;
        this.setXYCode= setXYCode;
        this.addAnimation= Map_addAnimation;
        this.cleanAnimations= Map_cleanAnimations;
        this.removeAnimation= Map_removeAnimation;
        this.distance= Map_distance;
        this.clear= Map_clear;

        this.setMap= Map_setMap;
        if(mapRaw) {
                this.setMap(mapRaw);
        }
}

function readMap(mapPre) {
        var mapRaw=new Array();

        //IE and gecko: var lines= mapPre.match(/.*/g);
        //opera is funny and divides the lines by whitespace
        var lines= mapPre.match(/\S*\s*/g);

        var y=0;
        for(var yy=0, ylen=lines.length;yy<ylen;++yy) {
          //debugMsg("line("+(lines[yy].length)+"):"+lines[yy]);
          if(lines[yy].length>2){
            var mapLine= new Array();
            theLine= lines[yy];
            var x=0;
            for(var xx=0, xlen=theLine.length;xx<xlen;++xx) {
                  var thechar= theLine.charAt(xx);
                  if(thechar!='\n' && thechar!='\r' && thechar>' ') {
                        mapLine[x]= thechar;
                        ++x;
                  }
            }
            mapRaw[y]= mapLine;
            ++y;
           }
        }

        var map= new Map(mapRaw);
        return map;
}


function GameState_isFillable(pos){
         var x= pos.x/step_unit;
         var y= pos.y/step_unit;
         if(x<0||y<0||x>=map.xmax||y>=map.ymax)
             return false;
         var code= map.getXYCode(x,y);
         switch(code) {
                case 'A':
                case '_':
                case ' ':
                         return true;
                         break;
                default: return false;
         }
}

function GameState_pushTo(pos,from) {
        var two= new Coordinate(pos.x,pos.y);
        var step= step_unit;

        var angle=  Math.atan2(-(pos.y-from.y), pos.x-from.x);
        var direction= (180*angle/Math.PI+360)%360;

        if(direction>=0 && direction<=45)
                two.x+= step;
        else if(direction>=45 && direction<=135)
                two.y-= step;
        else if(direction>=135 && direction<=225)
                two.x-= step;
        else if(direction>=225 && direction<=315)
                two.y+= step;
        else if(direction>=315 && direction<=360)
                this.image= basename+"_right.gif";
        else
                debugMsg("pushTo bug:"+pos+" "+from);


//         if(from.x<pos.x) {
//                 two.x+= step;
//         } else if(from.x>pos.x) {
//                 two.x-= step;
//         } else if(from.y<pos.y) {
//                 two.y+= step;
//         } else if(from.y>pos.y) {
//                 two.y-= step;
//         }
        return two;
}

function pos_update(pos) {
        //debugMsg("updating to pos:"+pos.toString());
        this.pos= pos;
}

function Bullet_finish() {
        if(!this.hit) {
          //wimp rules:
          //return unused bullet to protagonist
          protagonist.bullets++;
        }
        gameState.removeBullet(this);
}


function Mover_finish() {
        gameState.removeMover(this);
        map.setXYCode(toGridX(this.pos.x),toGridY(this.pos.y),this.avatar);
}

//FIXME: this bullets stuff needs to be rewritten for an action game
function CoordinateVector(pos,direction,steps) {
        this.direction= direction;
        this.dx= Math.cos(Math.PI*direction/180.0);
        this.dy= -Math.sin(Math.PI*direction/180.0);
        this.x= Math.floor(pos.x+this.dx*steps+fudge);
        this.y= Math.floor(pos.y+this.dy*steps+fudge);
        this.toString= Coordinate_toString;
}

function Mover(source,destination,avatar,from) {
        this.source= source;
        this.pos= source;
        this.destination= destination;
        //FIXME: superfluous:pushed from: this.from=from;

        //methods
        this.update= pos_update;
        this.finish= Mover_finish;
        this.avatar= avatar;
}


function Bullet(pos,direction,steps) {
        this.source= pos;
        this.pos= pos;
        this.destination= new CoordinateVector(pos,direction,steps);
        this.direction= (direction+360)%360;

        //methods
        this.update= pos_update;
        this.finish= Bullet_finish;

        if(this.direction>=0 && this.direction<=45)
                this.avatar= bullet_right;
        else if(this.direction>=45 && this.direction<=135)
                this.avatar= bullet_up;
        else if(this.direction>=135 && this.direction<=225)
                this.avatar= bullet_left;
        else if(this.direction>=225 && this.direction<=315)
                this.avatar= bullet_down;
        else if(this.direction>=315 && this.direction<=360)
                this.avatar= bullet_right;
        else {
                this.avatar='fuck up('+this.direction+', bad argument)';
                debugMsg(this.avatar);
        }
}

function Attack_finish(animation) {
        map.removeAnimation(this.animation);
        // The following code was for giving the home precedence over attacks
        // Winners are immortal
        if(!gameState.victory) {
                protagonist.die();
        } else {
                protagonist.freeze= false;
        }
}

//Well, I try to avoid the super() constructor, since so far I made it without prototypes..
function Attack_init(that,code,source,destination) {
        that.source= source;
        that.pos= source;
        that.destination= destination;

        var angle=  Math.atan2(-(destination.y-source.y), destination.x-source.x);
        that.direction= (180*angle/Math.PI+360)%360;

        //methods
        that.update= pos_update;
        that.finish= Attack_finish;
}

function Attack(code,source,destination) {
        Attack_init(this,code,source,destination);
        switch(code) {
                case 'R':
                case 'S':
                case 'T':
                case 'U':

                case 'r':
                case 's':
                case 't':
                case 'u': this.avatar= getBirdForDirection(this.direction); break;

                case 'P':
                case 'p':

                case 'P.right':
                case 'p.right': this.avatar= getSirenForDirection(this.direction); break;

                default:
                        debugMsg("unknown attacker "+code);
                        this.avatar="unknown attacker";
        }

}



function GameState_hitBullet(bullet,pos) {
        bullet.hit= true;
        gameState.removeBullet(bullet);

        var x= toGridX(pos.x);
        var y= toGridY(pos.y);
        var code= map.getXYCode(x,y);

       if(shrink) {
             map.addAnimation(
                    new Animation(
                            new Snapshot(code,pos,1.0,gameState.time),
                            new Snapshot(' ',pos,0.0,gameState.time+1.0*ticks_unit)
                    )
             );
       }

        map.setXYCode(x,y,' ');
        return true;
}



function GameState_checkBullet(bullet) {

         var x= toGridX(bullet.pos.x);
         var y= toGridY(bullet.pos.y);
         var code= map.getXYCode(x,y);

         //debugMsg("comparing x"+bullet.pos.x+" to x:"+x*step_unit);

         //FIXME: this should be 0.5 or 0.05 instead of 0.40, but..
         var target= new Coordinate(x*step_unit,y*step_unit);

// FIXME: sometimes bullets don't hit
//          debugMsg("bullet at "+bullet.pos+
//                   "target at "+target+
//                   "distance"+map.distance(bullet.pos,target));

         if( map.distance(bullet.pos,target)
             > 0.40*step_unit
            ){
                return false;
         }

         switch(code) {
                case 'Z':
                case '[':
                case 'g':
                case 'C':
                        return this.hitBullet(bullet,target);
                        break;
                default:
                     var absorbed= !this.isPassable(bullet.pos,bullet.source,"bullet");
                     if(absorbed)
                        gameState.removeBullet(bullet);
                     return absorbed;
         }
}

function GameState_checkBullets(bullet) {
        if( this.bullet!= null ) {
                this.checkBullet(this.bullet);
        }
}

function GameState_isPassable(pos,from,by){
         var x= pos.x/step_unit;
         var y= pos.y/step_unit;
         var code= map.getXYCode(x,y);

         switch(code) {
                case 'H':
                        return gameState.home;
                case 'A':
                case '_':
                case ' ':
                          return true; break;

                case 'I':
                case 'J':
                case 'K':
                case 'i':
                case 'j':
                case 'k':
                          return by=="protagonist"; break;
                case 'g':
                          return this.color!="green"; break;
                case 'C':
                          return this.color!="red"; break;
                case 'M':
                case 'N':
                case 'O':
                case 'P':
                          return false; break;
                case 'R':
                case 'S':
                case 'T':
                case 'U':
                          return false; break;
                case 'Z':
                case '[':
                          return false; break;
                case 'L':
                          return by=="protagonist" && this.isFillable(this.pushTo(pos,from)); break;
                case 'l':
                          return false;
                default:
                     debugMsg("isPassable: unknown tile code:"+code);
                     return false;

         }
}

function GameState_beginEnter(pos,from){
         var x= toGridX(pos.x);
         var y= toGridY(pos.y);
         var code= map.getXYCode(x,y);

         switch(code) {
                case 'H':
                        //this.victory= true;
                        break;
                case 'A':
                case '_':
                case ' ':
                         break;
                case 'I':
                case 'J':
                case 'K':
                        if(shrink) {
                               map.addAnimation(
                                      new Animation(
                                              new Snapshot(code,pos,1.0,gameState.time),
                                              new Snapshot(' ',pos,0.0,gameState.time+1.0*ticks_unit)
                                      )
                               );
                        }
                         map.setXYCode(x,y,code.toLowerCase());
                         break;
                case 'g':
                         break;
                case 'C':
                         break;
                case 'M':
                case 'N':
                case 'O':
                case 'P':
                         break;
                case 'R':
                case 'S':
                case 'T':
                case 'U':
                         break;
                case 'Z':
                case '[':
                         break;
                case 'L':
                        var destination= gameState.pushTo(pos,from);
                         // place dummy
                         map.setXYCode(toGridX(destination.x),toGridY(destination.y),'l');
                         //empty tile
                         map.setXYCode(x,y,' ');
                         //begin animation
                          var range= 1;
                          var mover= new Mover(pos,destination,code,from);
                          var animation=
                                 new Animation(
                                          new Snapshot(mover.avatar,mover.source,1.0,gameState.time),
                                          new Snapshot(mover.avatar,mover.destination,1.0,gameState.time+range*ticks_unit),
                                          mover
                                  );

                          mover.animation= animation;
                          gameState.addMover(mover);

                         break;
                default:
                     debugMsg("beginEnter: unknown tile code:"+code);
         }
}

function removeBall(code,x,y) {
        gameState.sirens= true;
        map.setXYCode(x,y,' ');
}

function GameState_finishEnter(pos,from){
         var x= toGridX(pos.x);
         var y= toGridY(pos.y);
         var code= map.getXYCode(x,y);

         switch(code) {
                case 'H':
                        if(!protagonist.freeze) {
                                this.victory= true;
                                protagonist.setMode("victorious");
                        }
                        break;
                case 'A':
                case '_':
                case ' ':
                        break;
                case 'I':
                case 'J':
                case 'K':
                        break;
                case 'i':
                        removeBall(code,x,y);
                        protagonist.bullets++;
                        break;
                case 'j':
                        this.color="green";
                        removeBall(code,x,y);
                        break;
                case 'k':
                        this.color="red";
                        removeBall(code,x,y);
                        break;
                case 'g':
                        break;
                case 'C':
                        break;
                case 'M':
                case 'N':
                case 'O':
                case 'P':
                        break;
                case 'R':
                case 'S':
                case 'T':
                case 'U':
                        break;
                case 'Z':
                case '[':
                        break;
                case 'L','l':
                        //stop animation
                        //rest on new tile
                        break;
                default:
                     debugMsg("finishEnter: unknown tile code:"+code);
         }
}


function GameState_filterCode(code,x,y) {
             switch(code) {
                case 'H':
                          if(this.home)
                              code= code.toLowerCase();
                          break;
                case 'g':
                          if( this.color!="green")
                              code=' ';
                          break;
                case 'C':
                          if( this.color!="red")
                              code=' ';
                          break;
                case 'P':
                          if(this.sirens) {
                              code= code.toLowerCase();
                              //FIXME: bug is: map has to change to cause redraw, so a second map is needed ..
                              if(protagonist.pos.x>x) {
                                    code+= '.right';
                              }
                          }
                          break;
                case 'R':
                case 'S':
                case 'T':
                case 'U':
                          if(this.birds)
                              code= code.toLowerCase();
                          break;
                case 'i':
                case 'j':
                case 'k':
                          code=' ';
                          break;
                case 'l':
                          code=' ';
                          break;
             }

             return code;
}

function GameState_mayShoot(){
        return this.bullet==null;
}


function GameState_checkTriggers() {
        //TODO: check if one has to check for i,j,k too
        if(!map.scan('I')&&!map.scan('J')&&!map.scan('K')) {
                this.birds= true;
                this.home= true;
        } else {
                 this.birds= false;
                 this.home= false;
        }
}

//trap functions

//currently there is only one type of ray
function checkForObstacle(code,pos) {
        //hack to disallow rays pushing
        var result=!gameState.isPassable(pos,pos,"ray");
        return result;
}

function checkForTarget(code,pos,source) {
        var hit= map.distance(protagonist.pos, pos)<=step_unit/2;
        if(hit) {
               protagonist.freeze= true;
               // start kill animation
               //empty tile
               map.setXYCode(x,y,' ');
               //begin animation
                var range= Math.floor(map.distance(source,protagonist.pos)/(2*step_unit))+1;
                var mover= new Attack(code,source,protagonist.pos);
                var animation=
                       new Animation(
                                new Snapshot(mover.avatar,mover.source,1.0,gameState.time),
                                new Snapshot(mover.avatar,mover.destination,1.0,gameState.time+range*ticks_unit),
                                mover
                        );

                mover.animation= animation;
                map.addAnimation(mover.animation);
        }
        return hit;
}


function checkTrap(code,direction,checkForObstacle,checkForTarget) {
        var pos= null;
        while( pos= map.scan(code,pos) ) {
                //debugMsg("checking trap "+code+" at "+pos);
                scanTrap(code,direction,pos,checkForObstacle,checkForTarget);
        }
}

function checkTraps() {
        //debugMsg("BEGIN checkTraps");
        if(this.birds) {
          checkTrap('R',180,checkForObstacle,checkForTarget);
          checkTrap('S',90,checkForObstacle,checkForTarget);
          checkTrap('T',0,checkForObstacle,checkForTarget);
          checkTrap('U',270,checkForObstacle,checkForTarget);
        }

        //debugMsg("end checkTraps birds");

        if(this.sirens) {
          checkTrap('P',180,checkForObstacle,checkForTarget);
          checkTrap('P',90,checkForObstacle,checkForTarget);
          checkTrap('P',0,checkForObstacle,checkForTarget);
          checkTrap('P',270,checkForObstacle,checkForTarget);
        }

        //debugMsg("end checkTraps sirens");

        if(this.home) {
           var pos= map.scan(code_home);
           if(pos && map.distance(protagonist.pos, pos)<step_unit/3 && !protagonist.freeze ) {
                this.victory= true;
                protagonist.setMode("victorious");
           }
        }
        //debugMsg("END checkTraps");
}


function GameState() {
         this.time= 0;
         this.color="green";
         this.sirens= false;
         this.birds= false;
         this.home= false;
         this.victory= false;
         this.bullet= null;
         this.mover= null;
        //methods
        this.isPassable= GameState_isPassable;
        this.isFillable= GameState_isFillable;
        this.pushTo= GameState_pushTo;
        this.filterCode= GameState_filterCode;

        this.beginEnter= GameState_beginEnter;
        this.finishEnter= GameState_finishEnter;

        this.addBullet= GameState_addBullet;
        this.hitBullet= GameState_hitBullet;
        this.checkBullet= GameState_checkBullet;
        this.checkBullets= GameState_checkBullets;
        this.removeBullet= GameState_removeBullet;
        this.mayShoot= GameState_mayShoot;

        this.addMover= GameState_addMover;
        this.removeMover= GameState_removeMover;

        this.checkTriggers= GameState_checkTriggers;
        this.checkTraps= checkTraps;
}


function Facings() {
        this.right= 0;
        this.left= 180;
        this.up= 90;
        this.down= 270;
}
var facings= new Facings();

var keybuffer= new Array();

var Agent_tick= function (ticks) {

        //else used, so no diagonal movement allowed
        var nuarrived= true;
        var step= step_unit*ticks/ticks_unit;
        if(this.pos.x+step<=this.destination.x) {
                this.pos.x+= step;
                nuarrived= false;
        }
        else if(this.pos.x-step>=this.destination.x) {
                this.pos.x-= step;
                nuarrived= false;
        }
        else if(this.pos.y+step<=this.destination.y) {
                this.pos.y+= step;
                nuarrived= false;
        }
        else if(this.pos.y-step>=this.destination.y) {
                this.pos.y-= step;
                nuarrived= false;
        }

        var togox= Math.abs(this.destination.x-this.pos.x)/step_unit;
        var togoy= Math.abs(this.destination.y-this.pos.y)/step_unit;

        this.completion= 1.0-(togox>togoy?togox:togoy);

        //debugMsg("Arrived:"+this.arrived+" "+(100*this.completion)+"% x:"+this.pos.x+" tox:"+this.destination.x+" y:"+this.pos.y+" toy:"+this.destination.y+" step="+step);

        //process keys

        //optional: read keyboard after 50%
        if(this.completion<0.5) {
                 keybuffer= new Array();
        }

        //detect flank
        if(nuarrived & !this.arrived) {
             //optional: empty key buffer on arrival
             //keybuffer= new Array();
             //debugMsg("finish enter "+this.destination.x+","+this.destination.y);
             gameState.finishEnter(this.destination,this.source);
             this.setMode("");
        }

        if(nuarrived && keybuffer.length>0 && !this.freeze) {
          var keyCode= keybuffer[0];

          //optional:use key buffer
           if(keybuffer.shift) {
                 keybuffer.shift();
           } else
                 keybuffer= new Array();

          if (keyCode == key_right) {
            //r += 'arrow right';
            this.destination.x= this.pos.x+step_unit;
            this.setFacing(facings.right);
            this.setMode("moving");
          } else if (keyCode == key_down) {
            //r += 'arrow down';
            this.destination.y= this.pos.y+step_unit;
            this.setFacing(facings.down);
            this.setMode("moving");
          } else if (keyCode == key_up) {
            //r += 'arrow up';
            this.destination.y= this.pos.y-step_unit;
            this.setFacing(facings.up);
            this.setMode("moving");
          } else if (keyCode == key_left) {
            //r += 'arrow left';
            this.destination.x= this.pos.x-step_unit;
            this.setFacing(facings.left);
            this.setMode("moving");
          } else if (keyCode == key_fire) {
            // shoot
            if(this.bullets>0 && gameState.mayShoot() ) {
                    var range= 2.44;
                    var bullet= new Bullet(this.pos,this.facing,step_unit*range);
                    var animation=
                           new Animation(
                                    new Snapshot(bullet.avatar,bullet.source,1.0,gameState.time),
                                    new Snapshot(bullet.avatar,bullet.destination,1.0,gameState.time+range*ticks_unit),
                                    bullet
                            );

                    bullet.animation= animation;
                    gameState.addBullet(bullet);
                    this.bullets--;
            }
          }

          this.destination.clip();
          if( !gameState.isPassable(this.destination,this.pos,"protagonist") ) {
              this.destination.x= this.pos.x;
              this.destination.y= this.pos.y;
          }else {
              this.source.x= this.pos.x;
              this.source.y= this.pos.y;

              //fixme
              //this should not be needed
              //this.destination.gridLock();
              //this.pos.gridLock();
              gameState.beginEnter(this.destination,this.pos);
          }
        }

        this.arrived= nuarrived;
}

function Agent_setFacing(direction) {
        if(this.facing!=direction) {
                this.facing= direction;
                this.hasChanged= true;
        }
}

function Agent_setMode(mode) {
        if(this.mode!=mode) {
                this.mode= mode;
                this.hasChanged= true;
        }
}


function Agent_init(agent,name,x,y) {
        agent.name= name;
        agent.buffer= 0;

        agent.source= new Coordinate(x,y);
        agent.pos= new Coordinate(x,y);
        agent.destination= new Coordinate(x,y);

        agent.arrived= true;
        agent.completion= 1.0;
        agent.hasChanged= true;

        agent.facing= 0;//right
        //methods
        agent.tick= Agent_tick;
        agent.setFacing= Agent_setFacing;
        agent.setMode= Agent_setMode;
}

function Agent(name,x,y) {
        Agent_init(this, name, x ,y);
}


// function mapFromTemplate(map,initializer) {
//          var tis= new Array(map.length);
//          for(var y=0;y<tis.length;++y) {
//            tis[y]= new Array(map[y].length);
//              for(var x=0;x<tis[y].length;++x) {
//                tis[y][x]= initializer;
//              }
//          }
//          return tis;
// }


//
// Please don't much use browser detection, only for final polish
//
function setBrowser() {
        if( navigator.appName=="Microsoft Internet Explorer" ) {

                debugMsg("like IE!");// or opera or any faker ..
                browsercaps.doublebuffer= true;

                //this is for IE, not opera FIXME!
                //IE is very slow with fixed and divs and scrolls anyway
                //Actually it seems IE is slow with big windows
                var wrapper= document.getElementById("mapwrapper");
                if(wrapper&&wrapper.style&&wrapper.style.position) {
                  wrapper.style.position="";
                }
        }

        if( navigator.product=="Gecko" ) {
                debugMsg("like Gecko!");
                // switch on HTML normalization to avoid flicker.
                browsercaps.fixHTML= true;
        }
}

function preload() {
   var dummy= new Agent("dummy",0,0);
   dummy.mapToImage= Protagonist_mapToImage;

   dummy.setFacing(0);
   dummy.setMode("victorious");
   loadImages( imagePath(dummy.mapToImage() ) );
   dummy.setMode("");
   loadImages( imagePath(dummy.mapToImage() ) );
   dummy.setMode("moving");
   loadImages( imagePath(dummy.mapToImage() ) );
   dummy.setFacing(90);
   dummy.setMode("");
   loadImages( imagePath(dummy.mapToImage() ) );
   dummy.setMode("moving");
   loadImages( imagePath(dummy.mapToImage() ) );
   dummy.setFacing(180);
   dummy.setMode("");
   loadImages( imagePath(dummy.mapToImage() ) );
   dummy.setMode("moving");
   loadImages( imagePath(dummy.mapToImage() ) );
   dummy.setFacing(270);
   dummy.setMode("");
   loadImages( imagePath(dummy.mapToImage() ) );
   dummy.setMode("moving");
   loadImages( imagePath(dummy.mapToImage() ) );

   // only loading what can appear later
   loadImages( mapCodeToImage('h') );

   loadImages( mapCodeToImage('r') );
   loadImages( mapCodeToImage('s') );
   loadImages( mapCodeToImage('t') );
   loadImages( mapCodeToImage('u') );

   loadImages( mapCodeToImage(bird_right) );
   loadImages( mapCodeToImage(bird_down) );
   loadImages( mapCodeToImage(bird_left) );
   loadImages( mapCodeToImage(bird_up) );


   loadImages( mapCodeToImage('P') );
   loadImages( mapCodeToImage('p') );
   loadImages( mapCodeToImage('P.right') );
   loadImages( mapCodeToImage('p.right') );
   loadImages( mapCodeToImage('p.moving.left') );
   loadImages( mapCodeToImage('p.moving.up') );
   loadImages( mapCodeToImage('p.moving.down') );
   loadImages( mapCodeToImage('p.moving.right') );

   loadImages( mapCodeToImage('g') );
   loadImages( mapCodeToImage('C') );

   loadImages( mapCodeToImage('bullet_big' ) );
   loadImages( mapCodeToImage( bullet_right ) );
   loadImages( mapCodeToImage( bullet_up ) );
   loadImages( mapCodeToImage( bullet_left ) );
   loadImages( mapCodeToImage( bullet_down ) );

   debugMsg("commanded preloading of "+myImages.length+" images");
}

function Protagonist_die() {
    this.shrink= true;
    if(typeof this.zoom!='number') {
            this.zoom= 1.0;
    }
}

function Protagonist_tick(ticks) {
           //well, I worked without prototypes so far, I'm not going to start it now
           //this.prototype.tick(ticks)
           if(this.shrink) {
                this.zoom-= ticks/ticks_unit;
                this.hasChanged= true;
           }
}

function Protagonist(ppos) {
   Agent_init(this,"protagonist",ppos.x,ppos.y);
   this.draw= Protagonist_draw;
   this.bullets=0;
   this.mapToImage= Protagonist_mapToImage;
   this.freeze= false;
   this.die= Protagonist_die;
   this.zoom= null;
   this.shrink= false;
   this.ptick= Protagonist_tick;
}

function resetGameState() {
   gameState= new GameState();

   var ppos= map.scan(code_start);
   if(!ppos) {
          alert("this map lacks a starting position");
          ppos= new Coordinate(0,0);
   }

   protagonist= new Protagonist(ppos);
}

function layout2(left) {
  var div;
  //controls
  var div= document.getElementById("controls");
  div.style.top= 20;
  div.style.left= left;
  //scratchpad
  var div= document.getElementById("help");
  div.style.top= 150;
  div.style.left= left;

  var div= document.getElementById("by");
  div.style.top= 210;
  div.style.left= left;

  var div= document.getElementById("sp0");
  div.style.top= 360;
  div.style.left= left+300;

  //debug
  var div= document.getElementById("debug");
  div.style.left= left;

}

function resizeDiv(name,width,height) {
    var div= document.getElementById(name);
    if(div && div.style) {
      div.style.width= width;
      div.style.height= height;
    }
}

function configure() {
  var search= window.location.search;

  if( search.match(/debug=1/) ){
        debug= true;
        hideDebug= false;
  }

  if( search.match(/shrink=0/) ){
        shrink= false;
  }

  if( search.match(/size=32/) ){
        map_left= 20;
        cell_width= 32;
        cell_height= 32;
        setTimeout("layout2("+(cell_width*12+20+map_left)+")",1);
        if(!debug) {
                resizeDiv("mapwrapper",1000-206,580-170);
        }
  }

  if( search.match(/size=48/) ){
        cell_width= 48;
        cell_height= 48;
        setTimeout("layout2("+(cell_width*12+28+map_left)+")",1);
        resizeDiv("mapwrapper",1000,580-20);
  }

  if( search.match(/size=64/) ){
        cell_width= 64;
        cell_height= 64;
        setTimeout("layout2("+(cell_width*12+28+map_left)+")",1);
        resizeDiv("mapwrapper",1000+210,580+100);
  }

  var debugDiv= document.getElementById("debugoutput");
  debugDiv.style.visibility= debug?'visible':'hidden';

  var debugonoffDiv= document.getElementById("debugonoff");
  debugonoffDiv.style.visibility= (!hideDebug)?'visible':'hidden';
}

function resizeStatusbar(map){
        var statusDiv= document.getElementById("status");
        var width= (map.xmax)*cell_width;
        if(width<minStatusbarWidth)
           width= minStatusbarWidth;
        if(statusDiv && statusDiv.style) {
                statusDiv.style.width= width;
        }
        var height= bullet_height>status_font?bullet_height:status_font;
        statusDiv.style.height= height;
        statusDiv.style.top= map_top-height;
}


function resetGUI() {
         resizeStatusbar(map);
         var mapObjO= document.getElementById("map"+0);
         if(mapObjO) {
                mapObjO.innerHTML="";
         }
         var mapObjN= document.getElementById("map"+1);
         if(mapObjN) {
                mapObjN.innerHTML="";
         }
         keybuffer= new Array();
         scroll(0,0);
         window.focus();
}

function init() {

         configure();

         //The code is supposed to work in all browsers, but we can improve performance sometimes if we know browser
         setBrowser();
         preload();
         placeMap();
         drawStatusBullets();
         loadSetIndex();

         map= new Map(mapRaw);
         resetGameState();
         resetGUI();

         setTimeout("loadNextMap()",4999);

         mainLoop();

}

function mainLoop() {
         if(mainProceed) {

           protagonist.tick(theTick);
           protagonist.ptick(theTick);
           gameState.time+=theTick;

           drawMap();

           drawAnimations();
           map.cleanAnimations(gameState.time);
           gameState.checkBullets();

           if(gameState.time%ticks_unit<theTick) {
             gameState.checkTriggers();
             //FIXME: the tiles affected by traps should be marked,
             //so that processing is faster
             gameState.checkTraps();
           }

           if(gameState.time%(ticks_unit/3)<theTick) {
             drawStatus();
           }
           //drawMap();//FIXME: This takes too long e.g. push box
           protagonist.draw();
         }
         setTimeout("mainLoop()",mainDelay);
}

function drawAnimations() {
         for(var i=0;i<map.animations.length;++i) {
                if(map.animations[i]) {
                        map.animations[i].animate(gameState.time);
                }
         }
}

var hashImages= new Array();

function imageFor(imgsrc,title,height,width,id) {
        var key= imgsrc+" "+title+" "+height+" "+width;
        var fullkey= key+" "+id;

        saved= hashImages[fullkey];
        if(saved) {
                return saved;
        }

        saved= hashImages[key];
        if(saved) {
                saved= saved.cloneNode(false);
                hashImages[fullkey]= saved;
                return saved;
        }

        var img= document.createElement("IMG");
        img.setAttribute("src",imgsrc);
        img.setAttribute("alt",title);
        img.setAttribute("title",title);
        img.setAttribute("height",""+height);
        img.setAttribute("width",""+width);
        img.setAttribute("style","margin: 0px 0px 0px 0px;");
        img.setAttribute("border","0");

        hashImages[key]= img;
        hashImages[fullkey]= img;

        return img;
}

function mapCodeToNode(code,width,height,forId) {
    var node= null;
    var imgsrc= mapCodeToImage(code);
    if(imgsrc) {
      node= imageFor(imgsrc,code,height,width,forId);
    } else {
         var box="";
         switch( code ){
                case '_':
                case ' ':
                case 'A':
                     box= "&nbsp;";
                     break;
                default:
                  box= "&nbsp;"+code+"&nbsp;";
         }
         node= document.createElement("SPAN");
         node.innerHTML= box;
    }
    return node;
}

function clearChildren(node) {
        while(node.firstChild != null ) {
                node.removeChild(node.firstChild);
        }
}

function makeMap() {

    var text="";

    text+='<table border=0 cellpadding=0 cellspacing=0 style="background-image:url(\''+mapCodeToImage("background")+'\')">';
    var tr= "<tr height="+cell_height+">";
    var td= "<td class=map width="+cell_width+">";

    for(y=0;y<map.ymax;++y) {
    text+= tr;
    for(x=0;x<map.xmax;++x) {
        text+= td;

        var code= map.map[y][x];

        code= gameState.filterCode(code,x*step_unit,y*step_unit);

        var box= mapCodeToHtml(code,cell_width,cell_height);

        text+= box;
        text+="</td>";
    }
    text+="</tr>";
    }
    text+="</table>";

    return text;
}

function placeMap(){
   var mapObjO= document.getElementById("map0");
   mapObjO.style.left=map_left;
   mapObjO.style.top=map_top;

   var mapObjN= document.getElementById("map1");
   mapObjN.style.left=map_left;
   mapObjN.style.top=map_top;

   var mapObjO= document.getElementById("status");
   mapObjO.style.left=map_left;
}

function drawMap() {
         if(map.mapHasChanged) {
                map.mapHasChanged=false;

         var starttime;
         if(debug && debugall) {
                starttime= new Date();
         }

         if( browsercaps.doublebuffer ) {
           //Opera and IE seem to double buffer anyway
           var mapObjO= document.getElementById("map"+map.buffer);
           mapObjO.innerHTML= makeMap();
         }else{
           //doublebuffering for Gecko/FireFox
           var mapObjO= document.getElementById("map"+map.buffer);
           map.buffer^=1;
           var mapObjN= document.getElementById("map"+map.buffer);
           mapObjN.style.zIndex=0;

           mapObjN.innerHTML= makeMap();
           mapObjO.style.zIndex=5;
           mapObjN.style.zIndex=10;
           mapObjO.style.zIndex=0;
         }

         if(debug && debugall) {
                var endtime= new Date();
                debugMsg("redrawing map took "+(endtime-starttime)+"ms");
         }


         }// map has changed


}

function Protagonist_draw() {
         var mapObjN= document.getElementById(this.name+this.buffer);
         //this.buffer= this.buffer^1;
         //var mapObjO= document.getElementById(this.name+this.buffer);
         var left= toScreenX(this.pos.x/step_unit);
         var top= toScreenY(this.pos.y/step_unit);
         //this redraw often isn't necessary => don't redraw
         if(this.hasChanged) {
           this.hasChanged= false;
           //FIXME: protagonist width and height are a hack
           var width= cell_width;
           var height= cell_height;

           if((typeof this.zoom)=='number') {
                zwidth= Math.floor(width*this.zoom+fudge);
                zheight= Math.floor(height*this.zoom+fudge);
                left+= Math.floor((width-zwidth)/2);
                top+= Math.floor((height-zheight)/2);
                width= zwidth;
                height= zheight;
           }

           var text;
           if(width<1||height<1) {
                text='';
                this.shrink= false;
           } else {
                text= '<img src="'+imagePath(protagonist.mapToImage())+'" alt="@" border="0" height="'+Math.floor(height)+'" width="'+Math.floor(width)+'" style="margin:0px;">';
           }
           mapObjN.innerHTML= text;
         }

         mapObjN.style.left= left;
         mapObjN.style.top= top;

         //mapObjO.style.visibility="hidden";
         //mapObjN.style.visibility="visible";
}

function checkArrows (field, evt) {
  var keyCode =
    document.layers ? evt.which :
    document.all ? event.keyCode :
    document.getElementById ? evt.keyCode : 0;


  var r = '';
  if (keyCode == 39 || keyCode == 57388 || String.fromCharCode(keyCode).toLowerCase()=='d') {
    r += 'arrow right';
    keyCode = key_right;
  }
  else if (keyCode == 40 || keyCode == 57386 || String.fromCharCode(keyCode).toLowerCase()=='s') {
    r += 'arrow down';
    keyCode = key_down;
  }
  else if (keyCode == 38 || keyCode == 57385 || String.fromCharCode(keyCode).toLowerCase()=='w') {
    r += 'arrow up';
    keyCode = key_up;
  }
  else if (keyCode == 37  || keyCode == 57387 || String.fromCharCode(keyCode).toLowerCase()=='a') {
    r += 'arrow left';
    keyCode = key_left;
  } else if( keyCode == 220 || keyCode == 49 || keyCode== 70 || keyCode== 81 || keyCode== 45) {
    //^1FQ0
    r += 'action key 1';
    keyCode = key_fire;
  } else if( String.fromCharCode(keyCode).toLowerCase() == 'r' ) {
    //r
    r += 'reload';
    loadFrom(lastLevel);
  }

  if(r!='') {
        if(keybuffer.push) {
                keybuffer.push(keyCode);
        }else{
               keybuffer[keybuffer.length]= keyCode;
        }

        //I don't manage to intercept keyboad events ..
        //scroll(0,0);
        return true;
  }

  //      var whichChar=String.fromCharCode(keyCode);
  r += ' ' + keyCode;
  if(debug) {
            window.status = r;
  }
  return false;
}

function focusOn(name) {
         var div= document.getElementById(name);
         if(div && div.focus) {
                div.focus();
         }
}

var focus_restart= false;

var bullets_number= 10;

// some versions of IE don't like loading many images, so we keep some static bullets

function drawStatusBullets() {
        var statusDiv= document.getElementById("status");
        text="";
        var x= 0;
        for(var i=0; i< bullets_number; ++i) {
          text+= '<div id="statusbullet'+i+'" style="position:absolute; left:'+x+';  z-index:16; visibility:hidden">';
          text+= '<img src="'+mapCodeToImage("bullet_big")+'" alt="o" border="0" height="'+bullet_height+'" width="'+bullet_width+'">';
          text+= '</div>';
          x+= bullet_width;
        }
        text+= '<div id="statustext" style="position: absolute; z-index:17;">&nbsp;</div>';

        statusDiv.innerHTML= text;
}


var last_show_bullets= -1;
function drawStatus() {
         var statusDiv= document.getElementById("status");
         var statustextDiv= document.getElementById("statustext");

         var text="";

         var show_bullets= protagonist.bullets;

         if(gameState.victory) {
            text='<b style="color: rgb(127, 255, 127);">You won!</b>';
            show_bullets= 0;
         } else if(protagonist.freeze) {
            text='<b style="color: red;">You loose! Failure lifts its ugly head.<a href="javascript:loadFrom(lastLevel);">restart!</a></b>';
            show_bullets= 0;
            if(!focus_restart) {
                focusOn("restart");
                focus_restart= true;
            }
         }

         if(show_bullets==0 && text=="") {
                statusDiv.style.visibility= 'hidden';
         } else {
                statusDiv.style.visibility= 'visible';
         }

         if(show_bullets!=last_show_bullets) {
            last_show_bullets= show_bullets;
            for(var i=0;i<bullets_number;++i) {
                    var bulletDiv= document.getElementById("statusbullet"+i);
                    var visibility= i<show_bullets?'visible':'hidden';
                    if(visibility!=bulletDiv.style.visibility) {
                            bulletDiv.style.visibility= visibility;
                            bulletDiv.style.zIndex= 16;
                    }
            }
         }
         if(show_bullets>bullets_number) {
            text= '<b>'+show_bullets+'</b>';
         }

         //uncomment this or fix html manually to stop flickering
         if(text!=statustextDiv.innerHTML) {
                //debug flickering
                if(debug && debugall) {
                        debugHTMLrep(text);
                }
                statustextDiv.innerHTML= text;
         }
}

//
// loading levels
//

function loadFrom(url){
     focus_restart= false;
     var scratchpadFrame= document.getElementById("scratchpadFrame");
     scratchpadFrame.src= url+'?'+Math.floor(Math.random()*1000000);
}


//
//fix IE mangled select values(remove trailing spaces)
//
function fixIE(value){
        if(value) {
                while( value.charCodeAt(value.length-1)<=32 ) {
                        value= value.substr(0,value.length-1);
                }
        }
        return value;
}

var levelSet= null;

function gotSets(setNames) {
        var setSelect= document.getElementById("setname");
        var options= setSelect.options;
        while(options.length>1)
                setSelect.remove(1);
//         var anOption = document.createElement("OPTION")
//         anOption.text = "--choose--";
//         anOption.value = "";
//         options.add(anOption);
        for(var i=0; i<setNames.length; ++i) {
                var anOption = document.createElement("OPTION")
                anOption.text = setNames[i];
                anOption.value = setNames[i];
                options.add(anOption);
        }
}

function gotLevels(levelNames) {
        var levelSelect= document.getElementById("levelname");
        var options= levelSelect.options;
        while(options.length>1)
                levelSelect.remove(1);
        for(var i=0; i<levelNames.length; ++i) {
                var anOption = document.createElement("OPTION")
                anOption.text = levelNames[i];
                anOption.value = "maps/"+levelSet+"/"+fixIE(levelNames[i])+'.html';
                options.add(anOption);
        }
}

//
// FIXME: some browser might require fiddling with selectedIndex
//

function loadSetIndex() {
     loadFrom("maps/list.html");
}


function loadSet(setInput) {
         var value= fixIE(setInput.value);
         if(value) {
             loadFrom("maps/"+value+"/list.html");
             levelSet= value;
             setInput.blur();
         }
}

function loadLevel(levelInput) {
        var level= fixIE(levelInput.value);
         if(level) {
             lastLevel= level;
             loadFrom(level);
             levelInput.blur();
         }
}

function loadNextMap(){
        var levelSelect= document.getElementById("levelname");
        var levelOptions= levelSelect.options;
        var selected= levelOptions.selectedIndex;
        ++selected;
        if( selected>=levelOptions.length ){
            var setSelect= document.getElementById("setname");
            var setOptions= setSelect.options;
            var selected= setOptions.selectedIndex;
            ++selected;
            setOptions.selectedIndex= selected>=setOptions.length?1:selected;
            loadSet(setSelect);
            setTimeout("loadNextMap()",1999);
        }else{
            levelOptions.selectedIndex= selected;
            loadLevel(levelSelect);
        }
}

function killBill(fileName) {
        return fileName.replace(/\\/gi,"/");
}

function loadFileFromFileinput(fileInput) {
         var fileName=fileInput.value;
         debugMsg("loading file:"+fileName);
         if(fileName) {
             fileName= "file:///"+killBill(fileName);
             loadFrom(fileName);
             fileInput.blur();
             return true;
         }else {
             return false;
         }
}

function loadFileFromUrlinput(urlInput) {
        var fileName=urlInput.value;
         debugMsg("loading url:"+fileName);
         if(fileName) {
             if(!fileName.match(/^http:/) ) {
                fileName= "http://"+fileName;
             }
             loadFrom(fileName);
             urlInput.blur();
             return true;
         }else {
             return false;
         }
}

function loadFileFromForm(form) {
//         if(! loadFileFromFileinput(form.filename) ) {
//             if(! loadFileFromUrlinput(form.url) )
              loadLevel(form.levelname);
//         }
        form.load.blur();
}

function loadFileFromFormname(formname) {
        loadFileFromForm(document[formname]);
}

function gotMaps(newMapsPre) {
        //only the first map is used currently
        if(newMapsPre.length>0) {
                if(map) {
                        map.clear();
                }
                map= readMap(newMapsPre[0]);
                resetGameState();
                resetGUI();
        }
}

//code graveyard, usefull stuff that I don't know by heart

//        document.body.addEventListener("keydown", processKeypress, true);


// code sample
//for (prop in scratchpadFrame) {
//       debugMsg(prop+":"+scratchpadFrame[prop]);
//}
//

// code sample, adding event listeners
// function addEvent(obj, evType, fn){
//  if (obj.addEventListener){
//    obj.addEventListener(evType, fn, true);
//    return true;
//  } else if (obj.attachEvent){
//    var r = obj.attachEvent("on"+evType, fn);
//    return r;
//  } else {
//    return false;
//  }
// }

//-->
</script>

<!-- bgsound -->
<script language="JavaScript" type="text/JavaScript"><!--
// See also: http://www.ricocheting.com/js/music.html
// var music="gayatri.mp3";
// if(navigator.product=="Gecko"){
// document.write('<embed src="'+music+'" autostart="true" loop="true" controls="SmallConsole" width=145 height=15></embed>');}
// else if(navigator.appName=="Microsoft Internet Explorer"){
// document.write('<embed src="'+music+'" autostart="true" loop="true" width=285 height=25></embed>');}
// else{
// document.write('<embed src="'+music+'" autostart="true" loop="true"></embed>');}
// //-->
// </script>
<!--noscript><embed src="gayatri.mp3" autostart="true" loop="true"></embed></noscript>
<noembed><bgsound src="gayatri.mp3" loop=true></noembed-->



</head>
<body onLoad="init();" onKeyDown="checkArrows(this,event)" style="margin: 0px 0px 0px 0px; padding: 0px 0px 0px 0px;">
<!-- FIXME: this fixed div(against scrolling) makes IE 5 incredibly slow -->
<div id="mapwrapper" style="position:fixed; top:0px; left:0px; height:400px; width:794px; overflow:hidden; background:#aa9988">

<!--begin screen-->
<div id="map0" style="position:absolute; z-index:10">
</div>
<div id="map1" style="position:absolute; z-index:10">
</div>
<div id="status" style="position:absolute; left:20px; width:384px; height:12px; z-index:15; background: rgb(0, 0, 127); visibility: hidden;" class="map">&nbsp;
</div>
<div id="protagonist0" style="position:absolute; z-index:20">
</div>
<div id="protagonist1" style="position:absolute; z-index:20; visibility:hidden;">
</div>



<div id="animation0" style="position:absolute; z-index:40; visibility:hidden;"></div>
<div id="animation1" style="position:absolute; z-index:40; visibility:hidden;"></div>
<div id="animation2" style="position:absolute; z-index:40; visibility:hidden;"></div>
<div id="animation3" style="position:absolute; z-index:40; visibility:hidden;"></div>
<div id="animation4" style="position:absolute; z-index:40; visibility:hidden;"></div>
<div id="animation5" style="position:absolute; z-index:40; visibility:hidden;"></div>
<div id="animation6" style="position:absolute; z-index:40; visibility:hidden;"></div>
<div id="animation7" style="position:absolute; z-index:40; visibility:hidden;"></div>
<div id="animation8" style="position:absolute; z-index:40; visibility:hidden;"></div>
<div id="animation9" style="position:absolute; z-index:40; visibility:hidden;"></div>


<div id="help" style="position:absolute;left:40px;width:320px;top:350px;">
<center>
Use the arrow keys to move. Also try "awds"-keys.
<br> 0,Q,F,1, or ^ shoots a bullet. R is restart. <A href="index.html" target="help">Help!</A>
</center>
</div>

<div id="by" style="position:absolute; left:452px; width:340px; top:150px;">
<center>
<h3>HylZee</h3>
game idea:<A href="http://www.huelsmann.net/">Roland G. H&uuml;lsmann</A>
<br>javascript coding:<A href="mailto:peter.schafer@gmail.com">(c)2004 Peter Sch&auml;fer</A>
<br>level design, set A:<A href="http://www.huelsmann.net/">Roland G. H&uuml;lsmann</A>
<br>level design, set B:<A href="mailto:peter.schafer@gmail.com">Peter Sch&auml;fer</A>
<br>Browsers tested: Opera7, IE5, FireFox 1.0
<br>This software is licensed under the GPL.
</center>
<p><!-- music -->
</div>

<!--end screen-->

<div id="controls" style="position:absolute; z-index:10; left:452px; top:20px; width:440px; height:90px;">
  <table border="0" class="controlembossed">
  <tr><td>

  <form name="form_controls" method="POST" enctype="multipart/form-data" style="margin:4px 4px 4px 4px" action="javascript:loadFileFromFormname('form_controls');">
  <table border="0" style="background: #66CC33;">
  <tr><td>
  level set:
  </td><td>
  <select id="setname" name="setname" OnChange="javascript:loadSet(this);return false;">
        <option value="">-- choose --</option>
        <option value="B">B</option>
  </select>
  level
  <select id="levelname"  name="levelname" OnChange="javascript:loadLevel(this);return false;">
        <option value="">-- choose --</option>
  </select>
<!--
  </td></tr><tr><td>
  load url:
   </td><td>
 <input type=text name="url" OnChange="javascript:loadFileFromUrlinput(this);return false;">
  </td></tr><tr><td>
  load file:
  </td><td>
  <input type=file name="filename" OnChange="javascript:loadFileFromFileinput(this);return false;">
-->
  </td></tr><tr><td align="center" colspan=2>
   <input type=button name="nextmap" value="next map" OnClick="javascript:
loadNextMap();">
   <input type=button id="restart" name="restart" value="restart last" OnClick="javascript:loadFrom(lastLevel);">
   <input type=button name="load" value="load" OnClick="javascript:loadFileFromFormname('form_controls');">
   &nbsp; &nbsp; <input type=reset value="reset" OnClick="javascript:return true;">
  </td></tr>
  </table>
  </form>

  </td></tr>
  </table>
</div>

<!-- load data with javascript -->
<!-- I have made this visible, since browsers might consider it bad security otherwise -->
<div id=sp0 style="position:absolute; z-index:0; visibility:visible; top:350px; left:600px; height:40px; width:40px; overflow:hidden">
        <iframe src="about:blank" id="scratchpadFrame" height="20" width="40"></iframe>
        <!-- scratchpad to convert innerHTML to browser representation -->
        <div id="scratchpad" style="position:absolute; z-index:0; visibility:visible; overflow: hidden;"></div>
</div>

<div id="debug" style="position:absolute;left:452px;width:300px;">
      <div id="debugonoff" style="position:absolute;width:300px;height:30px;top:155px; background:#EEEEEE; visibility: hidden;">
      <center>
      <form action="javascript:return false;" style="margin: 0px;">
       <input type=button value="debug on/off" OnClick="javascript:debug=!debug;document.getElementById('debugoutput').style.visibility='visible'; this.value='debug '+(debug?'off':'on')">
       <input type=button value="clear" OnClick="javascript:document.getElementById('debug').innerHTML=''">
       <input type=button value="proceed/stop" OnClick="javascript:mainProceed=!mainProceed;this.value=(mainProceed?'stop':'proceed')">
      </form>
      </center>
      </div>

      <div id="debugoutput" style="position:absolute; top:190px; width:300px; height:320px; overflow:auto; background:#EEEEEE; font-family: fixed; visibility: hidden;">
      <h4>debug log</h4>
      </div>
</div>

</div>

</body>
</html>



           
       








hylZee-001.zip( 182 k)

Related examples in the same category

1.Chess: Draughts
2.Mine game
3.Word search game
4.Ranisima english
5.Yasminuroban (by Joan Alba Maldonado)
6.Level editor for Yasminuroban by Joan Alba Maldonado
7.js mine sweeper
8.Another tictactoe
9.Marbles
10.Jigsaw
11.Game sudoku
12.Game PunkPong
13.Tetris in Javascript
14.Arrange Game
15.Guess Number
16.Tic tac toe
17.Count Game
18. A JavaScript Hangman Game
19.Game library
20.Type Tutor
21.Game: Place It (IE only)