JavaScript v praxi: Sokoban (5. přednáška)
Sokoban ...
Cíl Přesunout krabice tak, aby každá krabice byla na žlutém poli. Pravidla 1. Panáček se může pohybovat nahoru, dolů, doprava, doleva 2. Panáček může posunout krabicí ve směru pohybu, pokud je v tomto směru za krabicí volné místo.
Sokoban: Návrh
Budeme potřebovat: Objekt reprezentující mapu Objekt reprezentující stav hry (hru) Funkci, která vykreslí mapu/aktuální stav Funkci, která bude upravovat stav v závislosti na uživatelském vstupu
Mapa
1 2 3 4 5 6 7 8 9 10
var level = { 0:[ ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’0 ’] , 1:[ ’W ’ , ’0 ’ , ’0 ’ , ’X ’ , ’X ’ , ’X ’ , ’X ’ , ’0 ’ , ’0 ’ , ’W ’ , ’0 ’] , 2:[ ’W ’ , ’0 ’ , ’B ’ , ’X ’ , ’B , X ’ , ’X ’ , ’B , X ’ , ’0 ’ , ’0 ’ , ’W ’ , ’W ’ ], 3:[ ’W ’ , ’W ’ , ’B ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’0 ’ , ’0 ’ , ’W ’] , 4:[ ’W ’ , ’0 ’ , ’0 ’ , ’0 ’ , ’0 ’ , ’B ’ , ’0 ’ , ’0 ’ , ’0 ’ , ’0 ’ , ’W ’] , 5:[ ’W ’ , ’0 ’ , ’0 ’ , ’B ’ , ’0 ’ , ’B ’ , ’0 ’ , ’B ’ , ’0 ’ , ’S ’ , ’W ’] , 6:[ ’W ’ , ’0 ’ , ’0 ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’W ’] , 7:[ ’W ’ , ’W ’ , ’W ’ , ’W ’ , ’0 ’ , ’0 ’ , ’0 ’ , ’0 ’ , ’0 ’ , ’0 ’ , ’O ’] , }
W — stěna, ’0’ — nic, ’X’ — žluté políčko, ’B’ — krabice, ’S’ — panáček
Stav Hry
Budeme chtít: uměl načíst mapu uměl vykreslit mapu 1 2 3 4 5 6 7 8
function game () { this . loadMap = function ( mapa ) { ... }; this . drawPlan = function ( htmlElement ) { ... }; this . moveLeft = function () { ... }; this . moveRight = function () { ... }; this . moveUp = function () { ... }; this . moveDown = function () { ... }; }
Kreslení — Idea Zvolíme si nějaký div element (herní plán) do kterého budeme kreslit Každé prvek na mapě (krabice, stěna, . . .) bude odpovídat nějakému div elementu, který bude potomkem herního plánu. Umístění na plán zajistíme pomocí position:absolute; a css-vlastností top, left. O grafiku se bude starat css.
Intermezzo: Scripting the DOM Jak se v JavaScriptu pracuje s html prvky stránky (objekt document) getElementById( id ) getElementsByClassName( css_class ) createElement( tagname ) Html prvky (které získáme pomocí předcházejích funkcí) jsou také objekty, mají různé vlastnosti (a metody): appendChild( element ) removeChild( element ) children innerHTML id, className, style onClick, ...
Stav Hry: herní prvky stav si budeme udržovat jako seznam jednotlivých prvků (divů) na herním plánu u každého prvku si budeme pamatovat jeho pozici a jeho typ (W,0,X,B,S) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
function prvek ( typ ) = { this . typ = typ ; this . div = document . createElement ( " div " ) ; this . div . style . width =10+ ’ px ’; this . div . style . height =10+ ’ px ’; this . setXY = function (x , y ) { this . x = x ; this . y = y ; this . style . top = this . y *10+ ’ px ’; this . style . left = this . x *10+ ’ px ’; } this . placeOnBoard = function ( element ) { element . appendChild ( this . div ) ; }; this . removeFromBoard = function ( element ) { element . removeChild ( this . div ) ;}; }
Stav Hry: načtení mapy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
this . loadMap = function ( map ) { this . herniPrvky = []; this . height = 0; this . length = len ( map [0]) ; this . panacek = null ; for ( y in map ) { this . height ++; for ( x in map [ y ] ) { prvky = map [ y ][ x ]. split ( ’ , ’) ; for ( i in prvky ) { var p = new prvek ( prvky [ i ] ) ; p . setXY ( parseInt ( x ) , parseInt ( y ) ) ; this . herniPrvky . push ( p ) ; if ( prvky [ i ] == ’S ’) { this . panacek = p ; } } } } }
Stav Hry: kreslení
1 2 3 4 5
this . drawPlan = function ( herniplan_div ) { for ( i in this . herniPrvky ) { this . herniPrvky [ i ]. placeOnBoard ( herniplan_div ) ; } }
Stav Hry: moveLeft 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
this . moveLeft = function () { // Nesmime vylezt z mapy if ( this . panacek . x == 0) return false ; var sousedi = this . prvkyAtXY ( this . panacek .x -1 , this . panacek . y ) ; // Nesmime projit zdi if ( this . contains ( sousedi , ’W ’) ) return false ; // Krabici muzeme posunout pouze , pokud je misto if ( this . contains ( sousedi , ’B ’) ) { // Ani krabici nesmime vysunout z mapy if ( this . panacek . x == 1 ) return false ; ns = this . prvkyAtXY ( this . panacek .x -2 , this . panacek . y ) if ( this . contains ( ns , ’W ’) || this . contains ( ns , ’B ’) ) return false ; krabice = this . getType ( sousedi , ’B ’ ) ; krabice . setXY ( this . panacek .x -2 , this . panacek . y ) ; } // Posuneme panacka this . panacek . setXY ( this . panacek .x -1 , this . panacek . y ) ; }
Sta Hry: co chybí?
funkce contains(list, typ) která vrátí true, pokud seznam list herních prvků obsahuje prvek typu typ funkce getType(list,typ) která vrátí ze seznamu list herní prvek typu typ funkce, která si pamatuje, kolik tahů už bylo provedeno (skóre) funkce, která si udržuje přehled o tom, kolik krabic ještě chybí přesunout
Interakce s uživatelem
vlastnosti jednotlivých prvků (onclick, onblur, onfocus, ...) vlastnosti objektu document (onkeypress, onkeydown, onkeyup) metody objektu window (setTimeOut( "javascript code", msec ), setInterval, clearInterval)
Stav Hry: ošetření uživatelského vstupu 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
this . keyuphandler = function () { var me = this ; return function ( e ) { if ( me . keysEnabled ) return false ; e = window . event || e ; switch ( e . keyCode ) { case 37: me . moveLeft () ; break ; case 38: me . moveUp () ; break ; case 39: me . moveRight () ; break ; case 40: me . moveDown () ; break ; default : return true ; } return false ; } }
Dáme to dohromady
1 2 3 4 5 6 7 8 9 10
function startGame () { var level = { ... }; var g = new game () ; g . loadMap ( level ) ; herniPlanDiv = document . getElementById ( ’ HerniPlan ’) ; g . drawPlan ( herniPlanDiv ) ; window . onkeyup = g . keyuphandler () ; } window . onload = startGame ;