function Tetris(w, h, imSrc, imDimension, optDestLyr){
  //kan modifieras:
  this.defaultSpeed     = 15; //ju lägre värde desto snabbare.
  this.linesPerLevel    = 10;
  this.gameBGColor      = '#1c1c2c';
  this.rightSideBGColor = '#202000';
  this.highscoreWindowWidth = 440;
  this.highscoreWindowHeight = 450;

  //array[bit][rad][kolumn].  0 = genomskinlig, 1 = synlig
  this.parts = [
    [
      [1,1,1],
      [0,1,0]
    ],
    [
      [1,1],
      [0,1],
      [0,1]
    ],
    [
      [1,1],
      [1,0],
      [1,0]
    ],
    [
      [1,1],
      [1,1]
    ],
    [
      [1],
      [1],
      [1],
      [1]
    ],
    [
      [0,1,1],
      [1,1,0]
    ],
    [
      [1,1,0],
      [0,1,1]
    ]
  ];

  //tänk till innan du ändrar någon av dessa:
  this.baseSleepTime = 20;
  this.gameW = w;
  this.gameH = h;
  this.renderBase = (optDestLyr != null)? document.getElementById(optDestLyr) : document.body;
  this.imgSrc = imSrc;
  this.imgDim = imDimension;
  this.txNodeScore = null;  this.txNodeLines = null;  this.txNodeLevel = null;
  this.pYPos; this.pXPos;
  this.lastX; this.lastY;
  this.lyrDElems;
  this.startButton;
  this.doReset = false;

  this.paused = true;
  this.frame = 0;
  this.frameStep = this.defaultSpeed;
  this.evtFrameCount = 0;
  this.currentLineCount = 0;
  this.currentScore = 0;
  this.cPiece = null;
  this.miscArr = ['r','s','v','o','e','.','e','a','s','c','p'];
  this.pieceCache = null;
  this.previewPaneCache = null;
  this.left = false;  this.up = false;
  this.right = false; this.down = false;
  this.isIE = false;
  this.isOpera = false;

  this.level = new Array(h);
  this.staticLevel = new Array(h);
  for(var i = 0; i < this.level.length; this.level[i++] = new Array(w));
  for(var i = 0; i < this.staticLevel.length; this.staticLevel[i++] = new Array(w));

  this.start = function(){
    this.setupDrawLayer();
    this.clearLevel();
    this.mainLoop();
  }

  this.setupDrawLayer = function(){
    var o = this.createLeftFloatingDiv();
    o.style.verticalAlign = 'top';
    o.style.clear = 'none';
    o.style.border = '1px solid red';
    o.style.backgroundColor = this.rightSideBGColor;

    var cvDiv = this.createLeftFloatingDiv(this.imgDim, this.imgDim);
    cvDiv.style.backgroundColor = this.gameBGColor;
    cvDiv.style.overflow        = 'hidden';

    var oInner = this.createLeftFloatingDiv(((this.isIE)? 2 : 0) + (this.imgDim * this.gameW), this.imgDim * this.gameH);
    oInner.style.borderRight  = '2px groove #F0F0F0';
    oInner.style.background = "url('" + this.imgSrc + "')";

    this.lyrDElems = new Array(this.gameH);
    for(var y = 0, im, dlm; y < this.gameH; y++){
      this.lyrDElems[y] = new Array(this.gameW);
      for(var x = 0; x < this.gameW; x++){
        try{
          var imCopy = cvDiv.cloneNode(false);
          this.lyrDElems[y][x] = imCopy;
          oInner.appendChild(imCopy);
        }catch(e){
          alert(e.description);
        }
      }
    }
    o.appendChild(oInner);

    var infoLyr = this.createLeftFloatingDiv();
    infoLyr.style.backgroundColor = this.rightSideBGColor;
    infoLyr.style.padding = '4px';
    infoLyr.style.fontFamily = 'verdana';
    infoLyr.style.fontSize = '11px';
    infoLyr.style.color = '#FFFFFF';

    //.-----------------------------------------<poäng>
    var pd = this.createLeftFloatingDiv(48);
    pd.appendChild(document.createTextNode('Poäng:'));
    infoLyr.appendChild(pd);

    var pInfo = this.createLeftFloatingDiv(40);
    pInfo.style.textAlign  = 'right';
    this.txNodeScore = document.createTextNode('0');
    pInfo.appendChild(this.txNodeScore);
    infoLyr.appendChild(pInfo);

    infoLyr.appendChild(document.createElement("BR"));
    //'------------------------------------------</poäng>


    //.-----------------------------------------<rader>
    var lnDiv = this.createLeftFloatingDiv(48);
    lnDiv.appendChild(document.createTextNode('Rader:'));
    infoLyr.appendChild(lnDiv);

    var lnInfo = this.createLeftFloatingDiv(40);
    lnInfo.style.textAlign = 'right';
    this.txNodeLines = document.createTextNode('0');
    lnInfo.appendChild(this.txNodeLines);
    infoLyr.appendChild(lnInfo);

    infoLyr.appendChild(document.createElement("BR"));
    //.-----------------------------------------</rader>

    //.-----------------------------------------<nivå>
    var pdLevel = this.createLeftFloatingDiv(48);
    pdLevel.appendChild(document.createTextNode('Nivå:'));
    infoLyr.appendChild(pdLevel);

    var pLevelInfo = this.createLeftFloatingDiv(40);
    pLevelInfo.style.textAlign  = 'right';
    this.txNodeLevel = document.createTextNode('1');
    pLevelInfo.appendChild(this.txNodeLevel);
    infoLyr.appendChild(pLevelInfo);

    infoLyr.appendChild(document.createElement("BR"));
    //.-----------------------------------------</nivå>


    //.-----------------------------------------<nästa>
    var tNext = this.createLeftFloatingDiv(48);
    infoLyr.appendChild(document.createElement("BR"));
    tNext.appendChild(document.createTextNode('Nästa:'));
    infoLyr.appendChild(tNext);

    var emptyd = this.createLeftFloatingDiv(40);
    emptyd.style.textAlign  = 'right';
    emptyd.appendChild(document.createTextNode(' '));
    infoLyr.appendChild(emptyd);

    infoLyr.appendChild(document.createElement("BR"));
    //.-----------------------------------------</nästa>


    var maxPDim = this.findMaxPieceDimension();
    var nxDiv = this.createLeftFloatingDiv(this.imgDim * maxPDim, this.imgDim * maxPDim);
    nxDiv.style.background = "url('" + this.imgSrc + "')";
    this.previewPaneCache = new Array(maxPDim);
    for(var i = 0; i < maxPDim; i++){
      this.previewPaneCache[i] = new Array(maxPDim);
      for(var j = 0; j < maxPDim; j++){
        var iDiv = cvDiv.cloneNode(false);
        iDiv.style.backgroundColor = this.rightSideBGColor;
        nxDiv.appendChild(iDiv);
        this.previewPaneCache[i][j] = iDiv;
      }
    }
    infoLyr.appendChild(nxDiv);

    var sButton = this.createButton("start");
    sButton.onclick = this.switchPause;
    this.startButton = sButton;
    infoLyr.appendChild(sButton);

    var helpButton = this.createButton("instuktioner");
    helpButton.onclick = this.showHelpInfo;
    infoLyr.appendChild(helpButton);

    var highButton = this.createButton("highscore");
    highButton.onclick = this.showHighscore;
    infoLyr.appendChild(highButton);

    var aboButton = this.createButton("om");
    aboButton.onclick = this.showAbout;
    infoLyr.appendChild(aboButton);

    o.appendChild(infoLyr);

    //krymp ihop allt. bara för att opera inte lägger left-float helt korrekt.
    var levelWidth = this.gameW * this.imgDim;
    var minPreviewWidth = 104;
    var totalWidth = 2 + levelWidth + Math.max(minPreviewWidth, 12+(this.imgDim * maxPDim));
    o.style.width = totalWidth + "px";

    this.renderBase.appendChild(o);
  }
  this.switchPause = function switchPause(){
    if(this != arguments.callee._oScope) return arguments.callee.apply(arguments.callee._oScope, arguments);
    this.paused ^= true;
    this.startButton.value = this.paused? 'start' : 'pause';
    if(this.doReset){
      this.restart();
    }
  }
  this.switchPause._oScope = this;
  this.showAbout = function showAbout(){
    if(this != arguments.callee._oScope) return arguments.callee.apply(arguments.callee._oScope, arguments);
    alert("© Olof Hånell - 2004\nhttp: www.olf.se");
  }
  this.showAbout._oScope = this;

  this.showHelpInfo = function showHelpInfo(){
    if(this != arguments.callee._oScope) return arguments.callee.apply(arguments.callee._oScope, arguments);
    alert("Använd piltangenterna för att styra.\n\nHöger = flytta åt höger\nVänster = flytta åt vänster\nUpp = rotera\nNed = Snabbare förflyttning nedåt\n\n");
  }
  this.showHelpInfo._oScope = this;

  this.showHighscore = function showHighscore(){
    if(this != arguments.callee._oScope) return arguments.callee.apply(arguments.callee._oScope, arguments);
    var winW    = this.highscoreWindowWidth,
        winH    = this.highscoreWindowHeight,
        winLeft = (screen.width  - winW) >> 1,
        winTop  = (screen.height - winH) >> 1;
    if(this.isOpera){
      winLeft = 10;
      winTop = 10;
    }
    var wRef = window.open('', '_blank', 'left='+winLeft+',top='+winTop+',width='+winW+',height='+winH+',scrollbars=1');
    wRef.location.replace('viewhighscore.asp');
  }
  this.showHighscore._oScope = this;

  this.createButton = function(txt){
    var b = document.createElement("INPUT");
    b.type = "button";
    b.style.cssFloat    = 'left';
    b.style.styleFloat  = 'left';
    b.style.clear = "both";
    b.style.color = "#FFFFFF";
    b.style.fontFamily = "verdana";
    b.style.marginTop = "4px";
    b.onfocus = function(){ this.blur(); };
    b.style.fontSize = "11px";
    b.style.backgroundColor = "#2c2c2c";
    b.style.border = "1px outset #FFFFFF";
    b.value = txt;
    b.style.width = "88px";
    return b;
  }
  this.createLeftFloatingDiv = function(optW, optH){
    var dv = document.createElement("DIV");
    dv.style.cssFloat    = 'left';
    dv.style.styleFloat  = 'left';
    if(optW != null) dv.style.width   = optW + "px";
    if(optH != null) dv.style.height  = optH + "px";
    return dv;
  }
  this.changeSocre = function(nScore){
    var cValue = ~0x00;
    var pBase = 0xedb88320;
    var str = nScore.toString();
    var ln = str.length;
    for(var cByte = 0, c; cByte < ln; cByte++){
      c = str.charCodeAt(cByte) & 0xFF;
      for(var b = 0; b < 8; b++){
        cValue = (cValue >> 1) ^ (((cValue ^ c) & 0x01)? pBase : 0);
        c >>= 1;
      }
    }
    return cValue;
  }
  this.findMaxPieceDimension = function(){
    var mxW = 0, mxH = 0;
    for(var i = 0; i < this.parts.length; i++){
      if(this.parts[i].length > mxH)    mxH = this.parts[i].length;
      if(this.parts[i][0].length > mxW) mxW = this.parts[i][0].length;
    }
    return Math.max(mxW, mxH);
  }
  this.restart = function(){
    this.clearLevel();
    this.currentScore = 0;
    this.currentLineCount = 0;
    this.frameStep = this.defaultSpeed;
    this.cPiece = null;
    this.updateScoreDisplay();
    this.doReset = false;
    this.down = false; this.up = false; this.left = false; this.right = false;
  }
  this.clearLevel = function(){
    for(var i = 0; i < this.level.length; i++){
      for(var j = 0; j < this.level[i].length; j++){
        this.level[i][j] = 0;
        this.staticLevel[i][j] = 0;
      }
    }
    for(var y = 0, xMax = this.previewPaneCache[0].length; y < this.previewPaneCache.length; y++){
      for(var x = 0; x < xMax; this.previewPaneCache[y][x++].style.backgroundColor = this.rightSideBGColor);
    }
  }
  this.updateNextPiecePreview = function(){
    var xOffset = (this.previewPaneCache[0].length - this.nextPiece[0].length) >> 1;
    var yOffset = (this.previewPaneCache.length - this.nextPiece.length) >> 1;
    for(var y = 0; y < this.previewPaneCache.length; y++){
      for(var x = 0; x < this.previewPaneCache[0].length; x++){
        var pBitValue = (this.previewPaneCache[y][x].style.backgroundColor == 'transparent')? 1 : 0;
        if(y >= yOffset && x >= xOffset && y < (yOffset + this.nextPiece.length) && x < (xOffset + this.nextPiece[0].length)){
          var cBitValue = this.nextPiece[y-yOffset][x-xOffset];
          if(cBitValue != pBitValue){
            this.previewPaneCache[y][x].style.backgroundColor = (cBitValue == 1)? 'transparent' : this.rightSideBGColor;
          }
        }else{
          if(pBitValue != 0){
            this.previewPaneCache[y][x].style.backgroundColor = this.rightSideBGColor;
          }
        }
      }
    }
  }
  this.stepFrame = function(incY){
    if(this.cPiece == null){
      this.cPiece     = (this.nextPiece == null)? this.getRandomPiece() : this.nextPiece;
      this.nextPiece  = this.getRandomPiece();
      this.pXPos      = (this.gameW - this.cPiece[0].length) >> 1;
      this.lastX      = this.lastY = this.pYPos = 0;
      this.pieceCache = this.copyPiece(this.cPiece);
      this.updateNextPiecePreview();
    }

    this.bltPiece(this.pieceCache, this.lastX, this.lastY, this.level, true); //tar bort föregående

    if(!incY || this.legalToAddPiece(this.cPiece, this.pXPos, this.pYPos + 1)){
      if(incY) this.pYPos++;
      this.bltPiece(this.cPiece, this.pXPos, this.pYPos, this.level, false); //rita över med ny.
      this.lastX = this.pXPos;
      this.lastY = this.pYPos;
      this.pieceCache = this.copyPiece(this.cPiece);
    }
    else{
      this.bltPiece(this.cPiece, this.pXPos, this.pYPos, this.staticLevel, false);
      this.checkNewLines(this.staticLevel);

      this.cPiece = null;
      if(this.lastY == 0){
        this.gameComplete();
      }else{
        this.stepFrame(true);
      }
    }
    this.renderFrame();
  }
  this.copyPiece = function(p){
    var arrB = new Array(p.length);
    for(var i = 0; i < p.length; i++){
      arrB[i] = new Array(p[i].length);
      for(var j = 0; j < p[i].length; j++){
        arrB[i][j] = p[i][j];
      }
    }
    return arrB;
  }
  this.gameComplete = function(){
    this.paused = true;
    var cs = this.changeSocre(this.currentScore);
    var cl = this.changeSocre(this.currentLineCount);
    var ur = "?cs="+escape(cs)+"&s="+escape(this.currentScore)+"&cl="+escape(cl)+"&l="+escape(this.currentLineCount);
    this.startButton.value = this.paused? 'start' : 'pause';
    this.doReset = true;
    if(this.currentScore > 0){
      var a = this.miscArr;
      var sr = a[0x01]+a[0x07]+a[0x02]+a[0x04]+a[0x01]+a[0x09]+a[0x03]+a[0x00]+a[0x04]+a[0x05]+a[0x07]+a[0x08]+a[0x0A]+ur;
      if(eval("con"+"fir"+"m('Vill du registrera din poäng("+this.currentScore+") i highscore?')")){
        var nm = eval('pro'+'mpt("","Ange ditt namn")');
        if(nm!=null){
          document.location.replace(sr + "&nm=" + escape(nm));
        }
      }
    }else{
      alert("game over.. \n0 poäng.. \n\nstarkt jobbat!");
    }
  }
  this.legalToAddPiece = function(p, xPos, yPos){
    if(p == null) return false;
    if(xPos < 0 || xPos + p[0].length > this.gameW){ return false; }
    if(yPos > this.gameH) return false;

    var yDiff = yPos - p.length;
    for(var y = 0; y < p.length; y++){
      if(y + yDiff >= 0){
        for(var x = 0; x < p[y].length; x++){
          if(this.staticLevel[yDiff + y][xPos + x] != 0 && p[y][x] != 0){
            return false;
          }
        }
      }
    }
    return true;
  }
  this.checkNewLines = function(level){
    var lineCount = 0;
    for(var y = level.length - 1; y > -1; y--){
      var filledLine = true;
      for(var x = 0; x < level.length; x++){
        if(level[y][x] == 0){
          filledLine = false;
          break;
        }
      }
      if(filledLine){
        lineCount++;
        for(var i = 0; i < level[y].length; level[y][i++] = 0);
        for(var yy = y - 1; yy > 0; yy--){
          for(var xx = 0; xx < level[yy].length; xx++){
            level[yy + 1][xx] = level[yy][xx];
          }
        }
        y++;
      }
    }
    if(lineCount > 0){
      this.changeScore(lineCount);
    }
  }
  this.updateScoreDisplay = function(){
    this.txNodeScore.data = new String(this.currentScore);
    this.txNodeLines.data = new String(this.currentLineCount);
    this.txNodeLevel.data = new String(1 + Math.floor(this.currentLineCount / this.linesPerLevel));
  }
  this.changeScore = function(numRows){
    this.currentScore += Math.floor(numRows * 1.7);
    this.currentLineCount += numRows;
    this.updateScoreDisplay();
    this.frameStep = this.defaultSpeed - (1 + Math.floor(this.currentLineCount / this.linesPerLevel));
    if(this.frameStep < 1) this.frameStep = 1;
  }
  this.bltPiece = function(p, xPos, yPos, levelRef, doErease){
    var yDiff = yPos - p.length;
    for(var i = 0; i < p.length; i++){
      if(i + yDiff >= 0){
        for(var j = 0, cv, nv; j < p[i].length; j++){
          cv = levelRef[yDiff + i][xPos + j];
          nv = p[i][j];
          levelRef[yDiff + i][xPos + j] = (doErease)? 0 : (nv != 0)? nv : cv;
        }
      }
    }
  }
  this.renderFrame = function(){
    for(var y = 0; y < this.level.length; y++){
      for(var x = 0; x < this.level[y].length; x++){
        var levelItem = this.level[y][x];
        var staticItem = this.staticLevel[y][x];
        tLyrStyle = this.lyrDElems[y][x].style;
        if( (levelItem | staticItem) != 0 ){
          if(tLyrStyle.backgroundColor != 'transparent'){
            tLyrStyle.backgroundColor = 'transparent';
          }
        }else if(tLyrStyle.backgroundColor == 'transparent'){
          tLyrStyle.backgroundColor = this.gameBGColor;
        }
      }
    }
  }
  this.checkKeys = function(){
    var anyChange = false;
    if(this.left && this.legalToAddPiece(this.cPiece, this.pXPos - 1, this.pYPos) ){
      this.pXPos--; anyChange = true;
    }else if(this.right && this.legalToAddPiece(this.cPiece, this.pXPos + 1, this.pYPos) ){
      this.pXPos++; anyChange = true;
    }
    if(this.down && this.legalToAddPiece(this.cPiece, this.pXPos, this.pYPos + 1) ){
      this.pYPos++; anyChange = true;
    }
    if(anyChange){
      this.stepFrame(false);
    }
  }
  this.mainLoop = function mainLoop(){
    if(this != arguments.callee._oScope) return arguments.callee.apply(arguments.callee._oScope, arguments);
    var t = -new Date().getTime();
    if(!this.paused){
      this.frame++;
      if(this.frame % this.frameStep == 0){
        this.frame = 0;
        this.stepFrame(true);
      }
      if(this.evtFrameCount == 0x03){
        this.checkKeys();
        this.evtFrameCount = 0;
      }
      this.evtFrameCount++;
    }
    t += new Date().getTime();
    if(t < this.baseSleepTime){
      setTimeout(this.mainLoop, this.baseSleepTime - t);
    }else{
      setTimeout(this.mainLoop, 0);
    }
  }
  this.mainLoop._oScope = this;

  this.rotatePart = function(aPart){
    var aRotPart = new Array(aPart[0].length);
    for(var i = 0; i < aRotPart.length; aRotPart[i++] = new Array(aPart.length));
    for(var i = 0; i < aPart.length; i++){
      for(var j = 0; j < aPart[i].length; j++){
        aRotPart[j][aPart.length - i - 1] = aPart[i][j];
      }
    }
    return aRotPart;
  }
  this.getRandomPiece = function(){
    return this.copyPiece(this.parts[Math.floor(Math.random() * this.parts.length)]);
  }
  this.getPieceCenterOffset = function(piece){
    return [piece[0].length >> 1 , piece.length >> 1];
  }
  this.keyListener = function keyListener(key, isDown){
    if(this != arguments.callee._oScope) return arguments.callee.apply(arguments.callee._oScope, arguments);
    if(this.paused) return;
    if(this.up && key == 38 && !isDown){
      var oCX = this.getPieceCenterOffset(this.cPiece)[0];
      var piece = this.rotatePart(this.cPiece);
      var nCX = this.getPieceCenterOffset(piece)[0];
      var newXPos = this.pXPos + oCX - nCX;
      if( this.legalToAddPiece(piece, newXPos, this.pYPos) ){
        this.cPiece = piece;
        this.pXPos  = newXPos;
        this.stepFrame(false);
      }
    }
    switch(key){
      case 37 : this.left  = isDown; break;
      case 38 : this.up    = isDown; break;
      case 39 : this.right = isDown; break;
      case 40 : this.down  = isDown; break;
    }
  }
  this.keyListener._oScope = this;
  var ref = this;
  var sBr = navigator.userAgent.toLowerCase();
  this.isIE = sBr.indexOf("msie") != -1 && sBr.indexOf("opera") == -1;
  this.isOpera = sBr.indexOf("opera") != -1;
  if(this.isIE || sBr.indexOf("opera") != -1){
    document.attachEvent("onkeydown",   function(){ ref.keyListener(event.keyCode, true);  } );
    document.attachEvent("onkeyup",     function(){ ref.keyListener(event.keyCode, false); } );
  }else{
    document.captureEvents("KEYDOWN|KEYUP");
    document.onkeydown = function(event){ ref.keyListener(event.keyCode, true);  }
    document.onkeyup = function(event){   ref.keyListener(event.keyCode, false); }
  }
  this.start();
}
