This is a documentation for Board Game Arena: play board games online !

Туториал реверси

Извор: Board Game Arena
Пређи на навигацију Пређи на претрагу

Интродуцтион

Усинг тхис туториал, yоу цан буилд а цомплете wоркинг гаме он тхе БГА енвиронмент: Реверси.

Бефоре yоу реад тхис туториал, yоу муст:

  • Реад тхе овералл пресентатионс оф тхе БГА Фрамеwорк (сее хере).
  • Кноw тхе рулес оф Реверси.
  • Кноw тхе лангуагес усед он БГА: ПХП, СQЛ, ХТМЛ, ЦСС, Јавасцрипт.

Цреате yоур фирст гаме

Wитх тхе инитиал скелетон оф цоде провидед инитиаллy, yоу цан алреадy старт а гаме фром тхе БГА Студио. Фор ноw, wе аре гоинг то wорк wитх 1 плаyер онлy. Мост оф тхе тиме тхис ис симплер то процеед wитх онлy оне плаyер дуринг тхе еарлy пхасе оф девелопмент оф yоур гаме, ас ит'с еасy анд фаст то старт/стоп гамес.

Тхус, yоу цан старт а "Реверси" гаме, анд арриве он а воид, емптy гаме. Yеах.

Лет ит лоок лике Реверси

Ит'с алwаyс а гоод идеа то старт wитх а литтле бит оф грапхиц wорк. Wхy? Бецаусе тхис хелпс то фигуре оут хоw тхе финал гаме wилл бе, анд иссуес тхат маy аппеар латер.

Бе царефул десигнинг тхе лаyоут оф yоур гаме: yоу муст алwаyс кееп ин минд тхат плаyерс wитх а 1024пx сцреен wидтх муст бе абле то плаy. Усуаллy, ит меанс тхат тхе wидтх оф тхе плаy ареа цан бе 750пx (ин тхе wорст цасе).

Фор Реверси, ит'с уселесс то хаве а 750x750пx боард - муцх тоо биг, со wе цхоосе тхис оне wхицх фит перфецтлy (536x528):

Боард.јпг

Ноте тхат wе аре усинг а јпг филе. Јпг аре лигхтер тхан пнг, со фастер то лоад. Латер wе аре гоинг то усе ПНГс фор дисцс фор транспаренцy пурпосе.

Ноw, лет'с маке ит аппеарс он оур гаме:

  • уплоад боард.јпг ин yоур "имг/" дирецторy.
  • едит "реверси_реверси.тпл" то адд а 'див' фор yоур боард:
<div id="board">
</div>
  • едит yоур реверси.цсс филе то трансформ ит инто а висибле боард:
#board {
   width: 536px;
   height: 528px;
   background-image: url('img/board.jpg');
}

Рефресх yоур паге. Хере'с yоур боард:

Реверси1.јпг

Маке тхе сqуарес аппеарс

Ноw, wхат wе неед ис то цреате соме инвисибле ХТМЛ елементс wхере сqуарес аре. Тхесе елементс wилл бе усед ас поситион референцес фор wхите анд блацк дисцс. Обвиоуслy, wе неед 64 сqуарес. То авоид wритинг 64 'див' елементс он оур темплате, wе аре гоинг то усе тхе "блоцк" феатуре.

Лет'с модифy оур темплате лике тхис:

 <div id="board">
    <!-- BEGIN square -->
        <div id="square_{X}_{Y}" class="square" style="left: {LEFT}px; top: {TOP}px;"></div>
    <!-- END square -->
 </div>

Ас yоу цан сее, wе цреатед а "сqуаре" блоцк, wитх 4 вариабле елементс: X, Y, ЛЕФТ анд ТОП. Обвиоуслy, wе аре гоинг то усе тхис блоцк 64 тимес дуринг паге лоад.

Лет'с до ит ин оур "реверси.виеw.пхп" филе:

        $this->page->begin_block( "reversi_reversi", "square" );
        
        $hor_scale = 64.8;
        $ver_scale = 64.4;
        for( $x=1; $x<=8; $x++ )
        {
            for( $y=1; $y<=8; $y++ )
            {
                $this->page->insert_block( "square", array(
                    'X' => $x,
                    'Y' => $y,
                    'LEFT' => round( ($x-1)*$hor_scale+10 ),
                    'TOP' => round( ($y-1)*$ver_scale+7 )
                ) );
            }        
        }

Ноте: ас yоу цан сее, сqуарес ин оур "боард.јпг" филес доес нот хаве ан еxацт wидтх/хеигхт ин пиxел, анд тхат'с тхе реасон wе аре усинг флоатинг поинт нумберс хере.

Ноw, то финисх оур wорк анд цхецк иф еверyтхинг wоркс фине, wе аре гоинг то стyле оур сqуаре а литтле бит ин оур ЦСС стyлесхеет:

#board {
    width: 536px;
    height: 528px;
    background-image: url('img/board.jpg');
    position: relative;
}

.square {
    width: 62px;
    height: 62px;
    position: absolute;
    background-color: red;
}

Еxпланатионс:

  • Wитх "поситион: релативе" он боард, wе енсуре сqуаре елементс аре поситионед релативелy то боард.
  • Фор тхе тест, wе усе а ред бацкгроунд цолор фор тхе сqуаре. Тхис ис а усефул тип то фигуре оут иф еверyтхинг ис фине wитх инвисибле елементс.

Лет'с рефресх анд цхецк оур оур (беаутифул) сqуарес:

Реверси2.јпг

Тхе дисцс

Ноw, оур боард ис реадy то рецеиве соме дисц токенс!

Ат фирст, wе интродуце а неw 'див' елемент ас а цхилд оф "боард" то хост алл тхесе токенс (ин оур темплате):

    <!-- END square -->
    
    <div id="tokens">
    </div>
</div>

Тхен, лет'с интродуце а неw пиеце оф арт wитх тхе дисцс. Wе неед соме транспаренцy хере со wе аре усинг а пнг филе:

Токенс.пнг

Импортант: wе аре усинг ОНЕ филе фор ботх дисцс. Ит'с реаллy импортант тхат yоу усе а минимум нумбер оф грапхиц филес фор yоур гаме wитх тхис "ЦСС сприте" тецхниqуе, бецаусе ит макес тхе гаме лоадинг фастер анд море релиабле. Реад море абоут ЦСС спритес.

Ноw, лет'с сепарате тхе дисц wитх соме ЦСС стуфф:

.token {
    width: 56px;
    height: 56px;
    position: absolute;
    background-image: url('img/tokens.png');
}
.tokencolor_ffffff { background-position: 0px 0px;   }
.tokencolor_000000 { background-position: -56px 0px;   }

Wитх тхис ЦСС цоде, wе апплy тхе цлассес "токен" анд "токенцолор_фффффф" то а див елемент анд wе'ве гот а wхите токен. Yеах.

Ноте тхе "поситион: абсолуте" wхицх аллоwс ус то поситион токенс он тхе боард анд маке тхем "слиде" то тхеир поситионс.

Ноw, лет'с маке а фирст токен аппеар он оур боард. Дисц токенс аре нот висибле ат тхе бегиннинг оф тхе гаме: тхеy аппеар дyнамицаллy дуринг тхе гаме. Фор тхис реасон, wе аре гоинг то маке тхем аппеар фром оур Јавасцрипт цоде, wитх а БГА Фрамеwорк тецхниqуе цаллед "ЈС темплате".

Ин оур темплате филе (реверси_реверси.тпл), лет'с цреате тхе пиеце оф ХТМЛ неедед то дисплаy оур токен:

<script type="text/javascript">

// Templates

var jstpl_token='<div class="token tokencolor_${color}" id="token_${x_y}"></div>';

</script>

Ноте: wе алреадy цреатед тхе "темплатес" сецтион фор yоу ин тхе гаме скелетон.

Ас yоу цан сее, wе дефинед а ЈС темплате намед "јстпл_токен" wитх а пиеце оф ХТМЛ анд тwо вариаблес: тхе цолор оф тхе токен анд итс x/y цоординатес. Ноте тхат тхе сyнтаx оф тхе аргумент ис дифферент фор темплате блоцк вариаблес (брацкетс) анд ЈС темплате вариаблес (доллар анд брацкетс).

Ноw, лет'с цреате а метход ин оур Јавасцрипт цоде тхат wилл маке а токен аппеар он тхе боард, усинг тхис темплате:

        addTokenOnBoard: function( x, y, player )
        {
            dojo.place( this.format_block( 'jstpl_token', {
                x_y: x+'_'+y,
                color: this.gamedatas.players[ player ].color
            } ) , 'tokens' );
            
            this.placeOnObject( 'token_'+x+'_'+y, 'overall_player_board_'+player );
            this.slideToObject( 'token_'+x+'_'+y, 'square_'+x+'_'+y ).play();
        },

Ат фирст, wитх "дојо.плаце" анд "тхис.формат_блоцк" метходс, wе цреате а ХТМЛ пиеце оф цоде анд инсерт ит ас а неw цхилд оф "токенс" див елемент.

Тхен, wитх БГА "тхис.плацеОнОбјецт" метход, wе плаце тхис елемент овер тхе панел оф соме плаyер. Иммедиателy афтер, усинг БГА "тхис.слидетоОбјецт" метход, wе маке тхе дисц слиде то тхе "сqуаре" елемент, итс финал дестинатион.

Ноте: дон'т форгет то цалл тхе "плаy()", отхерwисе тхе токен ремаинс ат итс оригинал лоцатион.

Ноте: ноте тхат дуринг алл тхе процесс, тхе парент оф тхе неw дисц ХТМЛ елемент wилл ремаин "токенс". плацеОнОбјецт анд слидеТоОбјецт метходс аре онлy мовинг тхе поситион оф елементс он сцреен, анд тхеy аре нот модифyинг тхе ХТМЛ трее.

Бефоре wе цан схоw а токен, wе неед то сет тхе плаyер цолорс ин тхе сетупНеwГаме фунцтион ин реверси.гаме.пхп:

        $default_colors = array( "ffffff", "000000" );

Ноw, то тест иф еверyтхинг wоркс фине, јуст цалл "тхис.аддТокенОнБоард( 2, 2, <yоур_плаyер_ид> )" ин yоур "сетуп" Јавасцрипт метход, анд релоад тхе паге. А токен схоулд аппеар анд слиде иммедиателy то итс поситион, лике тхис:

Реверси3.јпг

Тхе датабасе

Wе дид мост оф тхе цлиент-сиде программинг, со лет'с хаве а лоок он тхе отхер сиде ноw.

То десигн тхе датабасе модел оф оур гаме, тхе бест тхинг то до ис то фоллоw тхе "Го то гаме датабасе" линк ат тхе боттом оф оур гаме, то аццесс тхе датабасе дирецтлy wитх а ПхпМyАдмин инстанце.

Тхен, yоу цан цреате тхе таблес yоу неед фор yоур табле (до нот ремове еxистинг таблес!), анд репорт еверy СQЛ цомманд усед ин yоур "дбмодел.сqл" филе.

Реверси4.јпг

Тхе датабасе модел оф Реверси ис верy симпле: јуст оне табле wитх тхе сqуарес оф тхе боард. Ин оур дбмодел.сqл, wе хаве тхис:

CREATE TABLE IF NOT EXISTS `board` (
  `board_x` smallint(5) unsigned NOT NULL,
  `board_y` smallint(5) unsigned NOT NULL,
  `board_player` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`board_x`,`board_y`)
) ENGINE=InnoDB;

Ноw, а неw датабасе wитх а "боард" табле wилл бе цреатед еацх тиме wе старт а Реверси гаме. Тхис ис wхy афтер модифyинг оур дбмодел.сqл ит'с а гоод тиме то стоп & старт агаин оур гаме.

Сетуп тхе инитиал гаме поситион

Тхе "сетупНеwГаме" метход оф оур реверси.гаме.пхп ис цаллед дуринг инитиал сетуп: тхис ис тхе плаце то инитиализе оур дата анд то плаце тхе инитиал токенс он тхе боард (инитиаллy, тхере аре 4 токенс он тхе боард).

Лет'с до тхис:

        // Init the board
        $sql = "INSERT INTO board (board_x,board_y,board_player) VALUES ";
        $sql_values = array();
        list( $blackplayer_id, $whiteplayer_id ) = array_keys( $players );
        for( $x=1; $x<=8; $x++ )
        {
            for( $y=1; $y<=8; $y++ )
            {
                $token_value = "NULL";
                if( ($x==4 && $y==4) || ($x==5 && $y==5) )  // Initial positions of white player
                    $token_value = "'$whiteplayer_id'";
                else if( ($x==4 && $y==5) || ($x==5 && $y==4) )  // Initial positions of black player
                    $token_value = "'$blackplayer_id'";
                    
                $sql_values[] = "('$x','$y',$token_value)";
            }
        }
        $sql .= implode( $sql_values, ',' );
        self::DbQuery( $sql );
        
        
        // Active first player
        self::activeNextPlayer();  

Ас yоу цан сее, wе цреате оне табле ентрy фор еацх сqуаре, wитх а "НУЛЛ" валуе wхицх меанс "емптy сqуаре". Оф цоурсе, фор 4 специфиц сqуарес, wе плаце ан инитиал токен.

Ат тхе енд, wе цалл ацтивеНеxтПлаyер то маке тхе фирст плаyер ацтиве ат тхе бегиннинг оф тхе гаме.

Ноw, wе неед то маке тхесе токенс аппеар он тхе цлиент сиде. То ацхиеве тхис, тхе фирст степ ис то ретурн тхе токен поситионс wитх оур "гетАллДатас" ПХП метход (цаллед дуринг еацх паге релоад):

        // Get reversi board token
        $result['board'] = self::getObjectListFromDB( "SELECT board_x x, board_y y, board_player player
                                                       FROM board
                                                       WHERE board_player IS NOT NULL" );

Ас yоу цан сее, wе аре усинг тхе БГА фрамеwорк "гетОбјецтЛистФромДБ" метход тхат форматс тхе ресулт оф тхис СQЛ qуерy ин а ПХП арраy wитх x, y анд плаyер аттрибутес.

Тхе ласт тхинг wе неед то до ис то процесс тхис арраy цлиент сиде, анд плаце а дисц токен он тхе боард фор еацх арраy итем. Оф цоурсе, wе аре доинг тхис ис оур Јавасцрипт "сетуп" метход:

            for( var i in gamedatas.board )
            {
                var square = gamedatas.board[i];
                
                if( square.player !== null )
                {
                    this.addTokenOnBoard( square.x, square.y, square.player );
                }
            }

Ас yоу цан сее, оур "боард" ентрy цреатед ин "гетАллДатас" цан бе усед хере ас "гамедатас.боард" ин оур Јавасцрипт. Wе аре усинг оур превиоуслy девелопед "аддТокенОнБоард" метход.

Релоад... анд хере wе аре:

Реверси5.јпг

Ит стартс то смелл Реверси хере...

Тхе гаме стате мацхине

Ноw, лет'с стоп оур гаме агаин, бецаусе wе аре гоинг то старт тхе цоре гаме логиц.

Yоу алреадy реад тхе "Фоцус он БГА гаме стате мацхине", со yоу кноw тхат тхис ис тхе хеарт оф yоур гаме логиц. Фор реверси, ит'с верy симпле алтхоугх. Хере'с а диаграм оф оур гаме стате мацхине:

Реверси6.јпг

Анд хере'с оур "статес.инц.пхп", аццординг то тхис диаграм:

$machinestates = array(

    1 => array(
        "name" => "gameSetup",
        "description" => clienttranslate("Game setup"),
        "type" => "manager",
        "action" => "stGameSetup",
        "transitions" => array( "" => 10 )
    ),
    
    10 => array(
        "name" => "playerTurn",
		"description" => clienttranslate('${actplayer} must play a disc'),
		"descriptionmyturn" => clienttranslate('${you} must play a disc'),
        "type" => "activeplayer",
        "args" => "argPlayerTurn",
        "possibleactions" => array( 'playDisc' ),
        "transitions" => array( "playDisc" => 11, "zombiePass" => 11 )
    ),
    
    11 => array(
        "name" => "nextPlayer",
        "type" => "game",
        "action" => "stNextPlayer",
        "updateGameProgression" => true,        
        "transitions" => array( "nextTurn" => 10, "cantPlay" => 11, "endGame" => 99 )
    ),
   
    99 => array(
        "name" => "gameEnd",
        "description" => clienttranslate("End of game"),
        "type" => "manager",
        "action" => "stGameEnd",
        "args" => "argGameEnd"
    )

);

Ноw, лет'с цреате ин реверси.гаме.пхп тхе метходс тхат аре децларед ин тхис гаме статес десцриптион филе:

  • аргПлаyерТурн
  • стНеxтПлаyер

... анд старт а неw Реверси гаме.

Ас yоу цан сее он тхе сцреен цаптуре белоw, тхе БГА фрамеwорк макес тхе гаме јумп то оур фирст гаме стате "плаyерТурн" ригхт афтер тхе инитиал сетуп. Тхат'с wхy тхе статус бар цонтаинс тхе десцриптион оф плаyерТурн стате ("XXXX муст плаy а дисц"):

Реверси7.јпг

Тхе рулес

Ноw, wхат wе wоулд лике то до ис то индицате то тхе цуррент плаyер wхере схе ис аллоwед то плаy. Тхе идеа ис то буилд а "гетПоссиблеМовес" ПХП метход тхат ретурн а лист оф цоординатес wхере схе ис аллоwед то плаy. Тхис метход wилл бе усед ин партицулар:

  • Ас wе јуст саид, то хелп тхе плаyер то сее wхере хе цан плаy.
  • Wхен тхе плаyер плаy, то цхецк иф хе хас тхе ригхт то плаy хере.

Тхис ис пуре ПХП программинг хере, анд тхере'с но специал тхингс фром тхе БГА фрамеwорк тхат цан бе усед. Тхис ис wхy wе wон'т го инто детаилс хере. Тхе овералл идеа ис:

  • Цреате а "гетТурнедОверДисцс(x,y)" метход тхат ретурн цоординатес оф дисцс тхат wоулд бе турнед овер иф а токен wоулд бе плаyед ат x,y.
  • Лооп тхроугх алл фрее сqуарес оф тхе боард, цалл тхе "гетТурнедОверДисцс" метход он еацх оф тхем. Иф ат леаст 1 токен ис турнед овер, тхис ис а валид мове.

Оне импортант тхинг то кееп ин минд ис тхе фоллоwинг: макинг а датабасе qуерy ис слоw, со плеасе дон'т лоад тхе ентире гаме боард wитх а СQЛ qуерy мултипле тиме. Ин оур имплементатион, wе лоад тхе ентире боард онце ат тхе бегиннинг оф "гетПоссиблеМовес", анд тхен пасс тхе боард ас ан аргумент то алл метходс.

Иф yоу wант то лоок инто детаилс, плеасе лоок ат тхе "утилитy метход" сецтионс оф реверси.гаме.пхп.

Дисплаy аллоwед мовес

Ноw, wхат wе wант то до ис хигхлигхт тхе сqуарес wхере тхе плаyер цан плаце а дисц.

То до тхис, wе аре усинг тхе "аргПлаyерТурн" метход. Тхис метход ис цаллед еацх тиме wе ентер инто "плаyерТурн" гаме стате, анд итс ресулт ис трансферед аутоматицаллy то тхе цлиент-сиде:


    function argPlayerTurn()
    {
        return array(
            'possibleMoves' => self::getPossibleMoves( self::getActivePlayerId() )
        );
    }

Wе аре оф цоурсе усинг тхе "гетПоссиблеМовес" метход wе јуст девелопед.

Ноw, лет'с го то тхе цлиент сиде то усе тхе дата ретурнед бy тхе метход абове. Wе аре усинг тхе "онЕнтерингСтате" Јавасцрипт метход тхат ис цаллед еацх тиме wе ентер инто а неw гаме стате:

        onEnteringState: function( stateName, args )
        {
           console.log( 'Entering state: '+stateName );
            
            switch( stateName )
            {
            case 'playerTurn':
                this.updatePossibleMoves( args.args.possibleMoves );
                break;
            }
        },

Со, wхен wе аре ентеринг инто "плаyерТурн" гаме стате, wе аре цаллинг оур "упдатеПоссиблеМовес" метход. Тхис метход лоокс лике тхис:

        updatePossibleMoves: function( possibleMoves )
        {
            // Remove current possible moves
            dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );

            for( var x in possibleMoves )
            {
                for( var y in possibleMoves[ x ] )
                {
                    // x,y is a possible move
                    dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );
                }            
            }
                        
            this.addTooltipToClass( 'possibleMove', '', _('Place a disc here') );
        },

Тхе идеа хере ис тхат wе'ве цреатед а ЦСС цласс ("поссиблеМове") тхат цан бе апплиед то а "сqуаре" елемент то хигхлигхт ит:

.possibleMove {
    background-color: white;
    opacity: 0.2;
    filter:alpha(opacity=20); /* For IE8 and earlier */  
    cursor: pointer;  
}

Ат фирст, wе ремове алл "поссиблеМове" цлассес цуррентлy апплиед wитх тхе верy усефул цомбинатион оф "дојо.qуерy" анд "ремовеЦласс" метход.

Тхен wе лооп тхроугх алл поссибле мовес оур ПХП "упдатеПоссиблеМовес" фунцтион цреатед фор ус, анд адд тхе "поссиблеМове" цласс то еацх цорреспондинг сqуаре.

Финаллy, wе усе тхе БГА фрамеwорк "аддТоолтипТоЦласс" метход то ассоциате а тоолтип то алл тхосе хигхлигхтед сqуарес со тхат плаyерс цан ундерстанд тхеир меанинг.

Анд хере wе аре:

Реверси8.јпг.јпг

Лет'с плаy

Фром ноw, ит'с беттер то рестарт а гаме wитх 2 плаyерс, бецаусе wе аре гоинг то имплемент а цомплете Реверси турн. Тхе суммарy оф wхат wе аре гоинг то до ис:

  • Wхен wе цлицк он а "поссиблеМове" сqуаре, сенд тхе мове то тхе сервер.
  • Сервер сиде, цхецк тхе мове ис цоррецт, апплy Реверси рулес анд јумп то неxт плаyер.
  • Цлиент сиде, цханге тхе дисц поситион то рефлецт тхе мове.

Тхус, wхат wе до фирст ис ассоциате еацх цлицк он а сqуаре то оне оф оур метход. Wе аре доинг тхис ин оур Јавасцрипт "сетуп" метход:

            dojo.query( '.square' ).connect( 'onclick', this, 'onPlayDisc' );

Ноте тхе усе оф тхе "дојо.qуерy" метход то гет алл ХТМЛ елементс wитх "сqуаре" цласс ин јуст оне фунцтион цалл. Ноw, оур "онПлаyДисц" метход ис цаллед еацх тиме сомеоне цлицкс он а сqуаре.

Хере'с оур "онПлаyДисц" метход белоw:

        onPlayDisc: function( evt )
        {
            // Stop this event propagation
            dojo.stopEvent( evt );

            // Get the cliqued square x and y
            // Note: square id format is "square_X_Y"
            var coords = evt.currentTarget.id.split('_');
            var x = coords[1];
            var y = coords[2];

            if( ! dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )
            {
                // This is not a possible move => the click does nothing
                return ;
            }
            
            if( this.checkAction( 'playDisc' ) )    // Check that this action is possible at this moment
            {            
                this.ajaxcall( "/reversi/reversi/playDisc.html", {
                    x:x,
                    y:y
                }, this, function( result ) {} );
            }            
        },

Wхат wе до хере ис:

  • Wе стоп тхе пропагатион оф тхе Јавасцрипт "онцлицк" евент. Отхерwисе, ит цан леад то рандом бехавиор со ит'с алwаyс а гоод идеа.
  • Wе гет тхе x/y цоординатес оф тхе сqуаре бy усинг "евт.цуррентТаргет.ид".
  • Wе цхецк тхат цлицкед сqуаре хас тхе "поссиблеМове" цласс, отхерwисе wе кноw фор суре тхат wе цан'т плаy тхере.
  • Wе цхецк тхат "плаyДисц" ацтион ис поссибле, аццординг то цуррент гаме стате (сее "поссиблеацтионс" ентрy ин оур "плаyерТурн" гаме стате дефинед абове). Тхис цхецк ис импортант то авоид иссуес иф а плаyер доубле цлицкс он а сqуаре.
  • Финаллy, wе маке а цалл то тхе сервер усинг БГА "ајаxцалл" метход wитх аргумент x анд y.

Ноw, wе хаве то манаге тхис "плаyДисц" ацтион он сервер сиде. Ат фирст, wе интродуце а "плаyДисц" ентрy поинт ин оур "реверси.ацтион.пхп":

    public function playDisc()
    {
        self::setAjaxMode();     
        $x = self::getArg( "x", AT_posint, true );
        $y = self::getArg( "y", AT_posint, true );
        $result = $this->game->playDisc( $x, $y );
        self::ajaxResponse( );
    }

Ас yоу цан сее, wе гет тхе 2 аргументс x анд y фром тхе јавасцрипт цалл, анд цалл а цорреспондинг "плаyДисц" метход ин оур гаме логиц.

Ноw, лет'с хаве а лоок оф тхис плаyДисц метход:

    function playDisc( $x, $y )
    {
        // Check that this player is active and that this action is possible at this moment
        self::checkAction( 'playDisc' );  

... ат фирст, wе цхецк тхат тхис ацтион ис поссибле аццординг то цуррент гаме стате (сее "поссибле ацтион"). Wе алреадy дид ит он цлиент сиде, бут ит'с импортант то до ит он сервер сиде тоо (отхерwисе ит wоулд бе поссибле то цхеат).

        // Now, check if this is a possible move
        $board = self::getBoard();
        $player_id = self::getActivePlayerId();
        $turnedOverDiscs = self::getTurnedOverDiscs( $x, $y, $player_id, $board );
        
        if( count( $turnedOverDiscs ) > 0 )
        {
            // This move is possible!

...ноw, wе аре усинг тхе "гетТурнедОверДисцс" метход агаин то цхецк тхат тхис мове ис поссибле.

            // Let's place a disc at x,y and return all "$returned" discs to the active player
            
            $sql = "UPDATE board SET board_player='$player_id'
                    WHERE ( board_x, board_y) IN ( ";
            
            foreach( $turnedOverDiscs as $turnedOver )
            {
                $sql .= "('".$turnedOver['x']."','".$turnedOver['y']."'),";
            }
            $sql .= "('$x','$y') ) ";
                       
            self::DbQuery( $sql );

... wе упдате тхе датабасе то цханге тхе цолор оф алл турнед овер дисц + тхе дисц wе јуст плацед.

            // Update scores according to the number of disc on board
            $sql = "UPDATE player
                    SET player_score = (
                    SELECT COUNT( board_x ) FROM board WHERE board_player=player_id
                    )";
            self::DbQuery( $sql );
            
            // Statistics
            self::incStat( count( $turnedOverDiscs ), "turnedOver", $player_id );
            if( ($x==1 && $y==1) || ($x==8 && $y==1) || ($x==1 && $y==8) || ($x==8 && $y==8) )
                self::incStat( 1, 'discPlayedOnCorner', $player_id );
            else if( $x==1 || $x==8 || $y==1 || $y==8 )
                self::incStat( 1, 'discPlayedOnBorder', $player_id );
            else if( $x>=3 && $x<=6 && $y>=3 && $y<=6 )
                self::incStat( 1, 'discPlayedOnCenter', $player_id );

... ноw, wе упдате ботх плаyер сцоре бy цоунтинг алл дисц, анд wе манаге гаме статистицс.

            // Notify
            self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ), array(
                'player_id' => $player_id,
                'player_name' => self::getActivePlayerName(),
                'returned_nbr' => count( $turnedOverDiscs ),
                'x' => $x,
                'y' => $y
            ) );

            self::notifyAllPlayers( "turnOverDiscs", '', array(
                'player_id' => $player_id,
                'turnedOver' => $turnedOverDiscs
            ) );
            
            $newScores = self::getCollectionFromDb( "SELECT player_id, player_score FROM player", true );
            self::notifyAllPlayers( "newScores", "", array(
                "scores" => $newScores
            ) );

... тхен wе нотифy абоут алл тхесе цхангес. Wе аре усинг фор тхат 3 нотифицатионс ('плаyДисц', 'турнОверДисцс' анд 'неwСцорес' тхат wе аре гоинг то имплемент он цлиент сиде латер). Ноте тхат тхе десцриптион оф тхе 'плаyДисц' нотифицатион wилл бе логгед ин тхе гаме лог.

            // Then, go to the next state
            $this->gamestate->nextState( 'playDisc' );
        }
        else
            throw new feException( "Impossible move" );
    }

... финаллy, wе јумп то тхе неxт гаме стате иф еверyтхинг гоес фине ('плаyДисц' ис алсо тхе наме оф а транситион ин тхе 'плаyерТурн' гаме стате десцриптион абове). То маке тхе статистицс wорк, wе хаве то инитиализе тхем ин стате.инц.пхп:

    // Statistics existing for each player
    "player" => array(
    
        "discPlayedOnCorner" => array(   "id"=> 10,
                                "name" => totranslate("Discs played on a corner"), 
                                "type" => "int" ),
                                
        "discPlayedOnBorder" => array(   "id"=> 11,
                                "name" => totranslate("Discs played on a border"), 
                                "type" => "int" ),

        "discPlayedOnCenter" => array(   "id"=> 12,
                                "name" => totranslate("Discs played on board center part"), 
                                "type" => "int" ),

        "turnedOver" => array(   "id"=> 13,
                                "name" => totranslate("Number of discs turned over"), 
                                "type" => "int" )    
    )

А ласт тхинг то до он тхе сервер сиде ис то ацтивате тхе неxт плаyер wхен wе ентер тхе "неxтПлаyер" гаме стате:

    function stNextPlayer()
    {
        // Activate next player
        $player_id = self::activeNextPlayer();
        
        self::giveExtraTime( $player_id );
        $this->gamestate->nextState( 'nextTurn' );

    }

Ноw, wхен wе плаy а дисц, тхе рулес аре цхецкед анд тхе дисц аппеарс ин тхе датабасе.

Реверси9.јпг

Оф цоурсе, ас wе дон'т манаге нотифицатионс он цлиент сиде, wе неед то пресс Ф5 афтер еацх мове то сее тхе цхангес он тхе боард.

Маке тхе мове аппеар аутоматицаллy

Ноw, wхат wе хаве то до ис процесс тхе нотифицатионс сент бy тхе сервер анд маке тхе мове аппеар он тхе интерфаце.

Ин оур "сетупНотифицатионс" метход, wе регистер 2 метходс фор тхе 2 нотифицатионс wе цреатед ат тхе превиоус степ ('плаyДисц' анд 'турнОверДисцс'):

            dojo.subscribe( 'playDisc', this, "notif_playDisc" );
            this.notifqueue.setSynchronous( 'playDisc', 500 );
            dojo.subscribe( 'turnOverDiscs', this, "notif_turnOverDiscs" );
            this.notifqueue.setSynchronous( 'turnOverDiscs', 1500 );

Ас yоу цан сее, wе ассоциате оур 2 нотифицатионс wитх 2 метходс wитх тхе "нотиф_" префиx. Ат тхе саме тиме, wе дефине тхесе нотифицатионс ас "сyнцхроноус", wитх а дуратион ин миллисецонд (500 фор тхе фирст оне, анд 1500 фор тхе сецонд оне). Ит теллс тхе усер интерфаце то wаит соме тиме афтер еxецутинг тхе нотифицатион, то лет тхе аниматион енд бефоре стартинг тхе неxт нотифицатион. Ин оур специфиц цасе, тхе аниматион wилл бе тхе фоллоwинг:

  • Маке а дисц слиде фром тхе плаyер панел то итс плаце он тхе боард
  • (wаит 500мс)
  • Маке алл турнед овер дисцс блинк (анд оф цоурсе турнед тхем овер)
  • (wаит 1500мс)
  • Лет тхе неxт плаyер плаy.

Лет'с хаве а лоок ноw он тхе "плаyДисц" нотифицатион хандлер метход:

        notif_playDisc: function( notif )
        {
            // Remove current possible moves (makes the board more clear)
            dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );        
        
            this.addTokenOnBoard( notif.args.x, notif.args.y, notif.args.player_id );
        },

Но сурприсе хере, wе ре-усед соме еxистинг стуфф то:

  • Ремове тхе хигхлигхтед сqуарес.
  • Адд а неw дисц он боард, цоминг фром плаyер панел.

Ноw, хере'с тхе метход тхат хандлес тхе турнОверДисцс нотифицатион:

        notif_turnOverDiscs: function( notif )
        {
            // Get the color of the player who is returning the discs
            var targetColor = this.gamedatas.players[ notif.args.player_id ].color;

            // Make these discs blink and set them to the specified color
            for( var i in notif.args.turnedOver )
            {
                var token = notif.args.turnedOver[ i ];
                
                // Make the token blink 2 times
                var anim = dojo.fx.chain( [
                    dojo.fadeOut( { node: 'token_'+token.x+'_'+token.y } ),
                    dojo.fadeIn( { node: 'token_'+token.x+'_'+token.y } ),
                    dojo.fadeOut( { 
                                    node: 'token_'+token.x+'_'+token.y,
                                    onEnd: function( node ) {

                                        // Remove any color class
                                        dojo.removeClass( node, [ 'tokencolor_000000', 'tokencolor_ffffff' ] );
                                        // ... and add the good one
                                        dojo.addClass( node, 'tokencolor_'+targetColor );
                                                             
                                    } 
                                  } ),
                    dojo.fadeIn( { node: 'token_'+token.x+'_'+token.y  } )
                                 
                ] ); // end of dojo.fx.chain

                // ... and launch the animation
                anim.play();                
            }
        },

Тхе лист оф тхе дисцс то бе турнед овер хас беен маде аваилабле бy оур сервер сиде цоде ин "нотиф.аргс.турнедОвер" (сее превиоус параграпх). Wе лооп тхроугх алл тхесе дисцс, анд цреате а цомплеx аниматион усинг дојо.Аниматион фор еацх оф тхем. Тхе цомплете доцументатион он дојо аниматионс цан бе фоунд хере.

Ин феw wордс: wе цреате а цхаин оф 4 аниматионс то маке тхе дисц фаде оут, фаде ин, фаде оут агаин, анд фаде ин агаин. Ат тхе енд оф тхе сецонд фаде оут, wе цханге тхе цолор оф тхе дисц. Финаллy, wе лаунцх тхе аниматион wитх "плаy()".