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

Khác biệt giữa bản sửa đổi của “Main game logic: yourgamename.game.php”

Từ Board Game Arena
Bước tới điều hướng Bước tới tìm kiếm
Không có tóm lược sửa đổi
 
(Không hiển thị 43 phiên bản của 11 người dùng ở giữa)
Dòng 1: Dòng 1:


This file is the main file for your game logic. Here you initialize the game, persist data, implement the rules and notify changes to the client interface.
This is the main file for your game logic. Here you initialize the game, persist data, implement the rules and notify the client interface of changes.


== File Structure ==
== File Structure ==


The details on how the file is structured is described directly with comments on the code skeleton provided to you.
The details of how the file is structured are described directly with comments in the code skeleton provided to you.
   
   
Basically, here's this structure:
Here is the basic structure:
 
* EmptyGame (constructor): where you define global variables.
* EmptyGame (constructor): where you define global variables.
* setupNewGame: initial setup of the game.
* setupNewGame: initial setup of the game.
Dòng 13: Dòng 14:
* Utility functions: your utility functions.
* Utility functions: your utility functions.
* Player actions: the entry points for players actions.  
* Player actions: the entry points for players actions.  
* Game state arguments: methods to return additional data on specific game states.
* Game state arguments: methods to return additional data on specific game states ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#args more info here]).
* Game state actions: the logic to run when entering a new game state.
* Game state actions: the logic to run when entering a new game state ([http://en.doc.boardgamearena.com/Your_game_state_machine:_states.inc.php#action more info here]).
* zombieTurn: what to do it's the turn of a zombie player.
* zombieTurn: what to do it's the turn of a zombie player.


== Accessing player informations ==
== Accessing player information ==


; getPlayersNumber()
; getPlayersNumber()
Dòng 34: Dòng 35:
; loadPlayersBasicInfos()
; loadPlayersBasicInfos()
: Get an associative array with generic data about players (ie: not game specific data).
: Get an associative array with generic data about players (ie: not game specific data).
: The key of the associative array is the player id.
: The key of the associative array is the player id. The returned table is cached, so ok to call multiple times without performance concerns.
: The content of each value is:
: The content of each value is:
: * player_name
: * player_name - the name of the player
: * player_color (ex: ff0000)
: * player_color (ex: ff0000) - the color code of the player
: * player_no - the position of the player at the start of the game in natural table order, i.e. 1,2,3


; getCurrentPlayerId()
; getCurrentPlayerId()
Dòng 53: Dòng 55:


; isCurrentPlayerZombie()
; isCurrentPlayerZombie()
: Check the "current_player" zombie status. If true, player leave the game.
: Check the "current_player" zombie status. If true, player is zombie, i.e. left or was kicked out of the game.


== Accessing database ==
== Accessing the database ==


The main game logic should be the only point from where you should access to the game database. You access your database using SQL queries with the methods below.
The main game logic should be the only point from which you should access the game database. You access your database using SQL queries with the methods below.


'''IMPORTANT'''
'''IMPORTANT'''


BGA is using [http://dev.mysql.com/doc/refman/5.0/en/sql-syntax-transactions.html database transactions]. It means that your database changes WON'T BE APPLIED to the database until your request ends normally. Using transaction is in fact very useful for you: at any time, if your game logic detects that something is wrong (ex: unallowed move), you just have to throw an exception and all the changes already performed on the game situation will be removed.
BGA uses [http://dev.mysql.com/doc/refman/5.0/en/sql-syntax-transactions.html database transactions]. This means that your database changes WON'T BE APPLIED to the database until your request ends normally. Using transactions is in fact very useful for you; at any time, if your game logic detects that something is wrong (example: a disallowed move), you just have to throw an exception and all changes to the game situation will be removed.


; DbQuery( $sql )
; DbQuery( $sql )
: This is the generic method to access the database.
: This is the generic method to access the database.
: It can execute any type of SELECT/UPDATE/DELETE/REPLACE query on the database.
: It can execute any type of SELECT/UPDATE/DELETE/REPLACE query on the database.
: You should use it for UPDATE/DELETE/REPLACE query. For SELECT queries, the specialized methods above are much better.
: You should use it for UPDATE/DELETE/REPLACE queries. For SELECT queries, the specialized methods below are much better.


; getUniqueValueFromDB( $sql )
; getUniqueValueFromDB( $sql )
Dòng 106: Dòng 108:
: Idem than previous one, but raise an exception if the collection is empty
: Idem than previous one, but raise an exception if the collection is empty


; function getObjectFromDB( $sql )
; getObjectFromDB( $sql )
: Returns one row for the sql SELECT query as an associative array or null if there is no result
: Returns one row for the sql SELECT query as an associative array or null if there is no result
: Raise an exception if the query return more than one row
: Raise an exception if the query return more than one row
Dòng 141: Dòng 143:
Example 2:
Example 2:
<pre>
<pre>
self::getObjectListFromDB( "SELECT player_id id, player_name name FROM player", true );
self::getObjectListFromDB( "SELECT player_name name FROM player", true );


Result:
Result:
Dòng 168: Dòng 170:
: (unsafe = can be modified by a player).
: (unsafe = can be modified by a player).
: This method makes sure that no SQL injection will be done through the string used.
: This method makes sure that no SQL injection will be done through the string used.
: Note: if you using standard types in ajax actions, like AT_alphanum it is sanitized before arrival,
: this is only needed if you manage to get unchecked string, like in the games where user has to enter text as a response.


<pre>
<pre>
self::getObjectFromDB( "SELECT player_id id, player_name name, player_color color FROM player WHERE player_id='1234'" );


Result:
array(
'id' => 1234,
'name' => 'myuser1',
'color' => 'ff0000'
)


</pre>
</pre>
; function getNonEmptyObjectFromDB( $sql )
: Idem, but raise an exception if the query doesn't return exactly one row


Note: see Editing [[Game database model: dbmodel.sql]] to know how to define your database model.
Note: see Editing [[Game database model: dbmodel.sql]] to know how to define your database model.
Dòng 198: Dòng 190:
This method is located at the beginning of your game logic. This is the place you defines the globals used in your game logic, by assigning them IDs.
This method is located at the beginning of your game logic. This is the place you defines the globals used in your game logic, by assigning them IDs.


You can define up to 89 globals, with IDs from 10 to 89. You must NOT use globals outside this range as globals are used by other components of the framework.
You can define up to 79 globals, with IDs from 10 to 89. You must NOT use globals outside this range as globals are used by other components of the framework.


<pre>
<pre>
Dòng 227: Dòng 219:
== Game states and active players ==
== Game states and active players ==


; checkAction( $actionName, $bThrowException=true )
: Check if action is valid regarding current game state (exception if fails)
: The action is valid if it is listed as a "possibleactions" in the current game state (see game state description).
: This method MUST be called in the first place in ALL your PHP methods that handle players action, in order to make sure a player can't do an action when the rules disallow it at this moment of the game.
: if "bThrowException" is set to "false", the function return false in case of failure instead of throwing and exception. This is useful when several actions are possible in order to test each of them without throwing exceptions.


; activeNextPlayer()
=== Activate player handling ===
 
; $this->activeNextPlayer()
: Make the next player active in the natural player order.
: Make the next player active in the natural player order.
: Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.
: Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.


; activePrevPlayer()
; $this->activePrevPlayer()
: Make the previous player active (in the natural player order).
: Make the previous player active (in the natural player order).
: Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.
: Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.
Dòng 245: Dòng 234:
: Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.
: Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.


; $this->getActivePlayerId()
: Return the "active_player" id
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multiplayer"
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.


; $this->gamestate->getActivePlayerList()
=== Multiactivate player handling ===
: With this method you can retrieve the list of the active player at any time.
: During a "game" type gamestate, it will return a void array.
: During a "activeplayer" type gamestate, it will return an array with one value (the active player id).
: during a "multipleactiveplayer" type gamestate, it will return an array of the active players id.
: Note: you should only use this method is the latter case.


; $this->gamestate->setAllPlayersMultiactive()
; $this->gamestate->setAllPlayersMultiactive()
: With this method, all playing players are made active.
: All playing players are made active. Update notification is sent to all players (triggers onUpdateActionButtons).
: Usually, you use this method at the beginning (ex: "st" action method) of a multiplayer game state when all players have to do some action.
: Usually, you use this method at the beginning (ex: "st" action method) of a multiplayer game state when all players have to do some action.


; $this->gamestate->setPlayersMultiactive( $players, $next_state )
; $this->gamestate->setAllPlayersNonMultiactive( $next_state )
: Make a specific list of players active during a multiactive gamestate.
: All playing players are made inactive. Transition to next state
: Bare in mind it doesn't deactivate other previously active players.
 
; $this->gamestate->setPlayersMultiactive( $players, $next_state, $bExclusive = false )
: Make a specific list of players active during a multiactive gamestate. Update notification is sent to all players who's state changed.
: "players" is the array of player id that should be made active.
: "players" is the array of player id that should be made active.
: If "exclusive" parameter is not set or false it doesn't deactivate other previously active players. If its set to true, the players who will be multiactive at the end are only these in "$layers" array
: In case "players" is empty, the method trigger the "next_state" transition to go to the next game state.
: In case "players" is empty, the method trigger the "next_state" transition to go to the next game state.
: returns true if state transition happened, false otherwise


; $this->gamestate->setPlayerNonMultiactive( $player_id, $next_state )
; $this->gamestate->setPlayerNonMultiactive( $player_id, $next_state )
Dòng 267: Dòng 260:
: Usually, you call this method during a multiactive game state after a player did his action.
: Usually, you call this method during a multiactive game state after a player did his action.
: If this player was the last active player, the method trigger the "next_state" transition to go to the next game state.
: If this player was the last active player, the method trigger the "next_state" transition to go to the next game state.
: returns true if state transition happened, false otherwise
;  $this->gamestate->updateMultiactiveOrNextState( $next_state_if_none )
: Sends update notification about multiplayer changes. All multiactive set* functions above do that, however if you want to change state manually using db queries for complex calculations, you have to call this yourself after. Do not call this if you calling one of the other setters above.
Example: you have player teams and you want to activate all players in one team
<pre>
        $sql = "UPDATE player SET player_is_multiactive='0'";
        self::DbQuery( $sql );
        $sql = "UPDATE player SET player_is_multiactive='1' WHERE player_id='$player_id' AND player_team='$team_no'";
        self::DbQuery( $sql );
       
        $this->gamestate->updateMultiactiveOrNextState( 'error' );
</pre>
; $this->gamestate->getActivePlayerList()
: With this method you can retrieve the list of the active player at any time.
: During a "game" type gamestate, it will return a void array.
: During a "activeplayer" type gamestate, it will return an array with one value (the active player id).
: During a "multipleactiveplayer" type gamestate, it will return an array of the active players id.
: Note: you should only use this method in the latter case.
=== States functions ===
; $this->gamestate->nextState( $transition )
: Change current state to a new state. Important: parameter $transition is the name of the transition, and NOT the name of the target game state, see [[Your game state machine: states.inc.php]] for more information about states.
; $this->checkAction( $actionName, $bThrowException=true )
: Check if action is valid regarding current game state (exception if fails).
: The action is valid if it is listed as a "possibleactions" in the current game state (see game state description).
: This method MUST be called in the first place in ALL your PHP methods that handle players action, in order to make sure a player can't do an action when the rules disallow it at this moment of the game.
: if "bThrowException" is set to "false", the function return false in case of failure instead of throwing and exception. This is useful when several actions are possible in order to test each of them without throwing exceptions.


; $this->gamestate->checkPossibleAction( $action )
; $this->gamestate->checkPossibleAction( $action )
Dòng 273: Dòng 296:
: This is used specifically in certain game states when you want to authorize some additional actions for players that are not active at the moment.
: This is used specifically in certain game states when you want to authorize some additional actions for players that are not active at the moment.
: Example: in Libertalia game, you want to authorize players to change their mind about card played. They are of course not active at the time they change their mind, so you cannot use "checkAction" and use "checkPossibleAction" instead.
: Example: in Libertalia game, you want to authorize players to change their mind about card played. They are of course not active at the time they change their mind, so you cannot use "checkAction" and use "checkPossibleAction" instead.
; $this->gamestate->state()
: Get an associative array of current game state attributes, see [[Your game state machine: states.inc.php]] for state attributes.
  $state=$this->gamestate->state(); if( $state['name'] == 'myGameState' ) {...}


== Players turn order ==
== Players turn order ==
Dòng 303: Dòng 330:


Get player playing before given player in natural playing order.
Get player playing before given player in natural playing order.
Note: There is no API to modify this order, if you have custom player order you have to maintain it in your database
and have custom function to access it.


== Notify players ==
== Notify players ==
Dòng 311: Dòng 341:


Notifications are sent at the very end of the request, when it ends normally. It means that if you throw an exception for any reason (ex: move not allowed), no notifications will be sent to players.
Notifications are sent at the very end of the request, when it ends normally. It means that if you throw an exception for any reason (ex: move not allowed), no notifications will be sent to players.
Notifications sent between the game start (setupNewGame) and the end of the "action" method of the first active state will never reach their destination.


'''notifyAllPlayers( $notification_type, $notification_log, $notification_args )'''
'''notifyAllPlayers( $notification_type, $notification_log, $notification_args )'''
Dòng 328: Dòng 359:
If you define a real string here, you should use "clienttranslate" method to make sure it can be translate.
If you define a real string here, you should use "clienttranslate" method to make sure it can be translate.


You can use arguments in your notification_log strings, that refers to values defines in the "notification_args" argument (see below).
You can use arguments in your notification_log strings, that refers to values defines in the "notification_args" argument (see below).  
Note: Make sure you only use single quotes ('), otherwise PHP will try to interpolate the variable and will ignore the values in the args array.
 


Note: you CAN use some HTML inside your notification log, and it is working. However:
_ pay attention to keep the log clear.
_ try to not include some HTML tags inside the "clienttranslate" method, otherwise it will make the translators work more difficult. You can use a notification argument instead, and provide your HTML through this argument.


* notification_args:
* notification_args:
Dòng 342: Dòng 372:


<pre>
<pre>
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ), array(
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),
array(
         'player_id' => $player_id,
         'player_id' => $player_id,
         'player_name' => self::getActivePlayerName(),
         'player_name' => self::getActivePlayerName(),
Dòng 353: Dòng 384:
You can see in the example above the use of the "clienttranslate" method, and the use of 2 arguments "player_name" and "returned_nbr" in the notification log.
You can see in the example above the use of the "clienttranslate" method, and the use of 2 arguments "player_name" and "returned_nbr" in the notification log.


Important: NO private date must be sent with this method, as a cheater could see it even it is not used explicitly by the game interface logic. If you want to send private information to a player, please use notifyPlayer below.
Important: NO private data must be sent with this method, as a cheater could see it even it is not used explicitly by the game interface logic. If you want to send private information to a player, please use notifyPlayer below.
 
Note: you CAN use some HTML inside your notification log, however it not recommended for many reasons:
* Its bad architecture, ui elements leak into server now you have to manage ui in many places
* If you decided to change something in ui in future version, old games reply and tutorials may not work, since they use stored notifications
* When you read log preview for old games its unreadable (this is log before you enter the game reply, useful for troubleshooting or game analysis)
* Its more data to transfer and store in db
* Its nightmare for translators, at least don't put HTML tags inside the "clienttranslate" method. You can use a notification argument instead, and provide your HTML through this argument.
 
If you still want to have pretty pictures in the log check this [[BGA_Studio_Cookbook#Inject_images_and_styled_html_in_the_log]].
 
If your notification contains some phrases that build programmatically you may need to use recursive notifications. In this case the argument can be not only the string but
an array itself, which contains 'log' and 'args', i.e.
 
  $this->notifyAllPlayers('playerLog',clienttranslate('Game moves ${token_name_rec}'),
                  ['token_name_rec'=>['log'=>'${token_name} #${token_number}',
                                      'args'=> ['token_name'=>clienttranslate('Boo'), 'token_number'=>$number, 'i18n'=>['token_name'] ]
                                      ]
                  ]);
 
 


'''notifyPlayer( $player_id, $notification_type, $notification_log, $notification_args )'''
'''notifyPlayer( $player_id, $notification_type, $notification_log, $notification_args )'''
Dòng 360: Dòng 411:


This method must be used each time some private information must be transmitted to a player.
This method must be used each time some private information must be transmitted to a player.
Important: the variable for player name must be ${player_name} in order to be highlighted with the player color in the game log
== About random and randomness ==
A large number of board games rely on random, most often based on dice, cards shuffling, picking some item in a bag, and so on. This is very important to ensure a high level of randomness for each of these situations.
Here's are a list of techniques you should use in these situations, from the best to the worst.
=== Dices and bga_rand ===
'''bga_rand( min, max )'''
This is a BGA framework function that provides you a random number between "min" and "max" (included), using the best available random method available on the system.
This is the preferred function you should use, because we are updating it when a better method is introduced.
At now, bga_rand is based on the PHP function "random_int", which ensure a cryptographic level of randomness.
In particular, it is '''mandatory''' to use it for all '''dice throw''' (ie: games using other methods for dice throwing will be rejected by BGA during review).
Note: rand() and mt_rand() are deprecated on BGA and should not be used anymore, as their randomness is not as good as "bga_rand".
=== shuffle and cards shuffling ===
To shuffle items, like a pile of cards, the best way is to use the BGA PHP [[Deck]] component and to use "shuffle" method. This ensure you that the best available shuffling method is used, and that if in the future we improve it your game will be up to date.
At now, the Deck component shuffle method is based on PHP "shuffle" method, which has a quite good randomness (even it is not as good as bga_rand). In consequence, we accept other shuffling methods during reviews, as long as their are based on PHP "shuffle" function (or similar, like "array_rand").
=== Other methods ===
Mysql "RAND()" function has not enough randomness to be a valid method to get a random element on BGA. This function has been used in some existing games and has given acceptable results, but now it should be avoided and you should use other methods instead.


== Game statistics ==
== Game statistics ==
Dòng 369: Dòng 451:
See [[Game statistics: stats.inc.php]] to see how you defines statistics for your game.
See [[Game statistics: stats.inc.php]] to see how you defines statistics for your game.


'''initStat( $table_or_player, $name, $value, $player_id=null )'''
 
Create a statistic entry for the specified statistics with a default value.
'''initStat( $table_or_player, $name, $value, $player_id = null )'''
 
Create a statistic entry with a default value.
This method must be called for each statistics of your game, in your setupNewGame method.
This method must be called for each statistics of your game, in your setupNewGame method.


'table_or_player' must be set to "table" if this is a table statistics, or "player" if this is a player statistics.
'$table_or_player' must be set to "table" if this is a table statistics, or "player" if this is a player statistics.
 
'$name' is the name of your statistics, as it has been defined in your stats.inc.php file.


'name' is the name of your statistics, as it has been defined in your stats.inc.php file.
'$value' is the initial value of the statistics. If this is a player statistics and if the player is not specified by "$player_id" argument, the value is set for ALL players.


'value' is the initial value of the statistics. If this is a player statistics and if the player is not specified by "player_id" argument, the value is set for ALL players.


'''setStat( $value, $name, $player_id = null )'''


'''function setStat( $value, $name, $player_id = null )'''
Set a statistic $name to $value.


Set a statistic value.
If "$player_id" is not specified, setStat consider it is a TABLE statistic.


If "player_id" is not specified, setStat consider it is a TABLE statistic.
If "$player_id" is specified, setStat consider it is a PLAYER statistic.


If "player_id" is specified, setStat consider it is a PLAYER statistic.


'''incStat( $delta, $name, $player_id = null )'''
'''incStat( $delta, $name, $player_id = null )'''


Increment (or decrement) specified statistic value. Same behavior as above.
Increment (or decrement) specified statistic value by $delta value. Same behavior as setStat function.
 
 
'''getStat( $name, $player_id = null )'''
 
Return the value of statistic specified by $name. Useful when creating derivative statistics such as average.


== Translations ==
== Translations ==
Dòng 397: Dòng 487:


== Manage player scores and Tie breaker ==
== Manage player scores and Tie breaker ==
=== Normal scoring ===


At the end of the game, players automatically get a rank depending on their score: the player with the biggest score is #1, the player with the second biggest score is #2, and so on...
At the end of the game, players automatically get a rank depending on their score: the player with the biggest score is #1, the player with the second biggest score is #2, and so on...
Dòng 415: Dòng 507:
Note: don't forget to notify the client side in order the score control can be updated accordingly.
Note: don't forget to notify the client side in order the score control can be updated accordingly.


'''Tie breaker'''
=== Tie breaker ===


Tie breaker is used when two players get the same score at the end of a game.
Tie breaker is used when two players get the same score at the end of a game.
Dòng 423: Dòng 515:
Tie breaker score is displayed only for players who are tied at the end of the game. Most of the time, it is not supposed to be displayed explicitly during the game.
Tie breaker score is displayed only for players who are tied at the end of the game. Most of the time, it is not supposed to be displayed explicitly during the game.


When you are using "player_score_aux" functionality, you must describe the formula to use in your Constructor method like this:
When you are using "player_score_aux" functionality, you must describe the formula to use in your gameinfos.inc.php file like this:


<pre>
<pre>
         $this->tie_breaker_description = self::_("Describe here your tie breaker formula");
         'tie_breaker_description' => totranslate("Describe here your tie breaker formula"),
</pre>
</pre>


This description will be used as a tooltip to explain to players how this auxiliary score has been calculated.
This description will be used as a tooltip to explain to players how this auxiliary score has been calculated.
=== Co-operative game ===
To make everyone lose in full-coop game:
Add the following in gameinfos.inc.php :
'is_coop' => 1, // full cooperative
And score zero to everyone.
=== Semi-coop ===
If the game is not full-coop, then everyone lose = everyone is tie. I.e. set score to 0 to everybody.
=== One winner only ===
If you need to one person to win and everybody else to lose, set the scores so that the winner has the best score, and the other players have the same (lower) score. Then add the following lines to gameinfos.php:
<pre>
// If in the game, all losers are equal (no score to rank them or explicit in the rules that losers are not ranked between them), set this to true
// The game end result will display "Winner" for the 1st player and "Loser" for all other players
'losers_not_ranked' => true,
</pre>
Quantum and Coup are implemented like this, as you can see here:
* https://boardgamearena.com/#!gamepanel?game=quantum&section=lastresults
* https://boardgamearena.com/#!gamepanel?game=coupcitystate&section=lastresults
=== Solo ===
If game supports solo variant, a negative score means defeat, a positive score means victory.
=== Player elimination ===
In some games, this is useful to eliminate a player from the game in order he/she can start another game without waiting for the current game end.
This case should be rare. Please don't use player elimination feature if some player just has to wait the last 10% of the game for game end. This feature should be used only in games where players are eliminated all along the game (typical examples: "Perudo" or "The Werewolves of Miller's Hollow").
Usage:
* Player to eliminate should NOT be active anymore (preferably use the feature in a "game" type game state).
* In your PHP code:
  self::eliminatePlayer( <player_to_eliminate_id> );
* the player is informed in a dialog box that he no longer have to played and can start another game if he/she wants too (whith buttons "stay at this table" "quit table and back to main site"). In any case, the player is free to start & join another table from now.
* When your game is over, all players who have been eliminated before receive a "notification" (the small "!" icon on the top right of the BGA interface) that indicate them that "the game has ended" and invite them to review the game results.
=== Scoring Helper functions ===
    // get score
    function dbGetScore($player_id) {
        return $this->getUniqueValueFromDB("SELECT player_score FROM player WHERE player_id='$player_id'");
    }
    // set score
    function dbSetScore($player_id, $count) {
        $this->DbQuery("UPDATE player SET player_score='$count' WHERE player_id='$player_id'");
    }
    // set aux score (tie breaker)
    function dbSetAuxScore($player_id, $score) {
        $this->DbQuery("UPDATE player SET player_score_aux=$score WHERE player_id='$player_id'");
    }
    // increment score (can be negative too)
    function dbIncScore($player_id, $inc) {
        $count = $this->dbGetScore($player_id);
        if ($inc != 0) {
            $count += $inc;
            $this->dbSetScore($player_id, $count);
        }
        return $count;
    }


== Reflexion time ==
== Reflexion time ==
Dòng 440: Dòng 603:
== Managing errors and exceptions ==
== Managing errors and exceptions ==


Note: when you throw an exception, all database changes and all notifications are cancelled immediately. This way, the game situation that were existing before the request is completely restored.
Note: when you throw an exception, all database changes and all notifications are cancelled immediately. This way, the game situation that existed before the request is completely restored.


; throw new BgaUserException ( $error_message)
; throw new BgaUserException ( $error_message)
: Base class to notify a user error
: Base class to notify a user error
: You must throw this exception when a player want to do something that he is not allowed to do.
: You must throw this exception when a player wants to do something that he is not allowed to do.
: The error message will be shown to the player as a "red message", so it must be translated.
: The error message will be shown to the player as a "red message", so it must be translated.
: Throwing such an exception is NOT considered as a bug, so it is not traced in BGA error logs.
: Throwing such an exception is NOT considered a bug, so it is not traced in BGA error logs.


Example from Gomoku:
Example from Gomoku:
Dòng 455: Dòng 618:


; throw new BgaVisibleSystemException ( $error_message)
; throw new BgaVisibleSystemException ( $error_message)
: You must throw this exception when you detect something that is not supposed to happened into your code.
: You must throw this exception when you detect something that is not supposed to happened in your code.
: The error message is shown to the user as an "Unexpected error", in order he can report it in the forum.
: The error message is shown to the user as an "Unexpected error", in order that he can report it in the forum.
: The error message is logged in BGA error logs. If it happens regularly, we will report it to you.
: The error message is logged in BGA error logs. If it happens regularly, we will report it to you.


Dòng 501: Dòng 664:


Note that in the example above, all corresponding game state should implement "zombiePass" as a transition.
Note that in the example above, all corresponding game state should implement "zombiePass" as a transition.
== Player color preferences ==
BGA players (Club members) may now choose their preferred color for playing. For example, if they are used to play green for every board game, they can select "green" in their BGA preferences page.
Making your game compatible with colors preferences is very easy and requires only 1 line of PHP and 1 configuration change :
On your gameinfos.inc.php file, add the following lines :
  // Favorite colors support : if set to "true", support attribution of favorite colors based on player's preferences (see reattributeColorsBasedOnPreferences PHP method)
  'favorite_colors_support' => true,
Then, on your main <your_game>.game.php file, find the "reloadPlayersBasicInfos" call in your "setupNewGame" method and replace :
        $sql .= implode( $values, ',' );
        self::DbQuery( $sql );
        self::reloadPlayersBasicInfos();
By :
        $sql .= implode( $values, ',' );
        self::DbQuery( $sql );
        self::reattributeColorsBasedOnPreferences( $players, array(  /* LIST HERE THE AVAILABLE COLORS OF YOUR GAME INSTEAD OF THESE ONES */"ff0000", "008000", "0000ff", "ffa500", "773300" ) );
        self::reloadPlayersBasicInfos();
The "reattributeColorsBasedOnPreferences" method reattributes all colors, taking into account players color preferences and available colors.
Note that you must update the colors to indicate the colors available for your game.
2 important remarks :
* for some games (ex : Chess), the color has an influence on a mechanism of the game, most of the time by giving a special advantage to a player (ex : Starting the game). Color preference mechanism must NOT be used in such a case.
* your logic should NEVER consider that the first player has the color X, that the second player has the color Y, and so on. If this is the case, your game will NOT be compatible with reattributeColorsBasedOnPreferences as this method attribute colors to players based on their preferences and not based as their order at the table.
Colours currently listed as a choice in preferences:
* #ff0000 Red
* #008000 Green
* #0000ff Blue
* #ffa500 Yellow
* #000000 Black
* #ffffff White
== Debugging and Tracing ==
To debug php code you can use some tracing functions available from the parent class such as debug, trace, error, warn, dump.
 
  self::debug("Ahh!");
  self::dump('my_var',$my_var);
See [[Practical_debugging]] section for complete information about debugging interfaces and where to find logs.

Bản mới nhất lúc 09:06, ngày 4 tháng 4 năm 2019

This is the main file for your game logic. Here you initialize the game, persist data, implement the rules and notify the client interface of changes.

File Structure

The details of how the file is structured are described directly with comments in the code skeleton provided to you.

Here is the basic structure:

  • EmptyGame (constructor): where you define global variables.
  • setupNewGame: initial setup of the game.
  • getAllDatas: where you retrieve all game data during a complete reload of the game.
  • getGameProgression: where you compute the game progression indicator.
  • Utility functions: your utility functions.
  • Player actions: the entry points for players actions.
  • Game state arguments: methods to return additional data on specific game states (more info here).
  • Game state actions: the logic to run when entering a new game state (more info here).
  • zombieTurn: what to do it's the turn of a zombie player.

Accessing player information

getPlayersNumber()
Returns the number of players playing at the table
Note: doesn't work in setupNewGame so use count($players) instead
getActivePlayerId()
Get the "active_player", whatever what is the current state type.
Note: it does NOT mean that this player is active right now, because state type could be "game" or "multiplayer"
Note: avoid using this method in a "multiplayer" state because it does not mean anything.
getActivePlayerName()
Get the "active_player" name
Note: avoid using this method in a "multiplayer" state because it does not mean anything.
loadPlayersBasicInfos()
Get an associative array with generic data about players (ie: not game specific data).
The key of the associative array is the player id. The returned table is cached, so ok to call multiple times without performance concerns.
The content of each value is:
* player_name - the name of the player
* player_color (ex: ff0000) - the color code of the player
* player_no - the position of the player at the start of the game in natural table order, i.e. 1,2,3
getCurrentPlayerId()
Get the "current_player". The current player is the one from which the action originated (the one who send the request).
Be careful: It is not always the active player.
In general, you shouldn't use this method, unless you are in "multiplayer" state.
getCurrentPlayerName()
Get the "current_player" name
Be careful using this method (see above).
getCurrentPlayerColor()
Get the "current_player" color
Be careful using this method (see above).
isCurrentPlayerZombie()
Check the "current_player" zombie status. If true, player is zombie, i.e. left or was kicked out of the game.

Accessing the database

The main game logic should be the only point from which you should access the game database. You access your database using SQL queries with the methods below.

IMPORTANT

BGA uses database transactions. This means that your database changes WON'T BE APPLIED to the database until your request ends normally. Using transactions is in fact very useful for you; at any time, if your game logic detects that something is wrong (example: a disallowed move), you just have to throw an exception and all changes to the game situation will be removed.

DbQuery( $sql )
This is the generic method to access the database.
It can execute any type of SELECT/UPDATE/DELETE/REPLACE query on the database.
You should use it for UPDATE/DELETE/REPLACE queries. For SELECT queries, the specialized methods below are much better.
getUniqueValueFromDB( $sql )
Returns a unique value from DB or null if no value is found.
$sql must be a SELECT query.
Raise an exception if more than 1 row is returned.
getCollectionFromDB( $sql, $bSingleValue=false )
Returns an associative array of rows for a sql SELECT query.
The key of the resulting associative array is the first field specified in the SELECT query.
The value of the resulting associative array if an associative array with all the field specified in the SELECT query and associated values.
First column must be a primary or alternate key.
The resulting collection can be empty.
If you specified $bSingleValue=true and if your SQL query request 2 fields A and B, the method returns an associative array "A=>B"

Example 1:

self::getCollectionFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );

Result:
array(
 1234 => array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),
 1235 => array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )
)

Example 2:

self::getCollectionFromDB( "SELECT player_id id, player_name name FROM player", true );

Result:
array(
 1234 => 'myuser0',
 1235 => 'myuser1'
)

getNonEmptyCollectionFromDB( $sql )
Idem than previous one, but raise an exception if the collection is empty
getObjectFromDB( $sql )
Returns one row for the sql SELECT query as an associative array or null if there is no result
Raise an exception if the query return more than one row

Example:

self::getObjectFromDB( "SELECT player_id id, player_name name, player_score score FROM player WHERE player_id='$player_id'" );

Result:
array(
  'id'=>1234, 'name'=>'myuser0', 'score'=>1 
)
getNonEmptyObjectFromDB( $sql )
Idem than previous one, but raise an exception if no row is found
getObjectListFromDB( $sql, $bUniqueValue=false )
Return an array of rows for a sql SELECT query.
the result if the same than "getCollectionFromDB" except that the result is a simple array (and not an associative array).
The result can be empty.
If you specified $bUniqueValue=true and if your SQL query request 1 field, the method returns directly an array of values.

Example 1:

self::getObjectListFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );

Result:
array(
 array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),
 array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )
)

Example 2:

self::getObjectListFromDB( "SELECT player_name name FROM player", true );

Result:
array(
 'myuser0',
 'myuser1'
)

getDoubleKeyCollectionFromDB( $sql, $bSingleValue=false )
Return an associative array of associative array, from a SQL SELECT query.
First array level correspond to first column specified in SQL query.
Second array level correspond to second column specified in SQL query.
If bSingleValue = true, keep only third column on result


DbGetLastId()
Return the PRIMARY key of the last inserted row (see PHP mysql_insert_id function).
DbAffectedRow()
Return the number of row affected by the last operation
escapeStringForDB( $string )
You must use this function on every string type data in your database that contains unsafe data.
(unsafe = can be modified by a player).
This method makes sure that no SQL injection will be done through the string used.
Note: if you using standard types in ajax actions, like AT_alphanum it is sanitized before arrival,
this is only needed if you manage to get unchecked string, like in the games where user has to enter text as a response.


Note: see Editing Game database model: dbmodel.sql to know how to define your database model.

Use globals

Sometimes, you have to keep a single integer value that is global to your game, and you don't want to create a DB table specifically for it.

Using a BGA framework "global", you can do such a thing. Your value will be stored in the "global" table in database, and you can access it with simple methods.

initGameStateLabels

This method is located at the beginning of your game logic. This is the place you defines the globals used in your game logic, by assigning them IDs.

You can define up to 79 globals, with IDs from 10 to 89. You must NOT use globals outside this range as globals are used by other components of the framework.

        self::initGameStateLabels( array( 
                "my_first_global_variable" => 10,
                "my_second_global_variable" => 11
        ) );

setGameStateInitialValue( $value_label, $value_value )

Init your global value. Must be called before any use of your global, so you should call this method from your "setupNewGame" method.

getGameStateValue( $value_label )

Retrieve the current value of a global.

setGameStateValue( $value_label, $value_value )

Set the current value of a global.

incGameStateValue( $value_label, $increment )

Increment the current value of a global. If increment is negative, decrement the value of the global.

Return the final value of the global.

Game states and active players

Activate player handling

$this->activeNextPlayer()
Make the next player active in the natural player order.
Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.
$this->activePrevPlayer()
Make the previous player active (in the natural player order).
Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.
$this->gamestate->changeActivePlayer( $player_id )
You can call this method to make any player active.
Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.
$this->getActivePlayerId()
Return the "active_player" id
Note: it does NOT mean that this player is active right now, because state type could be "game" or "multiplayer"
Note: avoid using this method in a "multiplayer" state because it does not mean anything.

Multiactivate player handling

$this->gamestate->setAllPlayersMultiactive()
All playing players are made active. Update notification is sent to all players (triggers onUpdateActionButtons).
Usually, you use this method at the beginning (ex: "st" action method) of a multiplayer game state when all players have to do some action.
$this->gamestate->setAllPlayersNonMultiactive( $next_state )
All playing players are made inactive. Transition to next state
$this->gamestate->setPlayersMultiactive( $players, $next_state, $bExclusive = false )
Make a specific list of players active during a multiactive gamestate. Update notification is sent to all players who's state changed.
"players" is the array of player id that should be made active.
If "exclusive" parameter is not set or false it doesn't deactivate other previously active players. If its set to true, the players who will be multiactive at the end are only these in "$layers" array
In case "players" is empty, the method trigger the "next_state" transition to go to the next game state.
returns true if state transition happened, false otherwise
$this->gamestate->setPlayerNonMultiactive( $player_id, $next_state )
During a multiactive game state, make the specified player inactive.
Usually, you call this method during a multiactive game state after a player did his action.
If this player was the last active player, the method trigger the "next_state" transition to go to the next game state.
returns true if state transition happened, false otherwise
$this->gamestate->updateMultiactiveOrNextState( $next_state_if_none )
Sends update notification about multiplayer changes. All multiactive set* functions above do that, however if you want to change state manually using db queries for complex calculations, you have to call this yourself after. Do not call this if you calling one of the other setters above.

Example: you have player teams and you want to activate all players in one team

        $sql = "UPDATE player SET player_is_multiactive='0'";
        self::DbQuery( $sql );
        $sql = "UPDATE player SET player_is_multiactive='1' WHERE player_id='$player_id' AND player_team='$team_no'";
        self::DbQuery( $sql );
        
        $this->gamestate->updateMultiactiveOrNextState( 'error' );
$this->gamestate->getActivePlayerList()
With this method you can retrieve the list of the active player at any time.
During a "game" type gamestate, it will return a void array.
During a "activeplayer" type gamestate, it will return an array with one value (the active player id).
During a "multipleactiveplayer" type gamestate, it will return an array of the active players id.
Note: you should only use this method in the latter case.

States functions

$this->gamestate->nextState( $transition )
Change current state to a new state. Important: parameter $transition is the name of the transition, and NOT the name of the target game state, see Your game state machine: states.inc.php for more information about states.
$this->checkAction( $actionName, $bThrowException=true )
Check if action is valid regarding current game state (exception if fails).
The action is valid if it is listed as a "possibleactions" in the current game state (see game state description).
This method MUST be called in the first place in ALL your PHP methods that handle players action, in order to make sure a player can't do an action when the rules disallow it at this moment of the game.
if "bThrowException" is set to "false", the function return false in case of failure instead of throwing and exception. This is useful when several actions are possible in order to test each of them without throwing exceptions.
$this->gamestate->checkPossibleAction( $action )
(rarely used)
This works exactly like "checkAction", except that it do NOT check if current player is active.
This is used specifically in certain game states when you want to authorize some additional actions for players that are not active at the moment.
Example: in Libertalia game, you want to authorize players to change their mind about card played. They are of course not active at the time they change their mind, so you cannot use "checkAction" and use "checkPossibleAction" instead.
$this->gamestate->state()
Get an associative array of current game state attributes, see Your game state machine: states.inc.php for state attributes.
 $state=$this->gamestate->state(); if( $state['name'] == 'myGameState' ) {...}

Players turn order

getNextPlayerTable()

Return an associative array which associate each player with the next player around the table.

In addition, key 0 is associated to the first player to play.

Example: if three player with ID 1, 2 and 3 are around the table, in this order, the method returns:

   array( 
    1 => 2, 
    2 => 3, 
    3 => 1, 
    0 => 1 
   );

getPrevPlayerTable()

Same as above, but the associative array associate the previous player around the table.

getPlayerAfter( $player_id )

Get player playing after given player in natural playing order.

getPlayerBefore( $player_id )

Get player playing before given player in natural playing order.

Note: There is no API to modify this order, if you have custom player order you have to maintain it in your database and have custom function to access it.

Notify players

To understand notifications, please read The BGA Framework at a glance first.

IMPORTANT

Notifications are sent at the very end of the request, when it ends normally. It means that if you throw an exception for any reason (ex: move not allowed), no notifications will be sent to players. Notifications sent between the game start (setupNewGame) and the end of the "action" method of the first active state will never reach their destination.

notifyAllPlayers( $notification_type, $notification_log, $notification_args )

Send a notification to all players of the game.

  • notification_type:

A string that defines the type of your notification.

Your game interface Javascript logic will use this to know what is the type of the received notification (and to trigger the corresponding method).

  • notification_log:

A string that defines what is to be displayed in the game log.

You can use an empty string here (""). In this case, nothing is displayed in the game log.

If you define a real string here, you should use "clienttranslate" method to make sure it can be translate.

You can use arguments in your notification_log strings, that refers to values defines in the "notification_args" argument (see below). Note: Make sure you only use single quotes ('), otherwise PHP will try to interpolate the variable and will ignore the values in the args array.


  • notification_args:

The arguments of your notifications, as an associative array.

This array will be transmitted to the game interface logic, in order the game interface can be updated.

Complete notifyAllPlayers example (from "Reversi"):

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
     ) );

You can see in the example above the use of the "clienttranslate" method, and the use of 2 arguments "player_name" and "returned_nbr" in the notification log.

Important: NO private data must be sent with this method, as a cheater could see it even it is not used explicitly by the game interface logic. If you want to send private information to a player, please use notifyPlayer below.

Note: you CAN use some HTML inside your notification log, however it not recommended for many reasons:

  • Its bad architecture, ui elements leak into server now you have to manage ui in many places
  • If you decided to change something in ui in future version, old games reply and tutorials may not work, since they use stored notifications
  • When you read log preview for old games its unreadable (this is log before you enter the game reply, useful for troubleshooting or game analysis)
  • Its more data to transfer and store in db
  • Its nightmare for translators, at least don't put HTML tags inside the "clienttranslate" method. You can use a notification argument instead, and provide your HTML through this argument.

If you still want to have pretty pictures in the log check this BGA_Studio_Cookbook#Inject_images_and_styled_html_in_the_log.

If your notification contains some phrases that build programmatically you may need to use recursive notifications. In this case the argument can be not only the string but an array itself, which contains 'log' and 'args', i.e.

 $this->notifyAllPlayers('playerLog',clienttranslate('Game moves ${token_name_rec}'),
                  ['token_name_rec'=>['log'=>'${token_name} #${token_number}',
                                      'args'=> ['token_name'=>clienttranslate('Boo'), 'token_number'=>$number, 'i18n'=>['token_name'] ]
                                     ]
                  ]);


notifyPlayer( $player_id, $notification_type, $notification_log, $notification_args )

Same as above, except that the notification is sent to one player only.

This method must be used each time some private information must be transmitted to a player.

Important: the variable for player name must be ${player_name} in order to be highlighted with the player color in the game log

About random and randomness

A large number of board games rely on random, most often based on dice, cards shuffling, picking some item in a bag, and so on. This is very important to ensure a high level of randomness for each of these situations.

Here's are a list of techniques you should use in these situations, from the best to the worst.

Dices and bga_rand

bga_rand( min, max ) This is a BGA framework function that provides you a random number between "min" and "max" (included), using the best available random method available on the system.

This is the preferred function you should use, because we are updating it when a better method is introduced.

At now, bga_rand is based on the PHP function "random_int", which ensure a cryptographic level of randomness.

In particular, it is mandatory to use it for all dice throw (ie: games using other methods for dice throwing will be rejected by BGA during review).

Note: rand() and mt_rand() are deprecated on BGA and should not be used anymore, as their randomness is not as good as "bga_rand".

shuffle and cards shuffling

To shuffle items, like a pile of cards, the best way is to use the BGA PHP Deck component and to use "shuffle" method. This ensure you that the best available shuffling method is used, and that if in the future we improve it your game will be up to date.

At now, the Deck component shuffle method is based on PHP "shuffle" method, which has a quite good randomness (even it is not as good as bga_rand). In consequence, we accept other shuffling methods during reviews, as long as their are based on PHP "shuffle" function (or similar, like "array_rand").

Other methods

Mysql "RAND()" function has not enough randomness to be a valid method to get a random element on BGA. This function has been used in some existing games and has given acceptable results, but now it should be avoided and you should use other methods instead.

Game statistics

There are 2 types of statistics:

  • a "player" statistic is a statistic associated to a player
  • a "table" statistics is a statistic not associated to a player (global statistic for this game).

See Game statistics: stats.inc.php to see how you defines statistics for your game.


initStat( $table_or_player, $name, $value, $player_id = null )

Create a statistic entry with a default value. This method must be called for each statistics of your game, in your setupNewGame method.

'$table_or_player' must be set to "table" if this is a table statistics, or "player" if this is a player statistics.

'$name' is the name of your statistics, as it has been defined in your stats.inc.php file.

'$value' is the initial value of the statistics. If this is a player statistics and if the player is not specified by "$player_id" argument, the value is set for ALL players.


setStat( $value, $name, $player_id = null )

Set a statistic $name to $value.

If "$player_id" is not specified, setStat consider it is a TABLE statistic.

If "$player_id" is specified, setStat consider it is a PLAYER statistic.


incStat( $delta, $name, $player_id = null )

Increment (or decrement) specified statistic value by $delta value. Same behavior as setStat function.


getStat( $name, $player_id = null )

Return the value of statistic specified by $name. Useful when creating derivative statistics such as average.

Translations

See Translations

Manage player scores and Tie breaker

Normal scoring

At the end of the game, players automatically get a rank depending on their score: the player with the biggest score is #1, the player with the second biggest score is #2, and so on...

During the game, you update player's score directly by updating "player_score" field of "player" table in database.

Examples:


  // +2 points to active player
  self::DbQuery( "UPDATE player SET player_score=player_score+2 WHERE player_id='".self::getActivePlayerId()."'" );

  // Set score of active player to 5
  self::DbQuery( "UPDATE player SET player_score=5 WHERE player_id='".self::getActivePlayerId()."'" );

Note: don't forget to notify the client side in order the score control can be updated accordingly.

Tie breaker

Tie breaker is used when two players get the same score at the end of a game.

Tie breaker is using "player_score_aux" field of "player" table. It is updated exactly like the "player_score" field.

Tie breaker score is displayed only for players who are tied at the end of the game. Most of the time, it is not supposed to be displayed explicitly during the game.

When you are using "player_score_aux" functionality, you must describe the formula to use in your gameinfos.inc.php file like this:

         'tie_breaker_description' => totranslate("Describe here your tie breaker formula"),

This description will be used as a tooltip to explain to players how this auxiliary score has been calculated.

Co-operative game

To make everyone lose in full-coop game:

Add the following in gameinfos.inc.php : 'is_coop' => 1, // full cooperative

And score zero to everyone.

Semi-coop

If the game is not full-coop, then everyone lose = everyone is tie. I.e. set score to 0 to everybody.

One winner only

If you need to one person to win and everybody else to lose, set the scores so that the winner has the best score, and the other players have the same (lower) score. Then add the following lines to gameinfos.php:

// If in the game, all losers are equal (no score to rank them or explicit in the rules that losers are not ranked between them), set this to true 
// The game end result will display "Winner" for the 1st player and "Loser" for all other players
'losers_not_ranked' => true,

Quantum and Coup are implemented like this, as you can see here:

Solo

If game supports solo variant, a negative score means defeat, a positive score means victory.

Player elimination

In some games, this is useful to eliminate a player from the game in order he/she can start another game without waiting for the current game end.

This case should be rare. Please don't use player elimination feature if some player just has to wait the last 10% of the game for game end. This feature should be used only in games where players are eliminated all along the game (typical examples: "Perudo" or "The Werewolves of Miller's Hollow").

Usage:

  • Player to eliminate should NOT be active anymore (preferably use the feature in a "game" type game state).
  • In your PHP code:
 self::eliminatePlayer( <player_to_eliminate_id> );
  • the player is informed in a dialog box that he no longer have to played and can start another game if he/she wants too (whith buttons "stay at this table" "quit table and back to main site"). In any case, the player is free to start & join another table from now.
  • When your game is over, all players who have been eliminated before receive a "notification" (the small "!" icon on the top right of the BGA interface) that indicate them that "the game has ended" and invite them to review the game results.

Scoring Helper functions

   // get score
   function dbGetScore($player_id) {
       return $this->getUniqueValueFromDB("SELECT player_score FROM player WHERE player_id='$player_id'");
   }
   // set score
   function dbSetScore($player_id, $count) {
       $this->DbQuery("UPDATE player SET player_score='$count' WHERE player_id='$player_id'");
   }
   // set aux score (tie breaker)
   function dbSetAuxScore($player_id, $score) {
       $this->DbQuery("UPDATE player SET player_score_aux=$score WHERE player_id='$player_id'");
   }
   // increment score (can be negative too)
   function dbIncScore($player_id, $inc) {
       $count = $this->dbGetScore($player_id);
       if ($inc != 0) {
           $count += $inc;
           $this->dbSetScore($player_id, $count);
       }
       return $count;
   }

Reflexion time

function giveExtraTime( $player_id, $specific_time=null )
Give standard extra time to this player.
Standard extra time depends on the speed of the game (small with "slow" game option, bigger with other options).
You can also specify an exact time to add, in seconds, with the "specified_time" argument (rarely used).

Managing errors and exceptions

Note: when you throw an exception, all database changes and all notifications are cancelled immediately. This way, the game situation that existed before the request is completely restored.

throw new BgaUserException ( $error_message)
Base class to notify a user error
You must throw this exception when a player wants to do something that he is not allowed to do.
The error message will be shown to the player as a "red message", so it must be translated.
Throwing such an exception is NOT considered a bug, so it is not traced in BGA error logs.

Example from Gomoku:

     throw new BgaUserException( self::_("There is already a stone on this intersection, you can't play there") );


throw new BgaVisibleSystemException ( $error_message)
You must throw this exception when you detect something that is not supposed to happened in your code.
The error message is shown to the user as an "Unexpected error", in order that he can report it in the forum.
The error message is logged in BGA error logs. If it happens regularly, we will report it to you.
throw new BgaSystemException ( $error_message)
Base class to notify a system exception. The message will be hidden from the user, but show in the logs. Use this if the message contains technical information.
You shouldn't use this type of exception except if you think the information shown could be critical. Indeed: a generic error message will be shown to the user, so it's going to be difficult for you to see what happened.

Zombie mode

When a player leaves a game for any reason (expelled, quit), he becomes a "zombie player". In this case, the results of the game won't count for statistics, but this is cool if the other players can finish the game anyway. That's why zombie mode exists: allow the other player to finish the game, even if the situation is not ideal.

While developing your zombie mode, keep in mind that:

  • Do not refer to the rules, because this situation is not planned by the rules.
  • Try to figure that you are playing with your friends and one of them has to leave: how can we finish the game without killing the spirit of the game?
  • The idea is NOT to develop an artificial intelligence for the game.

Most of the time, the best thing to do when it is zombie player turn is to jump immediately to a state where he is not active anymore. For example, if he is in a game state where he has a choice between playing A and playing B, the best thing to do is NOT to choose A or B, but to pass. So, even if there's no "pass" action in the rules, add a "zombiepass" transitition in your game state and use it.

Each time a zombie player must play, your "zombieTurn" method is called.

Parameters:

  • $state: the name of the current game state.
  • $active_player: the id of the active player.

Most of the time, your zombieTurn method looks like this:

    function zombieTurn( $state, $active_player )
    {
    	$statename = $state['name'];

        if( $statename == 'myFirstGameState'
             ||  $statename == 'my2ndGameState'
             ||  $statename == 'my3rdGameState'
               ....
           )
        {
            $this->gamestate->nextState( "zombiePass" );
        }
        else
            throw new BgaVisibleSystemException( "Zombie mode not supported at this game state: ".$statename );
    }

Note that in the example above, all corresponding game state should implement "zombiePass" as a transition.


Player color preferences

BGA players (Club members) may now choose their preferred color for playing. For example, if they are used to play green for every board game, they can select "green" in their BGA preferences page.

Making your game compatible with colors preferences is very easy and requires only 1 line of PHP and 1 configuration change :

On your gameinfos.inc.php file, add the following lines :

 // Favorite colors support : if set to "true", support attribution of favorite colors based on player's preferences (see reattributeColorsBasedOnPreferences PHP method)
 'favorite_colors_support' => true,

Then, on your main <your_game>.game.php file, find the "reloadPlayersBasicInfos" call in your "setupNewGame" method and replace :

       $sql .= implode( $values, ',' );
       self::DbQuery( $sql );
       self::reloadPlayersBasicInfos();

By :

       $sql .= implode( $values, ',' );
       self::DbQuery( $sql );
       self::reattributeColorsBasedOnPreferences( $players, array(  /* LIST HERE THE AVAILABLE COLORS OF YOUR GAME INSTEAD OF THESE ONES */"ff0000", "008000", "0000ff", "ffa500", "773300" ) );
       self::reloadPlayersBasicInfos();


The "reattributeColorsBasedOnPreferences" method reattributes all colors, taking into account players color preferences and available colors.

Note that you must update the colors to indicate the colors available for your game.

2 important remarks :

  • for some games (ex : Chess), the color has an influence on a mechanism of the game, most of the time by giving a special advantage to a player (ex : Starting the game). Color preference mechanism must NOT be used in such a case.
  • your logic should NEVER consider that the first player has the color X, that the second player has the color Y, and so on. If this is the case, your game will NOT be compatible with reattributeColorsBasedOnPreferences as this method attribute colors to players based on their preferences and not based as their order at the table.

Colours currently listed as a choice in preferences:

  • #ff0000 Red
  • #008000 Green
  • #0000ff Blue
  • #ffa500 Yellow
  • #000000 Black
  • #ffffff White

Debugging and Tracing

To debug php code you can use some tracing functions available from the parent class such as debug, trace, error, warn, dump.

 self::debug("Ahh!");
 self::dump('my_var',$my_var);

See Practical_debugging section for complete information about debugging interfaces and where to find logs.