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

Tutorial gomoku — разлика између измена

Извор: Board Game Arena
Пређи на навигацију Пређи на претрагу
Ред 182: Ред 182:
== Manage states and events ==
== Manage states and events ==


Define your game states in states.inc.php
Define your game states in states.inc.php. For gomoku we will use 3 states. One to play, one to check the end game condition, one to give his turn to the other player if the game is not over.


Add onclick events on intersections in .js, calling an action with appropriate parameters
The first state requires an action from the player, so its type is 'activeplayer'.


Add action in .action.php, retrieving parameters and calling the appropriate game action
The two others are automatic actions for the game, so their type is 'game'.


Add game action in .game.php to update the database, then notify the client using a method ‘stonePlayed’
We will update the progression while checking for the end of the game, so for this state we set the 'updateGameProgression' flag to true.
 
<pre>
    2 => array(
        "name" => "playerTurn",
        "description" => clienttranslate('${actplayer} must play a stone'),
        "descriptionmyturn" => clienttranslate('${you} must play a stone'),
        "type" => "activeplayer",
        "possibleactions" => array( "playStone" ),
        "transitions" => array( "stonePlayed" => 3, "zombiePass" => 3 )
    ),
 
    3 => array(
        "name" => "checkEndOfGame",
        "description" => '',
        "type" => "game",
        "action" => "stCheckEndOfGame",
        "updateGameProgression" => true,
        "transitions" => array( "gameEnded" => 99, "notEndedYet" => 4 )
    ),
 
    4 => array(
        "name" => "nextPlayer",
        "description" => '',
        "type" => "game",
        "action" => "stNextPlayer",
        "transitions" => array( "" => 2 )
    ),
</pre>
 
Add onclick events on intersections in gomoku.js->setup()
 
            // Add events on active elements (the third parameter is the method that will be called when the event defined by the second parameter happens - this method must be declared beforehand)
            this.addEventToClass( "gmk_intersection", "onclick", "onClickIntersection");
 
Declare the corresponding gomoku.js->onClickIntersection() function, which calls an action function on the server with appropriate parameters
 
        onClickIntersection: function( evt )
        {
            console.log( '$$$$ Event : onClickIntersection' );
            dojo.stopEvent( evt );
 
            if( ! this.checkAction( 'playStone' ) )
            { return; }
 
            var node = evt.currentTarget.id;
            var coord_x = node.split('_')[1];
            var coord_y = node.split('_')[2];
           
            console.log( '$$$$ Selected intersection : (' + coord_x + ', ' + coord_y + ')' );
           
            if ( this.isCurrentPlayerActive() ) {
                this.ajaxcall( "/gomoku/gomoku/playStone.html", { lock: true, coord_x: coord_x, coord_y: coord_y }, this, function( result ) {}, function( is_error ) {} );
            }
        },
 
Add this action function in gomoku.action.php, retrieving parameters and calling the appropriate game action
 
    public function playStone()
    {
        self::setAjaxMode();   
 
        // Retrieve arguments
        // Note: these arguments correspond to what has been sent through the javascript "ajaxcall" method
        $coord_x = self::getArg( "coord_x", AT_posint, true );
        $coord_y = self::getArg( "coord_y", AT_posint, true );
 
        // Then, call the appropriate method in your game logic, like "playCard" or "myAction"
        $this->game->playStone( $coord_x, $coord_y );
 
        self::ajaxResponse( );
    }
 
Add game action in gomoku.game.php to update the database, send a notification to the client providing the event notified (‘stonePlayed’) and its parameters, and proceed to the next state.
 
    function playStone( $coord_x, $coord_y )
    {
        // Check that this is player's turn and that it is a "possible action" at this game state (see states.inc.php)
        self::checkAction( 'playStone' );
       
        $player_id = self::getActivePlayerId();
       
        // Check that this intersection is free
        $sql = "SELECT
                    id, coord_x, coord_y, stone_color
                FROM
                    intersection
                WHERE
                    coord_x = $coord_x
                    AND coord_y = $coord_y
                    AND stone_color is null
              ";
        $intersection = self::getObjectFromDb( $sql );
 
        if ($intersection == null) {
            throw new BgaUserException( self::_("There is already a stone on this intersection, you can't play there") );
        }
 
        // Get player color
        $sql = "SELECT
                    player_id, player_color
                FROM
                    player
                WHERE
                    player_id = $player_id
              ";
        $player = self::getNonEmptyObjectFromDb( $sql );
        $color = ($player['player_color'] == 'ffffff' ? 'white' : 'black');
 
        // Update the intersection with a stone of the appropriate color
        $intersection_id = $intersection['id'];
        $sql = "UPDATE
                    intersection
                SET
                    stone_color = '$color'
                WHERE
                    id = $intersection_id
              ";
        self::DbQuery($sql);
       
        // Notify all players
        self::notifyAllPlayers( "stonePlayed", clienttranslate( '${player_name} dropped a stone ${coordinates}' ), array(
            'player_id' => $player_id,
            'player_name' => self::getActivePlayerName(),
            'coordinates' => $this->getFormattedCoordinates($coord_x, $coord_y),
            'coord_x' => $coord_x,
            'coord_y' => $coord_y,
            'color' => $color
        ) );
 
        // Go to next game state
        $this->gamestate->nextState( "stonePlayed" );
    }
 
Catch the notification in gomoku.js->setupNotifications() and link it to a javascript function to execute
 
        setupNotifications: function()
        {
            console.log( 'notifications subscriptions setup' );
       
            dojo.subscribe( 'stonePlayed', this, "notif_stonePlayed" );
        }


Implement this method in javascript to update the intersection to show the stone, and register it inside the setNotifications function.
Implement this method in javascript to update the intersection to show the stone, and register it inside the setNotifications function.


The basic game turn is implemented: you can drop some stones!
        notif_stonePlayed: function( notif )
        {
    console.log( '**** Notification : stonePlayed' );
            console.log( notif );
 
            // Create a stone
            dojo.place( this.format_block('jstpl_stone', {
                    stone_type:'stone_' + notif.args.color,
                    x:notif.args.coord_x,
                    y:notif.args.coord_y
                } ), $( 'intersection_' + notif.args.coord_x + '_' + notif.args.coord_y ) );
 
            // Place it on the player panel
            this.placeOnObject( $( 'stone_' + notif.args.coord_x + '_' + notif.args.coord_y ), $( 'player_board_' + notif.args.player_id ) );
 
            // Animate a slide from the player panel to the intersection
            dojo.style( 'stone_' + notif.args.coord_x + '_' + notif.args.coord_y, 'zIndex', 1000 );
            var slide = this.slideToObject( $( 'stone_' + notif.args.coord_x + '_' + notif.args.coord_y ), $( 'intersection_' + notif.args.coord_x + '_' + notif.args.coord_y ), 1000 );
            dojo.connect( slide, 'onEnd', this, dojo.hitch( this, function() {
        dojo.style( 'stone_' + notif.args.coord_x + '_' + notif.args.coord_y, 'zIndex', 'auto' );
      }));
            slide.play();
         
            // This intersection is taken, it shouldn't appear as clickable anymore
            dojo.removeClass( 'intersection_' + notif.args.coord_x + '_' + notif.args.coord_y, 'clickable' );
        },
 
For this function, you need to declare a stone javascript template in your .tpl file
 
var jstpl_stone='<div class="gmk_stone ${stone_type}" id="stone_${x}_${y}"></div>';
 
The basic game turn is implemented: you can now drop some stones!


[[File:Gomoku tuto4.png]]
[[File:Gomoku tuto4.png]]

Верзија на датум 5. децембар 2012. у 23:36

Тхис туториал wилл гуиде yоу тхроугх тхе басицс оф цреатинг а симпле гаме он БГА Студио, тхроугх тхе еxампле оф Гомоку (алсо кноwн ас Гобанг ор Фиве ин а Роw).

Yоу wилл старт фром оур 'емтпy гаме' темплате

Хере ис хоw yоур гамес лоокс бy дефаулт wхен ит хас јуст беен цреатед :

Гомоку туто1.пнг

Сетуп тхе боард

Гатхер усефул имагес фор тхе гаме анд едит тхем ас неедед. Уплоад тхем ин тхе 'имг' фолдер оф yоур СФТП аццесс.

Едит .тпл то адд соме дивс фор тхе боард ин тхе ХТМЛ.

<div id="gmk_game_area">
	<div id="gmk_background">
		<div id="gmk_goban">
		</div>
	</div>	
</div>

Едит .цсс то сет тхе див сизес анд поситионс анд схоw тхе имаге оф тхе боард ас бацкгроунд.

#gmk_game_area {
	text-align: center;
	position: relative;
}

#gmk_background {
	width: 620px;
	height: 620px;	
	position: relative;
	display: inline-block;
}

#gmk_goban {	
	background-image: url( '../../img/gomoku/goban.jpg');
	width: 620px;
	height: 620px;
	position: absolute;	
}

Гомоку туто2.пнг

Сетуп тхе бацкбоне оф yоур гаме

Едит дбмодел.сqл то цреате а табле фор интерсецтионс. Wе неед цоординатес фор еацх интерсецтион анд а фиелд то сторе тхе цолор оф тхе стоне он тхис интерсецтион (иф анy).

CREATE TABLE IF NOT EXISTS `intersection` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `coord_x` tinyint(2) unsigned NOT NULL,
   `coord_y` tinyint(2) unsigned NOT NULL,
   `stone_color` varchar(8) NULL,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Едит гомоку.гаме.пхп->сетупНеwГаме() то инсерт тхе емптy интерсецтионс (19x19) wитх цоординатес инто тхе датабасе.

        // Insert (empty) intersections into database
        $sql = "INSERT INTO intersection (coord_x, coord_y) VALUES ";
        $values = array();
        for ($x = 0; $x < 19; $x++) {
            for ($y = 0; $y < 19; $y++) {
        	
            	$values[] = "($x, $y)";   	
            }
        }
        $sql .= implode( $values, ',' );
        self::DbQuery( $sql );

Едит гомоку.гаме.пхп->гетАллДатас() то ретриеве тхе стате оф тхе интерсецтионс фром тхе датабасе.

        // Intersections
        $sql = "SELECT id, coord_x, coord_y, stone_color FROM intersection ";
        $result['intersections'] = self::getCollectionFromDb( $sql );

Едит .тпл то цреате а темплате фор интерсецтионс (јстпл_интерсецтион).

var jstpl_intersection='<div class="gmk_intersection ${stone_type}" id="intersection_${x}_${y}"></div>';

Дефине тхе стyлес фор тхе интерсецтион дивс

.gmk_intersection {
    width: 30px;
    height: 30px;
    position: relative;
}

Едит гомоку.јс->сетуп() то сетуп тхе интерсецтионс лаyер тхат wилл бе усед то гет цлицк евентс анд то дисплаy тхе стонес. Тхе дата yоу ретурнед ин $ресулт['интерсецтионс'] ин гомоку.гаме.пхп->гетАллДатас() ис ноw аваилабле ин yоур гомоку.јс->сетуп() ин гамедатас.интерсецтионс.

            // Setup intersections
            for( var id in gamedatas.intersections )
            {
                var intersection = gamedatas.intersections[id];

                dojo.place( this.format_block('jstpl_intersection', {
                    x:intersection.coord_x,
                    y:intersection.coord_y,
                    stone_type:(intersection.stone_color == null ? "no_stone" : 'stone_' + intersection.stone_color)
                } ), $ ( 'gmk_background' ) );

                var x_pix = this.getXPixelCoordinates(intersection.coord_x);
                var y_pix = this.getYPixelCoordinates(intersection.coord_y);
                
                this.slideToObjectPos( $('intersection_'+intersection.coord_x+'_'+intersection.coord_y), $('gmk_background'), x_pix, y_pix, 10 ).play();

                if (intersection.stone_color != null) {
                    // This intersection is taken, it shouldn't appear as clickable anymore
                    dojo.removeClass( 'intersection_' + intersection.coord_x + '_' + intersection.coord_y, 'clickable' );
                }
            } 

Усе соме темпорарy цсс бордер-цолор ор бацкгроунд-цолор анд опацитy то сее тхе дивс анд маке суре yоу хаве тхем поситионед ригхт.

.gmk_intersection {
    width: 30px;
    height: 30px;
    position: relative;
    background-color: blue;
    opacity: 0.3;
}

Yоу цан децларе соме цонстантс ин материал.инц.пхп анд пасс тхем то yоур гомоку.јс фор еасy репоситионинг (модифy цонстант, рефресх). Тхис ис еспециаллy усефул иф тхе саме цонстантс хаве то бе усед он тхе сервер анд он тхе цлиент.

  • Децларе yоур цонстантс ин материал.инц.пхп (тхис wилл бе аутоматицаллy инцлудед ин yоур гомоку.гаме.пхп)
$this->gameConstants = array(
		"INTERSECTION_WIDTH" => 30,
		"INTERSECTION_HEIGHT" => 30,		
		"INTERSECTION_X_SPACER" => 2.8, // Float
		"INTERSECTION_Y_SPACER" => 2.8, // Float
		"X_ORIGIN" => 0,
		"Y_ORIGIN" => 0,
);
  • Ин гомоку.гаме.пхп->гетАллДатас(), адд тхе цонстантс то тхе ресулт арраy
       // Constants
       $result['constants'] = $this->gameConstants;
  • Ин гомоку.јс цонструцтор, дефине а цласс вариабле фор цонстантс
       // Game constants
     	this.gameConstants = null;
  • Тхен усе ит ин yоур гетXПиxелЦоординатес анд гетYПиxелЦоординатес фунцтионс
       getXPixelCoordinates: function( intersection_x )
       {
       	return this.gameConstants['X_ORIGIN'] + intersection_x * (this.gameConstants['INTERSECTION_WIDTH'] + this.gameConstants['INTERSECTION_X_SPACER']); 
       },
       
       getYPixelCoordinates: function( intersection_y )
       {
       	return this.gameConstants['Y_ORIGIN'] + intersection_y * (this.gameConstants['INTERSECTION_HEIGHT'] + this.gameConstants['INTERSECTION_Y_SPACER']); 
       },

Хере ис wхат yоу схоулд гет:

Гомоку туто3.пнг

Манаге статес анд евентс

Дефине yоур гаме статес ин статес.инц.пхп. Фор гомоку wе wилл усе 3 статес. Оне то плаy, оне то цхецк тхе енд гаме цондитион, оне то гиве хис турн то тхе отхер плаyер иф тхе гаме ис нот овер.

Тхе фирст стате реqуирес ан ацтион фром тхе плаyер, со итс тyпе ис 'ацтивеплаyер'.

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

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

    2 => array(
        "name" => "playerTurn",
        "description" => clienttranslate('${actplayer} must play a stone'),
        "descriptionmyturn" => clienttranslate('${you} must play a stone'),
        "type" => "activeplayer",
        "possibleactions" => array( "playStone" ),
        "transitions" => array( "stonePlayed" => 3, "zombiePass" => 3 )
    ),

    3 => array(
        "name" => "checkEndOfGame",
        "description" => '',
        "type" => "game",
        "action" => "stCheckEndOfGame",
        "updateGameProgression" => true,
        "transitions" => array( "gameEnded" => 99, "notEndedYet" => 4 )
    ),

    4 => array(
        "name" => "nextPlayer",
        "description" => '',
        "type" => "game",
        "action" => "stNextPlayer",
        "transitions" => array( "" => 2 )
    ),

Адд онцлицк евентс он интерсецтионс ин гомоку.јс->сетуп()

           // Add events on active elements (the third parameter is the method that will be called when the event defined by the second parameter happens - this method must be declared beforehand)
           this.addEventToClass( "gmk_intersection", "onclick", "onClickIntersection");

Децларе тхе цорреспондинг гомоку.јс->онЦлицкИнтерсецтион() фунцтион, wхицх цаллс ан ацтион фунцтион он тхе сервер wитх аппроприате параметерс

       onClickIntersection: function( evt )
       {
           console.log( '$$$$ Event : onClickIntersection' );
           dojo.stopEvent( evt );
           if( ! this.checkAction( 'playStone' ) )
           { return; }
           var node = evt.currentTarget.id;
           var coord_x = node.split('_')[1];
           var coord_y = node.split('_')[2];
           
           console.log( '$$$$ Selected intersection : (' + coord_x + ', ' + coord_y + ')' );
           
           if ( this.isCurrentPlayerActive() ) {
               this.ajaxcall( "/gomoku/gomoku/playStone.html", { lock: true, coord_x: coord_x, coord_y: coord_y }, this, function( result ) {}, function( is_error ) {} );
           }
       },

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

   public function playStone()
   {
       self::setAjaxMode();     
       // Retrieve arguments
       // Note: these arguments correspond to what has been sent through the javascript "ajaxcall" method
       $coord_x = self::getArg( "coord_x", AT_posint, true );
       $coord_y = self::getArg( "coord_y", AT_posint, true );
       // Then, call the appropriate method in your game logic, like "playCard" or "myAction"
       $this->game->playStone( $coord_x, $coord_y );
       self::ajaxResponse( );
   }

Адд гаме ацтион ин гомоку.гаме.пхп то упдате тхе датабасе, сенд а нотифицатион то тхе цлиент провидинг тхе евент нотифиед (‘стонеПлаyед’) анд итс параметерс, анд процеед то тхе неxт стате.

   function playStone( $coord_x, $coord_y )
   {
       // Check that this is player's turn and that it is a "possible action" at this game state (see states.inc.php)
       self::checkAction( 'playStone' ); 
       
       $player_id = self::getActivePlayerId();
       
       // Check that this intersection is free
       $sql = "SELECT
                   id, coord_x, coord_y, stone_color
               FROM
                   intersection 
               WHERE 
                   coord_x = $coord_x 
                   AND coord_y = $coord_y
                   AND stone_color is null
              ";
       $intersection = self::getObjectFromDb( $sql );
       if ($intersection == null) {
           throw new BgaUserException( self::_("There is already a stone on this intersection, you can't play there") );
       }
       // Get player color
       $sql = "SELECT
                   player_id, player_color
               FROM
                   player 
               WHERE 
                   player_id = $player_id
              ";
       $player = self::getNonEmptyObjectFromDb( $sql );
       $color = ($player['player_color'] == 'ffffff' ? 'white' : 'black');
       // Update the intersection with a stone of the appropriate color
       $intersection_id = $intersection['id'];
       $sql = "UPDATE
                   intersection
               SET
                   stone_color = '$color'
               WHERE 
                   id = $intersection_id
              ";
       self::DbQuery($sql);
       
       // Notify all players
       self::notifyAllPlayers( "stonePlayed", clienttranslate( '${player_name} dropped a stone ${coordinates}' ), array(
           'player_id' => $player_id,
           'player_name' => self::getActivePlayerName(),
           'coordinates' => $this->getFormattedCoordinates($coord_x, $coord_y),
           'coord_x' => $coord_x,
           'coord_y' => $coord_y,
           'color' => $color
       ) );
       // Go to next game state
       $this->gamestate->nextState( "stonePlayed" );
   }

Цатцх тхе нотифицатион ин гомоку.јс->сетупНотифицатионс() анд линк ит то а јавасцрипт фунцтион то еxецуте

       setupNotifications: function()
       {
           console.log( 'notifications subscriptions setup' );
       
           dojo.subscribe( 'stonePlayed', this, "notif_stonePlayed" );
       }

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

       notif_stonePlayed: function( notif )
       {

цонсоле.лог( '**** Нотифицатион : стонеПлаyед' );

           console.log( notif );
           // Create a stone
           dojo.place( this.format_block('jstpl_stone', {
                   stone_type:'stone_' + notif.args.color,
                   x:notif.args.coord_x,
                   y:notif.args.coord_y
               } ), $( 'intersection_' + notif.args.coord_x + '_' + notif.args.coord_y ) );
           // Place it on the player panel
           this.placeOnObject( $( 'stone_' + notif.args.coord_x + '_' + notif.args.coord_y ), $( 'player_board_' + notif.args.player_id ) );
           // Animate a slide from the player panel to the intersection
           dojo.style( 'stone_' + notif.args.coord_x + '_' + notif.args.coord_y, 'zIndex', 1000 );
           var slide = this.slideToObject( $( 'stone_' + notif.args.coord_x + '_' + notif.args.coord_y ), $( 'intersection_' + notif.args.coord_x + '_' + notif.args.coord_y ), 1000 );
           dojo.connect( slide, 'onEnd', this, dojo.hitch( this, function() {
       			dojo.style( 'stone_' + notif.args.coord_x + '_' + notif.args.coord_y, 'zIndex', 'auto' );
      		}));
           slide.play();
          
           // This intersection is taken, it shouldn't appear as clickable anymore
           dojo.removeClass( 'intersection_' + notif.args.coord_x + '_' + notif.args.coord_y, 'clickable' );
       },

Фор тхис фунцтион, yоу неед то децларе а стоне јавасцрипт темплате ин yоур .тпл филе

вар јстпл_стоне='

';

Тхе басиц гаме турн ис имплементед: yоу цан ноw дроп соме стонес!

Гомоку туто4.пнг

Цлеануп yоур стyлес

Ремове темпорарy цсс висуалисатион хелперс : лоокс гоод!

Гомоку туто5.пнг

Имплемент рулес анд енд оф гаме цондитионс

Имплемент специфиц рулес фор тхе гаме (иф анy)

 Nothing special for Gomoku

Имплемент руле фор цомпутинг гаме прогрессион ин .гаме.пхп->гетГамеПрогрессион()

Имплемент енд оф гаме детецтион анд упдате тхе сцоре аццординг то wхо ис тхе wиннер ин .гаме.пхп->стЦхецкЕндОфГаме()

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

Тест еверyтхинг тхороугхлy... yоу аре доне!

Гомоку туто6.пнг