Autor Zpráva
Witiko
Profil
DHTML snake

Takhle to dopadá, když se jeden nudí. :) CSS hacky dočasné, grafickým interface se budu zabývat až budu spokojený se scriptovou částí. Objekt gameField vykreslí tabulku tak velikou, aby jedna buňka měla 30x30 px, proces "vykreslování" do tabulky se děje změnou class atributu jednotlivých buněk, veškerý vzhled má tedy na starosti css. Prototyp Snake má na starost hada, časem chci přidat do hry několik AI hadů. Třída Listener obsluhuje klávesy.

Osobní rekord: 5134 bodů

Pattern na výpočet skóre:
1 bod za každé 3 články těla

* 1 - 3 za rychlost při vypnutých zdech (přednastavená rychlost je maximální)
nebo
* 1 - 5 za rychlost při zapnutých zdech (přednastavená rychlost je maximální)

* 1 - 2 za poměr těla hada vůči všem políčkům (2 pokud had zabírá veškerá políčka)
* 3 jde-li o bonusové jídlo.

Patern na výpočet rychlosti:
Min. rychlost: Had přejede průměr(kratší, delší) stranu okna za 6 sekund
Max. rychlost: Had přejede průměr(kratší, delší) stranu okna za 1 sekundu
Dodatečné zpomalení pro větší hrací pole, 10 políček na kratší straně: 0 sekund, pak 700ms po 20 políčkách

Pattern na výpočet rychlosti spawnu bonus jídla:
Max. rychlost: 20 - 1 sekunda za poměr těla hada vůči všem políčkům (1 pokud had zabírá veškerá políčka)
Min. rychlost:
- Vypnuté zdi: Had jede 6x pomaleji, ale také dostává 3x méně bodů = 2x delší spawn, +2x penalizace za nižší rychlost - 80 - 4 sekundy za poměr těla hada vůči všem políčkům
- Zapnuté zdi: Had jede 6x pomaleji, ale také dostává 5x méně bodů = 1.2x delší spawn, +2x penalizace za nižší rychlost - 48 - 2.4 sekundy za poměr těla hada vůči všem políčkům

Pattern na výpočet trvání bonus jídla:
Vzdálenost hada od jídla (ne po přeponě, ale součet x a y tečen, při vypnutých stěnách se počítá přes stěny) * patternNaVýpočetIntervaluTahů
* 1,5 v případě vypnutých zdí
+ 2 tahy * patternNaVýpočetIntervaluTahů (na možnost otočení do protisměru)
+ 1000 milisekund (na fadeIn a fadeOut)

Snake prototyp:

function Snake() {
  var newdirection = false, interval, length,
  lastCell = function(that,x,y) {return (x == that.history[Ego.history.length-1][0] && y == that.history[Ego.history.length-1][1]);}
  this.spawn = function() {
    this.history = [];
    var x = this.position[0];
    var y = this.position[1];
    if(field.get(x,y) == food) spawn(food);
    field.spawn(x,y,this.head);
    for(var count = 0; count < this.length; count++) {
      if(this.direction == 0) x++;
      if(this.direction == 1) y++;
      if(this.direction == 2) x--;
      if(this.direction == 3) y--;
      if(field.get(x,y) == food) spawn(food);
      field.spawn(x,y,this.body);
      this.history[count] = [x,y];
    }
    var that = this;
    length = this.length;
  }
  this.changeDirection = function(direction) {
    if(this.direction == 0 && direction != 0 && direction !=2) newdirection = direction;
    if(this.direction == 1 && direction != 1 && direction !=3) newdirection = direction;
    if(this.direction == 2 && direction != 0 && direction !=2) newdirection = direction;
    if(this.direction == 3 && direction != 1 && direction !=3) newdirection = direction;
  }
  this.move = function() {
    if(newdirection !== false) {
      this.direction = newdirection;
      newdirection = false;
    }
    var x = this.position[0];
    var y = this.position[1];
    if(this.direction == 0) x--;
    if(this.direction == 1) y--;
    if(this.direction == 2) x++;
    if(this.direction == 3) y++;
    if((x < 0 || x >= width || y < 0 || y >= height) && !this.collision) {
      if(x < 0) x = width-1;
      if(x >= width) x = 0;
      if(y < 0) y = height-1;
      if(y >= height) y = 0;
    }
    var get = field.get(x,y);
    var last = get?lastCell(this,x,y):false;
    if(get && !last) {
      this.collisionCallback(x,y,get);
    } else if((x < 0 || x >= width || y < 0 || y >= height) && this.collision) {
      this.deathCallback();
    } else {
      var increaseSize = false;
      if(length != this.length) {
        increaseSize = true;
        length = this.length;
      }
      this.history.unshift(this.position.slice(0));
      field.move(this.position[0],this.position[1],x,y);
      this.position = [x,y];
      for(var count = this.history.length - 1; count > 0; count --) {
        if(count == this.history.length - 1 && last) {
          field.spawn(this.history[count-1][0],this.history[count-1][1],this.body);
        } else {
          field.copy(this.history[count][0],this.history[count][1],this.history[count-1][0],this.history[count-1][1]);
        }
      }
      if(!increaseSize) {
        if(!last) field.remove(this.history[this.history.length-1][0],this.history[this.history.length-1][1]);
        this.history.pop();
      }
    }
  }
}


GameField prototyp:

function gameField(width,height) {
  var field = [],
      HTMLfield = [];
  this.toHTML = function() {
    var table = document.createElement("table"),
        tbody = document.createElement("tbody"),
        tr, td;
        table.className = "gameField";
    for(var count = 0; count < height; count++) {
      tr = document.createElement("tr");
      for(var count2 = 0; count2 < width; count2++) {
        td = document.createElement("td");
        if(field[count] && field[count][count2]) td.className = field[count][count2];
        tr.appendChild(td);
        if(!HTMLfield[count2]) HTMLfield[count2] = [];
        HTMLfield[count2][count] = td;
      }
      tbody.appendChild(tr);
    }
    table.appendChild(tbody);
    pointer = table;
    return table;
  }
  this.spawn = function(x, y, what) {
    if(!field[x]) field[x] = [];
    field[x][y] = what;
    if(HTMLfield[x] && HTMLfield[x][y]) HTMLfield[x][y].className = what;
  }
  this.copy = function(x, y, x2, y2) {
    if(!field[x]) field[x] = [];
    if(!field[x2]) field[x2] = [];
    field[x2][y2] = field[x][y];
    if(HTMLfield[x2] && HTMLfield[x2][y2]) HTMLfield[x2][y2].className = field[x][y];
  }
  this.move = function(x, y, x2, y2) {
    if(!field[x]) field[x] = [];
    if(!field[x2]) field[x2] = [];
    field[x2][y2] = field[x][y];
    if(HTMLfield[x2] && HTMLfield[x2][y2]) HTMLfield[x2][y2].className = field[x][y];
    field[x][y] = null;
    if(HTMLfield[x] && HTMLfield[x][y]) HTMLfield[x][y].className = "";
  }
  this.remove = function(x, y) {
    if(!field[x]) field[x] = [];
    field[x][y] = null;
    if(HTMLfield[x] && HTMLfield[x][y]) HTMLfield[x][y].className = "";
  }
  this.clear = function() {
    field = [];
    for(var count = 0; count < height; count++) {
      if(!HTMLfield[count]) HTMLfield[count] = [];
      for(var count2 = 0; count2 < width; count2++) {
        if(HTMLfield[count][count2]) HTMLfield[count][count2].className = "";
      }
    }
  }
  this.get = function(x,y) {
    return field[x] && field[x][y]?field[x][y]:false;
  }
  this.getPointer = function(x,y) {
    return HTMLfield[x] && HTMLfield[x][y]?HTMLfield[x][y]:false;
  }
  this.getFree = function() {
    var places = [];
    for(var count = 0; count < width; count++) {
      if(!field[count]) {
        for(var count2 = 0; count2 < height; count2++) {
          places.push([count,count2]);
        }
      } else {
        for(var count2 = 0; count2 < height; count2++) {
          if(!field[count][count2]) places.push([count,count2]);
        }
      }
    }
    return places;
  }
  this.isFull = function() {
    for(var count = 0; count < width; count++) {
      if(!field[count]) field[count] = [];
      for(var count2 = 0; count2 < height; count2++) {
        if(!field[count][count2]) return false;
      }
    }
    return true;
  }
  this.getPos = function(x,y) {
    if(!HTMLfield[x] || !HTMLfield[x][y]) return false;
    var pos = findPos(HTMLfield[x][y]);
    var screen = getScreen();
    if(isIE || pos[0] > screen[0]) pos[0] -= HTMLfield[x][y].offsetWidth;
    pos[1] += (HTMLfield[x][y].offsetHeight - length) - HTMLfield[x][y].offsetHeight / 2;
    return pos;
  }
}
Witiko
Profil
Listener prototyp:

function Listener() {
  var enabled = 0,
      callback,
      codes,
      preventDefault = function(e) {
        if(!e) e = window.event;
        e.returnValue = false;
        return false;
      },
      collectKeyboard = function(e) {
        if(!e) e = window.event;
        e.returnValue = false;
        if(e.repeat) return false;
        var output = [];
        if(e.shiftKey) output.push(keyCodes[16])
        if(e.ctrlKey) output.push(keyCodes[17])
        if(e.altKey) output.push(keyCodes[18])
        if(!returnCodes[e.keyCode]) {
          if(keyCodes[e.keyCode]) output.push(keyCodes[e.keyCode]);
          else output.push(l[0] + e.keyCode);
        }
        if(callback && typeof callback == "function") {
          callback({
            "shiftKey" : e.shiftKey,
            "ctrlKey" : e.shiftKey,
            "altKey" : e.altKey,
            "keyCode" : returnCodes[e.keyCode]?null:e.keyCode,
            "keyName" : output.join(", ")
          });
        }
        return false;
      },
      listenToKeyboard = function(e) {
        if(!e) e = window.event;
        if(e.repeat) return false;
        var found = false;
        for(var count = 0; count < codes.length; count++) {
          if(
            codes[count]["shiftKey"] == e.shiftKey &&
            codes[count]["ctrlKey"] == e.ctrlKey &&
            codes[count]["altKey"] == e.altKey &&
            (returnCodes[e.keyCode] || 
            (codes[count]["keyCode"] == e.keyCode ||
            (codes[count]["keyCode"].indexOf(e.keyCode) >= 0 &&
            typeof codes[count]["keyCode"] == "object")))
          ) {found = true;break;}
        }
        if(!found) return true;
        if(callback && typeof callback == "function") callback(count);
        e.returnValue = false;
        return false;
      },
      collectMouse = function(e) {
        if(!e) e = window.event;
        e.returnValue = false;
        if(e.repeat) return true;
        var button = e.which || e.button;
        var output = [];
        if(e.shiftKey) output.push(keyCodes[16])
        if(e.ctrlKey) output.push(keyCodes[17])
        if(e.altKey) output.push(keyCodes[18])
        if(mouseCodes[button]) output.push(mouseCodes[button]);
        else output.push(l[1] + button);
        if(callback && typeof callback == "function") {
          callback({
            "shiftKey" : e.shiftKey,
            "ctrlKey" : e.shiftKey,
            "altKey" : e.altKey,
            "keyCode" : "M" + button,
            "keyName" : output.join(", ")
          });
        }
        return false;
      },
      listenToMouse = function(e) {
        if(!e) e = window.event;
        if(e.repeat) return false;
        var button = e.which || e.button;
        var found = false;
        for(var count = 0; count < codes.length; count++) {
          if(
            codes[count]["shiftKey"] == e.shiftKey &&
            codes[count]["ctrlKey"] == e.ctrlKey &&
            codes[count]["altKey"] == e.altKey &&
            codes[count]["keyCode"] == "M" + button ||
            (codes[count]["keyCode"].indexOf("M" + button) >= 0 &&
            typeof codes[count]["keyCode"] == "object")
          ) {found = true;break;}
        }
        if(!found) return true;
        if(callback && typeof callback == "function") callback(count);
        e.returnValue = false;
        return false;
      }
  this.startCollecting = function(call) {
    if(enabled) return false;
    enabled = 1;
    callback = call;
    window.addEventListener("keydown",collectKeyboard,false);
    window.addEventListener("mousedown",collectMouse,false);
    window.addEventListener("contextmenu",preventDefault,false);
  }
  this.stopCollecting = function() {
    if(enabled != 1) return false;
    enabled = 0;
    window.removeEventListener("keydown",collectKeyboard,false);
    window.removeEventListener("mousedown",collectMouse,false);
    window.removeEventListener("contextmenu",preventDefault,false);
  }
  this.startListening = function(cod,call) {
    if(enabled || !cod) return false;
    enabled = 2;
    callback = call;
    codes = cod;
    var mouse = false, keyboard = false;
    for(var count = 0; (count < cod.length && (!mouse || !keyboard)); count++) {
      if(typeof cod[count]["keyCode"] == "string") mouse = true;
      else if(typeof cod[count]["keyCode"] == "object") {
        for(var count2 = 0; (count2 < cod[count2]["keyCode"].length && (!mouse || !keyboard)); count2++) {
          if(typeof cod[count]["keyCode"][count2] == "string") mouse = true;
          else keyboard = true;
        }
      }
      else keyboard = true;
    }
    if(keyboard) window.addEventListener("keydown",listenToKeyboard,false);
    if(mouse) window.addEventListener("mousedown",listenToMouse,false);
    window.addEventListener("contextmenu",preventDefault,false);
  }
  this.stopListening = function() {
    if(enabled != 2) return false;
    enabled = 0;
    window.removeEventListener("keydown",listenToKeyboard,false);
    window.removeEventListener("mousedown",listenToMouse,false);
    window.removeEventListener("contextmenu",preventDefault,false);
  }


Snažil jsem se minimalizovat zvýhodnění různých velikostí okna, zcela odstranit jej pravděpodobně není ani možné. Prosím neposuzujte hru podle toho jak vypadá v MSIE, musel jsem transparency efekty dočasně v MSIE vyřadit, protože se hra stávala nehratelnou. Vykreslování posunu textů s body jsem pak musel v MSIE stáhnout na 10FPS, aby nedocházelo ke zpomalení.

Máte-li jakékoliv nápady vzhledem k logice výpočtů, elementů, které by bylo možné do hry dodat, chcete mi zaslat 30x30 px obrázky s grafikou těla, hlavy hada, jídla a bonus jídla, nebo chcete-li se pochlubit nejvyšším skóre (které jste nezískali pomocí
javascript:while(!field.isFull()) {spawn(bonusFood);}
), jsem jedno ucho.
Enyeus
Profil
Jediné co mi chybí je volba rychlosti. Jinak (v chrome) je extrémně rychlý, ale reaguje na tlačítka hned.
Witiko
Profil
Volbu rychlosti není možné "rychle" dodat, protože MSIE defaultně blokuje prompt(). Proto nejdřív dodělám script, pak tomu přidělám plnohodnotné menu.

EDIT: Ok, dočasně přidávám volbu rychlosti přes prompt() hackem pro všechny prohlížeče kromě IE, ten bude mít defaultně 5.
EDIT2: Ozkoušeno, v Chrome, Firefoxu i Opeře je had rychlý tak jak má, IE jede mírně pomaleji.

Další nápady na připojení:
1, Grafika hada a jídla, budu muset namalovat nebo sehnat 30x30 pixelů obrázky. Ale musí to být něco pěkného, jinak tam radši nechám buňky tabulky s background-color. Něco takovéhleho:

2, Povzbudivé texty za vysoké skóre: 9000 bodů - It's over nine thousand! atd.
3, Připojení SoundManager2 knihovny a ozvučení hry
4, Náhodné eventy při kterých se objeví jiný had, sní našemu hadovi jídlo a opět zmizí. Pohrávám si s nápadem více druhů nepřátel s různým cílem. Toto by byl samozřejmě herní mód, který by bylo třeba zapnout. Chci ve hře nechat i mód klasického hada jak ho všichni známe.
Witiko
Profil
Dodáno superJídlo. Má 10% šanci na spawn namísto bonusJídla a na rozdíl od něj nedává 3x více bodů, ale 10x více bodů.
Dodány Achievementy. Dodal jsem 48 oceňujících textů za bodové umístění počínaje fixním bodovým ohodnocením konče vypočítaným maximálním dostažitelným počtem bodů na danou velikost pole.

  "Good!",
  "Sweet!",	
  "Great!",
  "Impressive!",
  "Over 9000!",
  "Amazing!",
  "Extreme!",
  "Fantastic!",
  "Splendid!",
  "This is madness!",
  "Compelling!",
  "Impossible!",
  "Unstoppable!",
  "Worm-up!",
  "Superb!",
  "Inexterminable!",
  "Brütal!",
  "Virtuous!",
  "Snakeous frenzy!",
  "You did not!",
  "Hornmad!",
  "Ravenous!",
  "Snaketacular!",
  "Victual-minded!",
  "Resilient!",
  "Like a hurricane!",
  "Snaekonical!",
  "Epic!",
  "Never gonna give it up!",
  "Impossible!",
  "Chucklate rain!",
  "Hand of Henry!",
  "Invincible!",
  "Snakes on a plane!",
  "Wormhole!",
  "Inconceivable!",
  "This worm hungers!",
  "Legendary!",
  "Wormonger!",
  "No Way!",
  "All your snakes are belong to us!",
  "Snaky!",
  "Wait for it!",
  "Let's rock!",
  "Rocket-fingers!",
  "Food-villain!",
  "Wormazing!",
  "Almost there!",
  "Unattainable!"


A achievementy Lovec bonusového jídla! pokud hráč sní 5x za sebou jen bonusové jídlo a Gurmán! pokud hráč sní 10x za sebou pouze superJídlo.
Sirius
Profil
870 :)
Vypadá to pěkně, ale chybí mi možnost si tu hru pozastavit. (To je taky důvod proč jsem naboural...) Myslím, že tady na diskusi ani o ní není žádná zmínka.
joe
Profil
Pěkné, jinak on dneska ještě někdo hraje hada? :-)

Jinak pokud to spouštím v Opeře a zajedu s hadem na spoední políčko a pak zahnu (takže u okraje) had zabírá dva čtverečky.
Witiko
Profil
Sirius: Dodána možnost pauzy tlačítkem Pause a P, možnost zneužití odstraněna, po odpauzování se provede tah ihned, není proto možné pomocí pauzy podvádět.
Joe: Vím o tom, script rozdělí tabulku tak, aby jedno pole mělo 30x30px, přesah řeší prohlížeče různě, hodlám to mírně přepracovat.

U Achievementů došlo k barevnému rozdělení:
Za bodové ohodnocení:
Zelené - nejnižší
Zlaté - od 9000
Červené - za dosažení % maximálního počtu bodů při minimální efektivitě hry
Fialové - za dosažení % maximálního počtu bodů při normální efektivitě hry
Oranžové - za dosažení % maximálního počtu bodů vypočítaného na velikost pole

Ostatní achievementy (Dva v řadě, trojička, lovec bonusového jídla, gurmán ...) mají barvu modrou.
Dneska hodlám k textům přidat u prohlížečů s CSS 3 rotaci.
Witiko
Profil
1, Dodány animace s rotací. Pro ukázku můžete při hře vložit tento text do adresního řádku a spustit jej:
javascript:centerGrowDraw(500,3000,"#410000","#FF0000","#FFFFFF","#000000",true,false,length,length*2.5,"Ukázka");

2, Odstraněny css hacky.
3, Dodány speciální celoobrazovkové animace pomocí experimentálního přepisu obsahu CSS třídy za běhu programu, zatím defaultně vypnuto - na polovině prohlížečů neběhá, na polovině se to děsivě hryže.
4, Update: 0:44 - úplné zbavení se browser hacků, nyní slouží jen k určení defaultního nastavení v ini souboru, ale ta budou uživatelem pozměnitelná.
- Opraven problém popisovaný joe, velikost je nyní fixní
- Opraven časovač při pauze - doteď pauza představovala reset časovače spawnu bonusového jídla
- Přidán ini soubor globálně určující jaké mé grafické fičury budou zapnuté - výrazně to ulehčuje práci.
- Přidány 4 Achievementy - Skutečný jedlík a S větrem o závod za spořádání více jídla v krátkém časovém limitu, Dva v řadě a Trojička za na sebe navazující 2 / 3 políčka s jídlem.
5, Update: 13:08 - dodány celoplošné efekty, dočasně vypnuté u Firefoxu dokud se mi nepodaří vyřešit trhání, které v něm tyto animace způsobují. Přidávám celoplošné efekty i jako signalizaci Achievementů, jen to chvíli potrvá.

Dočasně přesunuto na alternativní hosting.
Witiko
Profil
Zpět na původním hostingu, doplněny celoobrazovkové efekty ke všem achievementům.

Dodána kontrola minimální velikosti pole 15x15.
Přidána podpora MSIE stínů.

Vaše odpověď

Mohlo by se hodit

Odkud se sem odkazuje


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm:

0