http://en.doc.boardgamearena.com/api.php?action=feedcontributions&user=Cpasbanal&feedformat=atom
Board Game Arena - User contributions [en]
2024-03-29T13:38:34Z
User contributions
MediaWiki 1.39.0
http://en.doc.boardgamearena.com/index.php?title=Using_Typescript_and_Scss&diff=9278
Using Typescript and Scss
2021-09-01T11:16:56Z
<p>Cpasbanal: /* create skeleton */ Fix capitalization</p>
<hr />
<div>This page will help you set up your project to use Typescript client file and Scss style files, and automatically build Javascript (in ES5) files and CSS files so your project stay compatible to BGA framework requirements.<br />
You can use only TS or only SCSS part if you want, they are not linked.<br />
<br />
== How to install the auto-build stack ==<br />
<br />
=== Install dev stack ===<br />
Install node/npm. Here is an example to activate auto-build in Visual Studio Code, if you use another tool I strongly recommend to find an equivalent so you don't have to launch build manually after each modification.<br />
<br />
=== Auto build JS and CSS files ===<br />
In VS Code, add extension https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave and then add to VSCode <code>config.json</code> extension part :<br />
"commands": [<br />
{<br />
"match": ".*\\.ts$",<br />
"isAsync": true,<br />
"cmd": "npm run build:ts"<br />
},<br />
{<br />
"match": ".*\\.scss$",<br />
"isAsync": true,<br />
"cmd": "npm run build:scss"<br />
}<br />
]<br />
}<br />
<br />
<code>build:ts</code> and <code>build:scss</code> are script tasks we'll configure later.<br />
<br />
=== Auto-upload built files ===<br />
Also add one auto-FTP upload extension (for example https://marketplace.visualstudio.com/items?itemName=lukasz-wronski.ftp-sync) and configure it. The extension will detected modified files in the workspace, including built ones, and upload them to remote server.<br />
In ignored files section, you can put :<br />
"ignore": [<br />
"\\.vscode",<br />
"\\.git",<br />
"\\.DS_Store",<br />
"package.json",<br />
"package-lock.json",<br />
"yarn.lock",<br />
"tsconfig.json",<br />
"src",<br />
"node_modules",<br />
"README.md"<br />
],<br />
<br />
=== Hint ===<br />
Make sure <code>.vscode</code> and <code>node_modules</code> are in <code>.gitignore</code> if you commit your project somewhere.<br />
<br />
== Create skeleton ==<br />
When you create your game in BGA Studio, a JS and CSS file are generated. As we will overwrite them with autobuild. You can put the TS/SCSS files on a <code>src</code> folder to separate them from built files, but that's not mandatory.<br />
<br />
=== Typescript file skeleton ===<br />
Typescript example here have been stripped down of all comments for clarity. You can view full file with generated comments [https://github.com/thoun/bga-ts-example/blob/main/src/yourgamename.ts here].<br />
Your TS code can be splitted in multiple files, you can have a look [https://github.com/thoun/bga-ts-example here] for inspiration.<br />
To make this page concise, I removed comments and code samples. If it's your first game, I strongly recommend you to report them on the TS file before activating auto-build !<br />
<br />
'''<your game name>.ts'''<br />
declare const define;<br />
declare const ebg;<br />
declare const $;<br />
//declare const dojo/*: Dojo*/;<br />
declare const _;<br />
declare const g_gamethemeurl;<br />
<br />
class <Your game name> /*implements Game*/ {<br />
<br />
private gamedatas: any;<br />
private player_id: string;<br />
private players: { [playerId: number]: any /*Player*/ };<br />
private playerNumber: number;<br />
<br />
constructor() {}<br />
<br />
public setup(gamedatas: any) {<br />
this.setupNotifications();<br />
} <br />
public onEnteringState(stateName: string, args: any) {}<br />
public onLeavingState(stateName: string) {}<br />
public onUpdateActionButtons(stateName: string, args: any) {} <br />
public setupNotifications() {}<br />
}<br />
<br />
To plug the class with BGA framework, we'll also create another file :<br />
'''define.ts'''<br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock"<br />
],<br />
function (dojo, declare) {<br />
return declare("bgagame.<your game name>", ebg.core.gamegui, new <Your game name>()); <br />
});<br />
Copy <code>bgagame.<your game name></code> from original JS to avoid name mistakes. <code>new <Your game name>()</code> must match your class name defined on previous file.<br />
<br />
=== SCSS file ===<br />
Nothing special here, just write classic SCSS (will be compiled by Dart scss) and name it '''<your game name>.scss'''<br />
<br />
Your SCSS code can be splitted in multiple files, for example :<br />
<br />
'''<your game name>.scss'''<br />
@import 'gametable';<br />
<br />
/* some classes */<br />
<br />
'''_gametable.scss'''<br />
/* _gametable related classes */<br />
<br />
=== Configure package.json and tsconfig.json ===<br />
<br />
Create a file named <code>package.json</code> on the root folder, not on the src folder.<br />
'''package.json'''<br />
{<br />
"name": "<your game name>",<br />
"version": "1.0.0",<br />
"description": "",<br />
"main": "<your game name>.js",<br />
"scripts": {<br />
"build:ts": "tsc",<br />
"build:scss": "sass --no-source-map src/<your game name>.scss <your game name>.css"<br />
},<br />
"author": "",<br />
"license": "ISC",<br />
"devDependencies": {<br />
"sass": "^1.32.6",<br />
"typescript": "^4.1.3"<br />
}<br />
}<br />
<br />
When package.json is created, run <code>npm i</code> on the root folder to install builders (will generate a node_modules folder).<br />
<br />
For Typescript compilation we also need to set up a <code>tsconfig.json</code> on the root folder.<br />
'''tsconfig.json'''<br />
{<br />
"rootDir": ".", <br />
"compilerOptions": {<br />
"target": "es5",<br />
"module": "none",<br />
"lib": ["dom", "esnext"], <br />
"sourceMap": false,<br />
"outFile": "<your game name>.js",<br />
},<br />
"files": [<br />
"src/<your game name>.ts",<br />
"src/define.ts"<br />
]<br />
}<br />
<br />
If you want to split your code onto multiple files, just add them in the file list (order is important for dependencies, new subpart should go on top of the list). No need to import classes or types from one file to another. In fact, you can't with this configuration. If you manage to make them work, please contact me and we'll update this documentation.<br />
<br />
=== Code completion ===<br />
To add code completion on framework, doko, stock, ... you'll need definition files. Feel free to copy them from [https://github.com/thoun/bga-ts-example this repo] and reference them at the top of files array, even before your subpart files.</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Using_Typescript_and_Scss&diff=9277
Using Typescript and Scss
2021-09-01T11:15:59Z
<p>Cpasbanal: /* Install dev stack */ fix typo</p>
<hr />
<div>This page will help you set up your project to use Typescript client file and Scss style files, and automatically build Javascript (in ES5) files and CSS files so your project stay compatible to BGA framework requirements.<br />
You can use only TS or only SCSS part if you want, they are not linked.<br />
<br />
== How to install the auto-build stack ==<br />
<br />
=== Install dev stack ===<br />
Install node/npm. Here is an example to activate auto-build in Visual Studio Code, if you use another tool I strongly recommend to find an equivalent so you don't have to launch build manually after each modification.<br />
<br />
=== Auto build JS and CSS files ===<br />
In VS Code, add extension https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave and then add to VSCode <code>config.json</code> extension part :<br />
"commands": [<br />
{<br />
"match": ".*\\.ts$",<br />
"isAsync": true,<br />
"cmd": "npm run build:ts"<br />
},<br />
{<br />
"match": ".*\\.scss$",<br />
"isAsync": true,<br />
"cmd": "npm run build:scss"<br />
}<br />
]<br />
}<br />
<br />
<code>build:ts</code> and <code>build:scss</code> are script tasks we'll configure later.<br />
<br />
=== Auto-upload built files ===<br />
Also add one auto-FTP upload extension (for example https://marketplace.visualstudio.com/items?itemName=lukasz-wronski.ftp-sync) and configure it. The extension will detected modified files in the workspace, including built ones, and upload them to remote server.<br />
In ignored files section, you can put :<br />
"ignore": [<br />
"\\.vscode",<br />
"\\.git",<br />
"\\.DS_Store",<br />
"package.json",<br />
"package-lock.json",<br />
"yarn.lock",<br />
"tsconfig.json",<br />
"src",<br />
"node_modules",<br />
"README.md"<br />
],<br />
<br />
=== Hint ===<br />
Make sure <code>.vscode</code> and <code>node_modules</code> are in <code>.gitignore</code> if you commit your project somewhere.<br />
<br />
== create skeleton ==<br />
When you create your game in BGA Studio, a JS and CSS file are generated. As we will overwrite them with autobuild. You can put the TS/SCSS files on a <code>src</code> folder to separate them from built files, but that's not mandatory.<br />
<br />
=== Typescript file skeleton ===<br />
Typescript example here have been stripped down of all comments for clarity. You can view full file with generated comments [https://github.com/thoun/bga-ts-example/blob/main/src/yourgamename.ts here].<br />
Your TS code can be splitted in multiple files, you can have a look [https://github.com/thoun/bga-ts-example here] for inspiration.<br />
To make this page concise, I removed comments and code samples. If it's your first game, I strongly recommend you to report them on the TS file before activating auto-build !<br />
<br />
'''<your game name>.ts'''<br />
declare const define;<br />
declare const ebg;<br />
declare const $;<br />
//declare const dojo/*: Dojo*/;<br />
declare const _;<br />
declare const g_gamethemeurl;<br />
<br />
class <Your game name> /*implements Game*/ {<br />
<br />
private gamedatas: any;<br />
private player_id: string;<br />
private players: { [playerId: number]: any /*Player*/ };<br />
private playerNumber: number;<br />
<br />
constructor() {}<br />
<br />
public setup(gamedatas: any) {<br />
this.setupNotifications();<br />
} <br />
public onEnteringState(stateName: string, args: any) {}<br />
public onLeavingState(stateName: string) {}<br />
public onUpdateActionButtons(stateName: string, args: any) {} <br />
public setupNotifications() {}<br />
}<br />
<br />
To plug the class with BGA framework, we'll also create another file :<br />
'''define.ts'''<br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock"<br />
],<br />
function (dojo, declare) {<br />
return declare("bgagame.<your game name>", ebg.core.gamegui, new <Your game name>()); <br />
});<br />
Copy <code>bgagame.<your game name></code> from original JS to avoid name mistakes. <code>new <Your game name>()</code> must match your class name defined on previous file.<br />
<br />
=== SCSS file ===<br />
Nothing special here, just write classic SCSS (will be compiled by Dart scss) and name it '''<your game name>.scss'''<br />
<br />
Your SCSS code can be splitted in multiple files, for example :<br />
<br />
'''<your game name>.scss'''<br />
@import 'gametable';<br />
<br />
/* some classes */<br />
<br />
'''_gametable.scss'''<br />
/* _gametable related classes */<br />
<br />
=== Configure package.json and tsconfig.json ===<br />
<br />
Create a file named <code>package.json</code> on the root folder, not on the src folder.<br />
'''package.json'''<br />
{<br />
"name": "<your game name>",<br />
"version": "1.0.0",<br />
"description": "",<br />
"main": "<your game name>.js",<br />
"scripts": {<br />
"build:ts": "tsc",<br />
"build:scss": "sass --no-source-map src/<your game name>.scss <your game name>.css"<br />
},<br />
"author": "",<br />
"license": "ISC",<br />
"devDependencies": {<br />
"sass": "^1.32.6",<br />
"typescript": "^4.1.3"<br />
}<br />
}<br />
<br />
When package.json is created, run <code>npm i</code> on the root folder to install builders (will generate a node_modules folder).<br />
<br />
For Typescript compilation we also need to set up a <code>tsconfig.json</code> on the root folder.<br />
'''tsconfig.json'''<br />
{<br />
"rootDir": ".", <br />
"compilerOptions": {<br />
"target": "es5",<br />
"module": "none",<br />
"lib": ["dom", "esnext"], <br />
"sourceMap": false,<br />
"outFile": "<your game name>.js",<br />
},<br />
"files": [<br />
"src/<your game name>.ts",<br />
"src/define.ts"<br />
]<br />
}<br />
<br />
If you want to split your code onto multiple files, just add them in the file list (order is important for dependencies, new subpart should go on top of the list). No need to import classes or types from one file to another. In fact, you can't with this configuration. If you manage to make them work, please contact me and we'll update this documentation.<br />
<br />
=== Code completion ===<br />
To add code completion on framework, doko, stock, ... you'll need definition files. Feel free to copy them from [https://github.com/thoun/bga-ts-example this repo] and reference them at the top of files array, even before your subpart files.</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Stock&diff=9275
Stock
2021-08-31T16:49:31Z
<p>Cpasbanal: /* Creation */ update typo on horizontally</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Overview ==<br />
<br />
<br />
'''Stock''' is a javascript component that you can use in your game interface to display a set of elements of the same size that need to be arranged in single or multiple lines.<br />
<br />
Stock is very flexible and is the most used component in BGA games.<br />
<br />
Examples of stock use cases:<br />
<br />
* Display a set of cards, typically hands (examples: ''Hearts'', ''Seasons'', ''The Boss'', ''Race for the Galaxy'').<br />
* Display items in player panels (examples: ''Takenoko'', ''Amyitis'', ...)<br />
* ... Many other situations. For example, black dice and cubes on cards in ''Troyes'' are displayed with stock components.<br />
<br />
Using stock:<br />
<br />
* Your items are arranged nicely and sorted by type.<br />
* When adding or removing items to a set, all items slide smoothly to their new position in the set.<br />
* Selecting and unselecting items are built-in functions.<br />
* You don't have to worry about inserting/removing HTML code; the entire life cycle of the stock is managed by the component.<br />
<br />
== Using stock: a simple example ==<br />
<br />
Let's have a look on how the stock is used in the game ''Hearts'' to display a hand of standard cards.<br />
<br />
First, don't forget to add "ebg/stock" as a dependency in your js file:<br />
<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <==== HERE<br />
],<br />
</pre><br />
<br />
The stock is initialized in the Javascript "setup" method like this:<br />
<br />
<pre><br />
// Player hand<br />
this.playerHand = new ebg.stock();<br />
this.playerHand.create( this, $('myhand'), this.cardwidth, this.cardheight );<br />
</pre><br />
<br />
Explanations:<br />
* We create a new stock object for the player hand.<br />
* As parameters of the "create" method, we provide the width/height of an item (a card), and the div container "myhand" - which is a simple empty "div" element defined in our HTML template (.tpl).<br />
<br />
Then, we must tell the stock what items it is going to display during its life: the 52 cards of a standard card game. Of course, we did not create 52 different images, but a "CSS sprite" image named "cards.jpg" with the cards arranged in 4 rows and 13 columns.<br />
<br />
Here's how we tell stock what items to display:<br />
<br />
<pre><br />
// Specify that there are 13 images per row in the CSS sprite image<br />
this.playerHand.image_items_per_row = 13;<br />
<br />
// Create card types:<br />
for( var color=1;color<=4;color++ )<br />
{<br />
for( var value=2;value<=14;value++ )<br />
{<br />
// Build card type id<br />
var card_type_id = this.getCardUniqueId( color, value );<br />
this.playerHand.addItemType( card_type_id, card_type_id, g_gamethemeurl+'img/cards.jpg', card_type_id );<br />
}<br />
}<br />
</pre><br />
<br />
Explanation:<br />
<br />
* First, we tell the stock component that our CSS sprite contains 13 items per row. This way, it can find the correct image for each card type id.<br />
* Then for the 4x13 cards, we call the "addItemType" method that creates the type. The arguments are the type id, the weight of the card (for sorting purpose), the URL of our CSS sprite, and the position of our card image in the CSS sprite.<br />
<br />
Note: In this specific example we need to generate a unique ID for each type of card based on its color and value. This is the only purpose of "getCardUniqueId".<br />
<br />
From now on, if we need to add a card - for example, the 5 of Hearts - to a player's hand, we can do this:<br />
<br />
<pre><br />
this.playerHand.addToStock( this.getCardUniqueId( 2 /* 2=hearts */, 5 ) );<br />
</pre><br />
<br />
In reality, cards have some IDs, which are useful to manipulate them. This is the reason we are using "addToStockWithId" instead:<br />
<br />
<pre><br />
this.playerHand.addToStockWithId( this.getCardUniqueId( 2 /* 2=hearts */, 5 ), my_card_id );<br />
</pre><br />
<br />
If afterwards we want to remove this card from the stock:<br />
<br />
<pre><br />
this.playerHand.removeFromStockById( my_card_id );<br />
</pre><br />
<br />
== Complete stock component reference ==<br />
<br />
=== Creation ===<br />
<br />
'''create( page, container_div, item_width, item_height ):'''<br />
<br />
With create, you create a new stock component.<br />
<br />
Parameters:<br />
* page: the container page. Usually: "this".<br />
* container_div: the container "div" element (an empty div element in your template, with an id).<br />
* width and height (in pixels) for the stock component.<br />
<br />
(See ''Hearts'' example above).<br />
<br />
'''addItemType( type, weight, image, image_position ):'''<br />
<br />
Define a new type of item and add it to the stock.<br />
<br />
This is mandatory to define a new item type before adding it to the stock. Example: if you want to have a stock contain cubes of 3 different colors, you must add 3 item types (one for each color).<br />
<br />
Parameters:<br />
* {int} type: id of the type to add. You can choose any positive integer. All item types must have distinct IDs.<br />
* {int} weight: weight of items of this type. Weight value is used to sort items of the stock during the display. Note that you can specify the same weight for all items; in this case, they are not sorted and their order might change randomly at any time.<br />
* {string} image: URL of item image. Most of the time, you will use a CSS sprite for stock items, so you have to specify CSS sprite image here.<br />
<br />
Be careful: you must specify the image url as this:<br />
<br />
<pre><br />
g_gamethemeurl+'img/yourimage.png'<br />
</pre><br />
<br />
* image_position: if "image" specify the URL of a CSS sprite, you must specify the position of the item image in this CSS sprite. For example, if you have a CSS sprite with 3 cubes with a size of 20x20 pixels each (so your CSS image has for example a size of 20x60 or 60x20), you specify "0" for the first cube image, 1 for the second, 2 for the third.<br />
<br />
''Important'': if there is more than one line of items in your CSS sprite, you must specify how many items per line you have in your CSS sprite, see image_items_per_row below:<br />
<br />
<br />
'''image_items_per_row'''<br />
Class member to set number of columns in css sprite (or how many items per row). I.e. if you sprite is 4 cards horizontally and 6 vertically, you have to set it to 4.<br />
<pre><br />
// Specify that there are 10 image items per row in images used in "myStockObject" control.<br />
this.myStockObject.image_items_per_row = 4;<br />
</pre><br />
<br />
=== Add/Remove items ===<br />
<br />
<br />
'''addToStock( type, from )'''<br />
<br />
Add an item to the stock, with the specified type, but without a unique ID.<br />
<br />
To make your life easier, in most cases we suggest you use '''addToStockWithId''' in order to give an ID to the item added. '''addToStock''' is suitable when you are using a stock to control items that are generic game materials that don't need to be tracked individually (example: a bunch of money tokens).<br />
<br />
Parameters:<br />
* type: ID of the item type to use (as specified in "addItemType")<br />
* from: OPTIONAL: if you specify an HTML item here, the item will appear on this item and will be slid to its position on the stock item.<br />
<br />
Example:<br />
<pre><br />
// Add a money token to the "player money" stock.<br />
// The money token will appear on "player_id" player panel and will move to its position.<br />
this.playerMoney.addToStock( MONEY_TOKEN, 'overall_player_board_'+player_id );<br />
</pre><br />
<br />
Important: for a given stock control, you must use either '''addToStock''' or '''addToStockWithId''', but NEVER BOTH OF THEM.<br />
<br />
'''addToStockWithId( type, id, from )'''<br />
<br />
This is the same method as '''addToStock''', except that it also associates an ID with the newly created item.<br />
<br />
This is especially useful:<br />
<br />
* When you need to know which item(s) have been selected by the user (see '''getSelectedItems''').<br />
* When you need to remove a specific item from the stock with '''removeFromStockById'''.<br />
<br />
'''removeFromStock( type, to, noupdate )'''<br />
<br />
Remove an item of the specific type from the stock.<br />
<br />
'to' is an optional parameter. If "to" contains the ID of an HTML element, the item removed from the Stock slides to this HTML element before it disappears. Note that this method does not expose the associated animation object, however, so it's not possible to trigger other actions when it finishes, for example. If you need to do that, use Dojo's slideToObject() instead.<br />
<br />
'noupdate' is an optional parameter. If set to "true" it will prevent the Stock display from changing. This is useful when multiple (but not all) items are removed at the same time, to avoid ghost items appearing briefly. If you pass noupdate you have to call updateDisplay() after all items are removed.<br />
<br />
'''removeFromStockById( id, to, noupdate )'''<br />
<br />
Remove an item with a specific ID from the stock.<br />
<br />
'to' is an optional parameter. If "to" contains the ID of an HTML element, the item removed from the Stock slides to this HTML element before it disappears. Note that this method does not expose the associated animation object, however, so it's not possible to trigger other actions when it finishes, for example. If you need to do that, use Dojo's slideToObject() instead.<br />
<br />
'noupdate' is an optional parameter. If set to "true" it will prevent the Stock display from changing. This is useful when multiple (but not all) items are removed at the same time, to avoid ghost items appearing briefly. If you pass noupdate you have to call updateDisplay() after all items are removed.<br />
<br />
'''removeAll()'''<br />
<br />
Remove all items from the stock.<br />
<br />
'''removeAllTo( to )'''<br />
<br />
Remove all items from the stock. If "to" contains the ID of an HTML element, the item removed from the stock slides to this HTML element before it disappears.<br />
<br />
<br />
<br />
=== Getters ===<br />
<br />
'''getPresentTypeList()'''<br />
<br />
Return an array with all the types of items present in the stock right now.<br />
<br />
Example:<br />
<pre><br />
this.myStockControl.removeAll();<br />
this.myStockControl.addToStock( 65 );<br />
this.myStockControl.addToStock( 34 );<br />
this.myStockControl.addToStock( 89 );<br />
this.myStockControl.addToStock( 65 );<br />
<br />
// The following returns: { 34:1, 65:1, 89:1 }<br />
var item_types = this.myStockControl.getPresentTypeList();<br />
</pre><br />
<br />
'''count():'''<br />
<br />
Return the total number of items in the stock right now.<br />
<br />
'''getAllItems()'''<br />
<br />
Get all items (same format as getSelectedItems and getUnselectedItems).<br />
<br />
'''getItemDivId(id)'''<br />
<br />
Get the div id using the stock item id (to manipulate element properties directly).<br />
<br />
'''getItemById(id)'''<br />
<br />
Get the Stock item with the id in parameter<br />
ex : return the object { type:1, id: 1001 }<br />
<br />
<br />
=== Selection ===<br />
<br />
'''setSelectionMode( mode )'''<br />
<br />
For each stock control, you can specify a selection mode:<br />
* 0: no item can be selected by the player.<br />
* 1: a maximum of one item can be selected by the player at a time.<br />
* 2 (default): multiple items can be selected by the player at the same time.<br />
<br />
'''setSelectionAppearance( type )'''<br />
<br />
For each stock control, you can specify a selection highlighting type:<br />
* 'border': there will be a red border around selected items (this is the default). The attribute 'apparenceBorderWidth' can be used to manage the width of the border (in pixels).<br />
* 'disappear': the selected item will fade out and disappear. This is useful when the selection has the effect of destroying the item.<br />
* 'class': there will be an extra '''stockitem_selected''' css class added to the element when it is selected (and removed when unselected). The name of the class can be changed by using the '''selectionClass''' attribute. You can also override the default class in the css file for your game but beware of the '''!important''' keyword.<br />
<br />
By default this class definition is:<br />
<pre><br />
.stockitem_selected {<br />
border: 2px solid red ! important;<br />
}<br />
</pre><br />
<br />
If you want to override it (for example, to change the border color) add this in your <game>.css file:<br />
<pre><br />
.stockitem_selected {<br />
border: 2px solid orange ! important;<br />
}<br />
</pre><br />
<br />
'''isSelected( id )'''<br />
<br />
Return a boolean indicating whether the specified item id has been selected.<br />
<br />
'''selectItem( id )'''<br />
<br />
Select the specified item.<br />
<br />
'''unselectItem( id )'''<br />
<br />
Unselect the specified item.<br />
<br />
'''unselectAll()'''<br />
<br />
Unselect all items of the stock.<br />
<br />
'''onChangeSelection'''<br />
<br />
This callback method is called when the player selects/unselects an item of the stock.<br />
<br />
You can connect this to one of your methods like this:<br />
<br />
<pre><br />
dojo.connect( this.myStockControl, 'onChangeSelection', this, 'onMyMethodToCall' );<br />
<br />
(...)<br />
<br />
onMyMethodToCall: function( control_name, item_id )<br />
{<br />
// This method is called when myStockControl selected items changed<br />
var items = this.myStockControl.getSelectedItems();<br />
<br />
// (do something)<br />
},<br />
</pre><br />
<br />
Nota bene: <br />
- The "control_name" argument is the ID (the "DOM" id) of the "div" container of your stock control. Using "control_name", you can use the same callback method for different Stock control and see which one trigger the method.<br />
- The "item_id" argument is the stock ID (index) of the stock item that has just been selected/unselected.<br />
<br />
'''getSelectedItems()'''<br />
<br />
Return the list of selected items, as an array with the following format:<br />
<pre><br />
[<br />
{ type:1, id: 1001 },<br />
{ type:1, id: 1002 },<br />
{ type:3, id: 1003 }<br />
...<br />
]<br />
</pre><br />
<br />
'''getUnselectedItems()'''<br />
<br />
Same as the previous one, but return unselected item instead of seleted ones.<br />
<br />
=== Layout ===<br />
<br />
<br />
'''resetItemsPosition()'''<br />
<br />
If you moved an item from the stock control manually (ex: after a drag'n'drop) and want to reset their positions to their original ones, you can call this method.<br />
Note: it is the same as updateDisplay() without arugment, not sure why there are two methods.<br />
<br />
<br />
<br />
'''updateDisplay(from)'''<br />
Update the display completely (if 'from' is defined: moves new items from this location).<br />
<br />
Example code if you change the underlying stock item, or otherwise want to make the stock item refresh:<br />
<pre><br />
this.myStockControl.updateDisplay();<br />
</pre><br />
<br />
'''item_margin'''<br />
<br />
By default, there is a margin of 5px between the items of a stock. You can change the member variable "item_margin" to change this.<br />
<br />
Example:<br />
<pre><br />
this.myStockControl.item_margin=5;<br />
</pre><br />
<br />
'''changeItemsWeight( newWeights )'''<br />
<br />
With this method you can change dynamically the weight of the item types in a stock control.<br />
<br />
Items are immediately re-sorted with the new weight.<br />
<br />
Example: with a stock control that contains classic cards, you can order them by value or by color. Using changeItemsWeight you can switch from one sort method to another when a player request this.<br />
<br />
newWeights is an associative array: item type id => new weight.<br />
<br />
Example:<br />
<pre><br />
// Item type 1 gets a new weight of 10, 2 a new weight of 20, 3 a new weight of 30.<br />
this.myStockControl.changeItemsWeight( { 1: 10, 2: 20, 3: 30 } );<br />
</pre><br />
<br />
Example2:<br />
<pre><br />
// Be careful with object initialisers with variables, use the bracket notation.<br />
// Item type 1 gets a new weight of 10<br />
var card_type = 1;<br />
this.myStockControl.changeItemsWeight( { [card_type]: 10 } );<br />
</pre><br />
<br />
<br />
'''centerItems'''<br />
<br />
Center the stock items in the middle of the stock container.<br />
e.g. this.myStock.centerItems = true; <br />
<br />
'''setOverlap( horizontal_percent, vertical_percent )'''<br />
<br />
Make items of the stock control "overlap" each other, to save space.<br />
<br />
By default, horizontal_overlap and vertical_overlap are 0.<br />
<br />
When horizontal_overlap=20, it means that a stock item will overlap to only show 20% of the width of all the previous items. horizontal_overlap can't be greater than 100.<br />
<br />
vertical_overlap works differently: one items on two are shifted up.<br />
<br />
See the games "Jaipur" or "KoryĆ" to see examples of use of this function.<br />
<br />
<br />
'''autowidth'''<br />
<br />
Stock does not play well if you attempt to inline-block it with other blocks, to fix that you have to set this flag which will calculate width properly<br />
mystock.autowidth = true;<br />
<br />
=== Customize apperance ===<br />
<br />
'''extraClasses'''<br />
<br />
Can be set to a list of class names (separated by space) on stock object, so when div is created these are added, i.e.<br />
<br />
this.playerHand.extraClasses='mycard';<br />
<br />
Note that it is the same list for all items.<br />
<br />
'''onItemCreate'''<br />
<br />
Using onItemCreate, you can trigger a method each time a new item is added to the Stock, in order to customize it.<br />
<br />
Complete example:<br />
<pre><br />
// During "setup" phase, we associate our method "setupNewCard" with the creation of a new stock item:<br />
this.myStockItem.onItemCreate = dojo.hitch( this, 'setupNewCard' ); <br />
<br />
<br />
// And here is our "setupNewCard":<br />
setupNewCard: function( card_div, card_type_id, card_id )<br />
{<br />
// Add a special tooltip on the card:<br />
this.addTooltip( card_div.id, _("Some nice tooltip for this item"), '' );<br />
<br />
// Note that "card_type_id" contains the type of the item, so you can do special actions depending on the item type<br />
<br />
// Add some custom HTML content INSIDE the Stock item:<br />
dojo.place( this.format_block( 'jstpl_my_card_content', {<br />
....<br />
} ), card_div.id );<br />
}<br />
<br />
</pre><br />
<br />
'''onItemDelete'''<br />
<br />
Function handler called when div is removed<br />
<br />
this.myStock.onItemDelete = (card_div, card_type_id, card_id) => { console.log("card deleted from myStock: "+card_id); };<br />
<br />
== Tips when adding/removing items to/from Stock components ==<br />
<br />
Most cases will be one of the following situations:<br />
<br />
'''Situation A''':<br />
<br />
When you add a card to a stock item, and this card is '''not''' coming from another stock:<br />
<br />
* Use '''addToStockWithId''' with a "from" argument set to the element of your interface where the card should come from (i.e. div id). For example if you want to "reveal" card from player hand and it is not an interface element you can set from to be 'player_board_'+activePlayerId (where activePlayerId is player who played that card).<br />
<br />
'''Situation B''':<br />
<br />
When you add a card to a stock item, and this card is coming from another stock:<br />
<br />
* On the destination Stock, use '''addToStockWithId''' with a "from" argument which is the HTML id of the corresponding item in the source Stock. For example, if the source stock id is "myHand", then the HTML id of card 48 is "myHand_item_48".<br />
* Then, remove the source item with '''removeFromStockById'''. Note: do NOT set the 'to' argument in this call, otherwise you'll get two animations.<br />
<br />
(Note that it's important to do things in this order, because the source item must still exist when you use it as the origin of the slide.)<br />
<br />
'''Situation C''':<br />
<br />
When you move a card from a stock item to something that is not a stock item:<br />
<br />
* Insert the card as a classic HTML template (dojo.place / this.format_block).<br />
* Place it on the Stock item with '''this.placeOnObject''', using the Stock item HTML id (see above).<br />
* Slide it to its new position with '''this.slideToObject'''.<br />
* Remove the card from the Stock item with '''removeFromStockById'''.<br />
<br />
Using the methods above, your cards should slide to, from, and between your Stock controls smoothly.<br />
<br />
<br />
You can customize this (showing the default value):<br />
<pre><br />
<br />
this.mystock.jstpl_stock_item= "<div id=\"${id}\" class=\"stockitem\" style=\"top:${top}px;left:${left}px;width:${width}px;height:${height}px;z-index:${position};background-image:url('${image}');\"></div>";<br />
</pre><br />
To produce a different type of stock item<br />
<br />
== Known issues ==<br />
<br />
==== Incorrectly displayed sprites on Safari ====<br />
<br />
As mentioned above, stock displays its images using "sprites" consisting of a portion of a larger image. To accomplish this, it uses the CSS background-position attribute in negative multiples of 100%. For example, the first element of the first row would be 0% 0%. The second element of the first row would be -100% 0%, etc.<br />
<br />
This works fine except in Safari, which sometimes slightly modifies the width of elements at smaller screen sizes. For example, you might get a ~49.99px x ~49.99px div instead of the 50px x 50px specified by CSS width/height. Combined with using percentages in background-position, this can cause sprites to be slightly off-center.<br />
<br />
Using pixels in background-position is probably the easiest way to fix this problem, but that would require modifying the stock code itself. As a developer, you can also work around the problem by specifying a background-size attribute based on the size in elements of your source image. For example, if you have a source image that contains 5 rows of 28 sprites, you should specify "background-size: 2800% 500%;"<br />
<br />
You can add background-size to your stock items by using the extraClasses attribute as explained above and then specifying the values in your game.css file.</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Translations&diff=7810
Translations
2021-03-31T16:43:02Z
<p>Cpasbanal: /* WARNING: Make sure your strings will be translated! */ Small typo on clienttranslate</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
<br />
Using BGA Studio, the game you create is ready to be translated to each language by the BGA community. To make this possible, you only need to specify which string must be translated and how to combine them.<br />
<br />
== How translation works? ==<br />
<br />
When developing your game, all strings must be in English. Strings must be coherent with the English version of the game.<br />
<br />
Before the release of the game, BGA team will do the French translation of the game.<br />
<br />
After the release of the game, the BGA players community will translate the game in every language.<br />
<br />
== What should be translated? ==<br />
<br />
Every text that can be visible by the player when the game is running normally. This includes tooltips, texts on cards, error messages, ...<br />
<br />
This does NOT include error messages that are not supposed to happen (unexpected errors).<br />
<br />
== What rules should I follow for the original English strings? ==<br />
<br />
For a coherent and homogeneous interface, here are some rules about ending a sentence with a final period '.'<br />
<br />
* As a general rule:<br />
** If a sentence is displayed isolated in the interface => no final period<br />
** If a sentence is followed or could be followed by another sentence in the same interface space => final period.<br />
<br />
* In detail:<br />
** No final period:<br />
*** button labels<br />
*** section titles<br />
*** menu elements<br />
*** links triggering an isolated action<br />
*** anything that is not a full sentence<br />
*** current actions in the status bar<br />
** Final period:<br />
*** complete sentences (e.g., explanations and descriptions) that can be chained with other sentences<br />
** Either a period or no period is acceptable (but this should be consistent throughout the game) for:<br />
*** isolated tooltips / small sentences<br />
*** game logs (no period is usually preferable)<br />
*** error messages (unless there is more than one sentence in the error message; final period is mandatory in this case)<br />
<br />
Otherwise, you should try to follow as closely as possible the general style and format (including capitalization) used in the published English rulebook and game materials.<br />
<br />
== Focus on translating notifications ==<br />
<br />
Usually, translating a website is simple: you just call a function on every string you have to translate, and the string is translated in the player's language. On Board Game Arena, this is exactly the same with the "_( string )" function.<br />
<br />
However, there is one difference on BGA: notifications. The server is sending notifications to players, and most of the time the notifications are the same for every players, no matter what language each player is using. This is why notifications are translated on client side in the proper language, even if the strings are defined on server side.<br />
<br />
== WARNING: Make sure your strings will be translated! ==<br />
<br />
For each game, our translation tool does a full scan of the code, looking for translation markers like "_()" or "clienttranslate()". (See below for the full list of translation markers.)<br />
<br />
If your original string is not completely contained inside one of these markers, it won't be translated.<br />
<br />
<pre><br />
// Examples: the following strings will be translated:<br />
var mystring_translated = _("my string"); // JS<br />
$mystring_translated = self::_("my string"); // PHP<br />
$mystring_translated = sprintf( self::_("my string with an %s argument"), $argument ); // PHP<br />
<br />
// Examples: the following strings WILL NOT be translated:<br />
$my_string = "my string";<br />
$not_translated = self::_( $my_string ); // The original string is not contained within a translator marker => no translation<br />
$not_translated = self::_( sprintf( "my string with a %s argument", $argument ) ); // Ditto<br />
</pre><br />
<br />
== How to not make translators crazy ;) ==<br />
<br />
* Try to reuse the exact same strings (with the same case, etc.) to minimize the number of strings to translate.<br />
** '''Example''': Consider replacing <pre>self::_("Winner")</pre> and <pre>self::_("Winners")</pre> (two strings to translate) with <pre>self::_("Winner(s)")</pre><br />
** '''Example 2''': <pre>clienttranslate("play a card")</pre> and <pre>clienttranslate("Play a card")</pre> means there will be two strings to translate.<br />
* Do not mark as translatable a game element that does not have to be translated (e.g., if the name of a monster on a card is "Zzzzz", maybe there's no need to translate it).<br />
* Words does not come in the same order in each language. Thus, when you have to translate a string with an argument, do not write something like:<br />
<pre>self::_("First part of the string, ").$argument.' '.self::_("second part of the string")</pre><br />
Write instead:<br />
<pre>sprintf( self::_("First part of the string, %s second part of the string"), $argument )</pre><br />
(or the equivalent "dojo.string.substitute" in Javascript)<br />
* When translators are going to translate your game, the most difficult task for them is to get the context of the string to be translated. The shorter the string is, the more difficult the task is for them. As a rule of thumb, try to avoid short, insignificant strings that require knowledge of their surrounding context. You can also leave a comment on the context of the string in the translation program (English to English) if you are the developer of the game.<br />
* The BGA translation policy is to be flexible on grammar. We prefer to write "player gets 1 coin(s)" rather than write two versions of the same string for plural and singular - it reduces the number of strings to translate.<br />
* Instead of writing elaborate strings like "With the effect of ZZZ, player XXX gains a new YYY", which is very difficult to translate, write strings like "ZZZ: XXX gets YYY".<br />
* Use present tense instead of past. E.g., "player gets wood" instead of "player got wood".<br />
* Avoid using gender-specific pronouns. E.g., instead of "player returns card to *his* hand" use "player returns card to *their* hand," or just avoid using pronouns. E.g., "player picks up the card".<br />
<br />
== On client side (Javascript) ==<br />
<br />
On client side, things are quite simple: you just have to use the "_()" function for all strings you want to translate.<br />
<br />
Examples:<br />
<pre><br />
// Get a string in player's language:<br />
var translated = _("original english string");<br />
<br />
// Get a string in player's language with parameter:<br />
var translated = dojo.string.substitute( _("You can pick ${p} cards and discard ${d}"), {<br />
p: 2,<br />
d: 4<br />
} );<br />
</pre><br />
<br />
'''WARNING:''' in Javascript strings to translate, you should never use '\n', '\t' or such, as it will break the translation bundle and result in all the Javascript translation to fail. In any case, the strings will result in HTML code, and such character codes won't have any impact on the HTML rendering. You should use HTML markup instead.<br />
<br />
'''ANOTHER WARNING:''' you cannot use this function _() in the javascript object constructor, but you can achieve the same if you use it the setup method<br />
<br />
== On server side (PHP) ==<br />
<br />
On PHP side, you can use 3 different functions to specify that a string must be translated.<br />
<br />
'''clienttranslate( "string to translate" ):'''<br />
<br />
This function is ''transparent'': it will return the original English string without any change. Its only purpose is to mark this string as "must be translated", and to make sure the translated version of the string will be available on client side.<br />
<br />
In general, you use clienttranslate:<br />
* In your '''states.inc.php''', for the fields "description" and "descriptionmyturn".<br />
<br />
<pre><br />
"description" => clienttranslate('${card_name}: ${actplayer} must discard 4 identical energies'),<br />
</pre><br />
<br />
* In '''material.inc.php''', when defining text for game materials that must be displayed on the client side.<br />
<br />
<pre><br />
$this->card_types = array(<br />
<br />
1 => array(<br />
'name' => clienttranslate("Amulet of Air"), // Thus, we can use "_( card_name )" on Javascript side.<br />
</pre><br />
<br />
* When sending a notification with ''notifyAllPlayers'' or ''notifyPlayer'', in the game log string and all game log arguments that need a translation.<br />
<br />
<pre><br />
// A game log string with no argument:<br />
self::notifyAllPlayers( 'pickLibraryCards', clienttranslate("Everyone draw cards from his library"), array() );<br />
</pre><br />
<br />
As a consequence there is no point passing variables to this function. E.g.:<br />
<br />
<pre><br />
notif="foo";<br />
self::notifyAllPlayers( 'log', clienttranslate(notif)); // BAD<br />
<br />
notif=clienttranslate("foo");<br />
self::notifyAllPlayers( 'log', notif); // GOOD<br />
</pre><br />
<br />
Translating arguments is a little bit more complex. This uses the '''i18n''' special argument as below:<br />
<br />
<pre><br />
// In the following example, we translate the game log itself, but also the "card_name" argument:<br />
<br />
self::notifyAllPlayers( 'winPoints', clienttranslate('${card_name}: ${player_name} gains ${points} point(s)'), array(<br />
'i18n' => array( 'card_name' ), // <===== We specify here that "card_name" argument must be translated<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'points' => $points,<br />
'card_name' => $this->card_types[8]['name'] // <==== Here, we provide original English string.<br />
) ); <br />
</pre><br />
<br />
To ensure the translation of the i18n argument will be made, clienttranslate must have been used somewhere, for instance:<br />
<br />
<pre><br />
$this->card_types = array(<br />
...<br />
8 => array(<br />
'name' => clienttranslate("Amulet of Fire"),<br />
...<br />
</pre><br />
<br />
Pay attention when using the '''i18n''' argument when translating arguments for the client: do NOT use same argument for both translations AND key codes for client-side actions (like using 'card_name' to move it on the player board as described in the example). It's pretty obvious in the example, but it can be very tricky when translation is made at the end of the development (which is often the case). Use explicit argument names like 'card_name_translated' by example.<br />
<br />
'''WARNING:''' you should NEVER use concatenation with ''clienttranslate'', as it would result in a different string to translate at runtime than the one retrieved statically in the translation system, and so translation would not be applied. If you need to compose a string, use substitution and the i18n parameter (but you also have to pay attention not to compose sentences in a way that is dependent upon the English language specific syntax, or it may be impossible to translate correctly in another language: sometimes, you need multiple full sentences rather than relying on substitution).<br />
<br />
'''self::_( "my string to translate" ):'''<br />
<br />
This function returns a string translated in the language of CURRENT user (i.e. player who send the request to the server) (be careful, this is NOT the active player).<br />
<br />
Most of the time, you don't need to translate strings on server side, except on the following 3 situations:<br />
* When throwing an exception because the player did a forbidden move.<br />
<br />
<pre><br />
// This will display a translatable red message to the player that just did some wrong action:<br />
throw new BgaUserException( self::_('You must choose 3 cards') );<br />
<br />
// ... notice the use of BgaUserException that signals that this exception is "expected". In theory, all exception that are expected should be translated.<br />
</pre><br />
<br />
* In "yourgame.view.php", when creating the labels for the game interface used in your template (.tpl) file.<br />
<br />
<pre><br />
$this->tpl['CARDS_FOR_YEAR_2'] = self::_("Your cards for year II");<br />
</pre><br />
<br />
'''Nota bene:''' it is recommended to set translated text in your interface client side rather than use template variables and server translation, so that translation works natively in replays (that don't have server side translations). For simple strings, the framework will handle moving the translation client side, but for more complex strings it may not work. In particular, you should not use server side translation for templates with substitution variables (or use the specific function "gameview_str_replace( $search, $replace, $string )" if not possible otherwise or for old code compatibility). Also, you should never use a "to_translate" class in your .tpl as it is used internally by the framework.<br />
<br />
* Eventually, in your material.inc.php, if for example you need to use some string elements in your exceptions.<br />
<br />
<pre><br />
// In material.inc.php, $this->energies[n]['nametr'] has been created with the self::_() method. Now we can do this:<br />
throw new BgaUserException( self::_("To execute this action you need more: ").' '.$this->energies[$resource_id]['nametr'] );<br />
</pre><br />
<br />
* Eventually, in your "getAllDatas" PHP method, as the data return by this method is used only by current user.<br />
<br />
'''totranslate( "my string to translate" ):'''<br />
<br />
This function works exactly like 'clienttranslate', except it tells BGA that the string is not needed on client side.<br />
<br />
You should not use this function, except on the following cases:<br />
* Statistics name in stats.inc.php<br />
* Option names and option values name in gameoptions.inc.php<br />
<br />
== On server side - advanced (PHP) ==<br />
<br />
If you want to use translation system in your custom static modules, you will first need to expose the _() function :<br />
<pre><br />
class mygame extends Table {<br />
// Exposing protected method translation<br />
public static function totranslate($text) {<br />
return self::_($text);<br />
}<br />
}<br />
</pre><br />
<br />
Then you'll be able to use this directly in other modules by doing<br />
<pre><br />
throw new BgaUserException(mygame::totranslate("Translated error from my awesome module file"));<br />
</pre><br />
<br />
Notice how the "totranslate" is also used by the static analysis to detect your string. So the following will not work :<br />
<pre><br />
$msg = "Translated error from my awesome module file";<br />
throw new BgaUserException(mygame::totranslate($msg));<br />
</pre></div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Game_interface_logic:_yourgamename.js&diff=7576
Game interface logic: yourgamename.js
2021-03-07T17:26:16Z
<p>Cpasbanal: /* Animations */ Add .play() to the example to remember this is an animation object</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game interface. Here you will define:<br />
<br />
* Which actions on the page will generate calls to the server.<br />
* What happens when you get a notification for a change from the server and how it will show in the browser. <br />
<br />
== File structure ==<br />
<br />
The details of how the file is structured are described below with comments on the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* '''constructor''': here you can define global variables for your whole interface.<br />
* '''setup''': this method is called when the page is refreshed, and sets up the game interface.<br />
* '''onEnteringState''': this method is called when entering a new game state. You can use it to customize the view for each game state.<br />
* '''onLeavingState''': this method is called when leaving a game state.<br />
* '''onUpdateActionButtons''': called when entering a new state, in order to add action buttons to the status bar.<br />
* ''(utility methods)'': this is where you can define your utility methods.<br />
* ''(player's actions)'': this is where you can write your handlers for player actions on the interface (example: click on an item).<br />
* '''setupNotifications''': this method associates notifications with notification handlers. For each game notification, you can trigger a javascript method to handle it and update the game interface.<br />
* ''(notification handlers)'': this is where you define the notifications handlers associated with notifications in '''setupNotifications''', above.<br />
<br />
<br />
More details:<br />
<br />
<br />
;onEnteringState(stateName, args)<br />
This method is called each time we are entering into a new game state.<br />
You can use this method to perform some user interface changes at this moment.<br />
To access state arguments passed via calling arg* method use args.args.<br />
Typically you would do something only for active player, using this.isCurrentPlayerActive() check.<br />
<br />
'''Warning''': for multipleactiveplayer states:<br />
the active players are NOT actives yet so you must use onUpdateActionButtons to perform the client side operation which depends on a player active/unactive status.<br />
If you are doing initialization of some structure which do not depend on active player, you can just replace <br />
if (this.isCurrentPlayerActive()) {<br />
into<br />
if (!this.isSpectator) {<br />
for the main switch in that method.<br />
<br />
;onLeavingState(stateName)<br />
This method is called each time we are leaving a game state.<br />
You can use this method to perform some user interface changes at this moment.<br />
<br />
;onUpdateActionButtons(stateName, args)<br />
In this method you can manage "action buttons" that are displayed in the action status bar.<br />
To access state arguments passed via calling arg* method use args parameter. Note: args can be null! For game states and when you don't supply state args function it is null.<br />
This method is called when active or multiactive player changes. In classic "activePlayer" state this method is called before the onEnteringState state.<br />
In multipleactiveplayer state it is a mess. The sequencing of call would depends on either you get into that from transition from reloading the whole game (i.e. F5).<br />
<br />
See more details in [[Your_game_state_machine:_states.inc.php#Diffrence_between_Single_active_and_Multi_active_states]]<br />
<br />
== General tips ==<br />
<br />
; this.player_id<br />
: ID of the player on whose browser the code is running.<br />
<br />
; this.isSpectator<br />
: Flag set to true if the user at the table is a spectator (not a player).<br />
: Note: This is a variable, not a function.<br />
: Note: If you want to hide an element from spectators, you should use [[Game_interface_stylesheet:_yourgamename.css#spectatorMode|CSS 'spectatorMode' class]].<br />
<br />
; this.gamedatas<br />
: Contains the initial set of data to init the game, created at game start or by game refresh (F5).<br />
: You can update it as needed to keep an up-to-date reference of the game on the client side if you need it. (Most of the time this is unnecessary).<br />
<br />
; this.isCurrentPlayerActive()<br />
: Returns true if the player on whose browser the code is running is currently active (it's his turn to play).<br />
<br />
; this.getActivePlayerId()<br />
: Return the ID of the active player, or null if we are not in an "activeplayer" type state.<br />
<br />
; this.getActivePlayers()<br />
: Return an array with the IDs of players who are currently active (or an empty array if there are none).<br />
<br />
; this.bRealtime<br />
: Return true if the game is in realtime. Note that having a distinct behavior in realtime and turn-based should be exceptional.<br />
<br />
; typeof g_replayFrom != 'undefined'<br />
: Returns true if the game is in replay mode <i>during the game</i> (the game is ongoing but the user clicked "reply from this move" in the log)<br />
<br />
; g_archive_mode<br />
: Returns true if the game is in archive mode <i>after the game</i> (the game has ended)<br />
<br />
; this.instantaneousMode<br />
: Returns true during replay/archive mode if animations should be skipped. Only needed if you are doing custom animations. (The BGA-provided animation functions like <i>this.slideToObject()</i> automatically handle instantaneous mode.)<br />
: Technically, when you click "replay from move #20", the system replays the game from the very beginning with moves 0 - 19 happening in instantaneous mode and moves 20+ happening in normal mode.<br />
<br />
---<br />
<br />
You may consider making a function like this, to detect if the game is in a read-only state:<br />
<br />
// Returns true for spectators, instant replay (during game), archive mode (after game end)<br />
isReadOnly: function () {<br />
return this.isSpectator || typeof g_replayFrom != 'undefined' || g_archive_mode;<br />
}<br />
<br />
== Dojo framework ==<br />
<br />
BGA uses the [http://dojotoolkit.org/ Dojo Javascript framework].<br />
<br />
The Dojo framework allows us to do complex things more easily. The BGA framework uses Dojo extensively.<br />
<br />
To implement a game, you only need to use a few parts of the Dojo framework. All the Dojo methods you need are described on this page.<br />
<br />
== Javascript minimization (before July 2020) ==<br />
<br />
For performance reasons, when deploying a game the js code is minimized using ShrinkSafe (based on ECMASCRIPT version 3). Some advanced syntax may not be compatible with this process. In particular:<br />
* You should not use reserved keywords from the javascript language as variables.<br />
* You should not declare default argument values in function declarations. The following syntax is invalid for ShrinkSafe: '''function myFunc(requiredArg, optionalArg = 'defaultValue') {}'''<br />
* You should not use '''let''' or '''const''' to declare variables.<br />
<br />
'''Tip:''' a developer encountering some problems with this has successfully used [http://plugins.netbeans.org/plugin/58580/jshint JSHint] on NetBeans to evaluate code to make it compatible for ECMAScript 3. With the plugin installed, set the below options in '''.jshintrc''' file, and then open '''Action Items''' window (in NetBeans):<pre>{ "maxerr": 999, "esversion": 3 }</pre><br />
<br />
'''Tip:''' some online tools also allow to convert between different versions of javascript, such as https://www.typescriptlang.org/play or https://babeljs.io/ or https://extendsclass.com/javascript-fiddle.html<br />
<br />
== Javascript minimization (after July 2020) ==<br />
<br />
For performance reasons, when deploying a game the javascript code is minimized using '''terser''' (https://github.com/terser/terser). This minifier works with modern javascript syntax. From your project "Manage game" page, you can now test a minified version of your javascript on the studio (and revert to the original).<br />
<br />
NB: it has been reported that there is an issue with this minifier and percentage values for opacity.<br />
<br />
== Accessing and manipulating the DOM ==<br />
<br />
'''$('some_html_element_id')'''<br />
<br />
The $() function is used to get an HTML element using its "id" attribute.<br />
<br />
Example 1: modify the content of a "span" element:<br />
<br />
<pre><br />
In your HTML code:<br />
<span id="a_value_in_the_game_interface">1234</span><br />
<br />
In your Javascript code:<br />
$('a_value_in_the_game_interface').innerHTML = "9999";<br />
</pre><br />
<br />
Note: $() is the standard method to access some HTML element with the BGA Framework. You should not use the '''getElementById''' function.<br />
<br />
<br />
'''dojo.style'''<br />
<br />
With dojo.style you can modify the CSS property of any HTML element in your interface.<br />
<br />
Examples:<br />
<br />
<pre><br />
// Make an element disappear<br />
dojo.style( 'my_element', 'display', 'none' );<br />
<br />
// Give an element a 2px border<br />
dojo.style( 'my_element', 'borderWidth', '2px' );<br />
<br />
// Change the background position of an element<br />
// (very practical when you are using CSS sprites to transform an element to another)<br />
dojo.style( 'my_element', 'backgroundPosition', '-20px -50px' );<br />
</pre><br />
<br />
Note: you must always use dojo.style to modify the CSS properties of HTML elements.<br />
<br />
NoteÂČ: if you have to modify several CSS properties of an element, or if you have a complex CSS transformation to do, you should consider using dojo.addClass/dojo.removeClass (see below).<br />
<br />
<br />
'''dojo.addClass''', '''dojo.removeClass''', '''dojo.hasClass''', '''dojo.toggleClass'''<br />
<br />
In many situations, many small CSS property updates can be replaced by a CSS class change (i.e., you add a CSS class to your element instead of applying all modifications manually).<br />
<br />
Advantages are:<br />
* All your CSS stuff remains in your CSS file.<br />
* You can add/remove a list of CSS modifications with a simple function and without error.<br />
* You can test whether you applied the CSS to an element with the '''dojo.hasClass''' method.<br />
<br />
Example from ''Reversi'':<br />
<br />
<pre><br />
// We add "possibleMove" to an element<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
<br />
// In our CSS file, the class is defined as:<br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
<br />
// So we've applied 4 CSS property changes in one line of code.<br />
<br />
// ... and when we need to check if a square is a possible move on the client side:<br />
if( dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{ ... }<br />
<br />
// ... and if we want to remove all possible moves in one line of code (see "dojo.query" method):<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
</pre><br />
<br />
Conclusion: We encourage you to use '''dojo.addClass''', '''dojo.removeClass''' and '''dojo.hasClass''' to make your life easier :)<br />
<br />
'''dojo.attr'''<br />
<br />
With dojo.attr you can access or change the value of an attribute or property of any HTML element in your interface.<br />
<br />
Exemple:<br />
<pre><br />
// Get the title of a node<br />
var title = dojo.attr( id, 'title' );<br />
// Change the height of a node<br />
dojo.attr( 'img_growing_tree', 'height', 100 );<br />
</pre><br />
<br />
'''dojo.query'''<br />
<br />
With dojo.query, you can query a bunch of HTML elements with a single function, with a "CSS selector" style.<br />
<br />
Example:<br />
<pre><br />
// All elements with class "possibleMove":<br />
var elements = dojo.query( '.possibleMove' );<br />
<br />
// Count number of tokens (i.e., elements of class "token") on the board (i.e., the element with id "board"):<br />
dojo.query( '#board .token' ).length;<br />
</pre><br />
<br />
But what is really cool with dojo.query is that you can combine it with almost all methods above.<br />
<br />
Examples:<br />
<pre><br />
// Trigger a method when the mouse enter in any element with class "meeple":<br />
dojo.query( '.meeple' ).connect( 'onmouseenter', this, 'myMethodToTrigger' );<br />
<br />
// Hide all meeples who are on the board<br />
dojo.query( '#board .meeple' ).style( 'display', 'none' );<br />
</pre><br />
<br />
'''dojo.place'''<br />
<br />
dojo.place is the best function to insert HTML code somewhere in your game interface without breaking something. It is much better to use than the '''innerHTML=''' method if you must insert HTML tags and not only values.<br />
<br />
<pre><br />
// Insert your HTML code as a child of a container element<br />
dojo.place( "<your html code>", "your_container_element_id" );<br />
<br />
// Replace the container element with your new html<br />
dojo.place( "<your html code>", "your_container_element_id", "replace" );<br />
<br />
</pre><br />
<br />
The third parameter of dojo.place can take various interesting values:<br />
<br />
"replace" : (see description above).<br />
<br />
"first" : Places the node as a child of the reference node. The node is placed as the first child.<br />
<br />
"last" (default) : Places the node as a child of the reference node. The node is placed as the last child.<br />
<br />
"before" : places the node right before the reference node.<br />
<br />
"after" : places the node right after the reference node.<br />
<br />
"only" : replaces all children of the reference node with the node.<br />
<br />
positif integer : This parameter can be a positif integer. In this case, the node will be placed as a child of the reference node with this number (counting from 0). If the number is more than number of children, the node will be appended to the reference node making it the last child. <br />
<br />
See also full doc on dojo.place : [https://dojotoolkit.org/reference-guide/1.7/dojo/place.html]<br />
<br />
Usually, when you want to insert some piece of HTML in your game interface, you should use "[[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]".<br />
<br />
'''dojo.empty'''<br />
<br />
Remove all children of the node element<br />
<br />
'''dojo.destroy'''<br />
<br />
Remove the element<br />
<br />
dojo.query(".green", mynode).forEach(dojo.destroy); // this remove all subnode of class green from mynode<br />
<br />
'''dojo.create'''<br />
<br />
Create element<br />
<br />
dojo.create("div", { class: "yellow_arrow" }, parent); // this creates div with class yellow_array and places it in "parent"<br />
<br />
'''addStyleToClass: function( cssClassName, cssProperty, propertyValue )'''<br />
<br />
Same as dojo.style(), but for all the nodes set with the specified cssClassName<br />
<br />
=== Animations ===<br />
<br />
'''Dojo Animations'''<br />
<br />
BGA animations is based on Dojo Animation ([http://dojotoolkit.org/documentation/tutorials/1.8/animation/ see tutorial here]).<br />
<br />
However, most of the time, you can just use methods below, which are built on top of Dojo Animation.<br />
<br />
Note: one interesting method from Dojo that could be useful from time to time is "Dojo.Animation". It allows you to make any CSS property "slide" from one value to another.<br />
<br />
Note 2: the slideTo methods are not compatible with CSS transform (scale, zoom, rotate...). If possible, avoid using CSS transform on nodes that are being slided. Eventually, the only possible solution to make these 2 compatible is to disable all CSS transform properties, use slideToObjectPos/placeOnObjectPos, and then apply them again.<br />
<br />
<br />
'''this.slideToObject( mobile_obj, target_obj, duration, delay )'''<br />
<br />
You can use slideToObject to "slide" an element to a target position.<br />
<br />
Sliding element on the game area is the recommended and the most used way to animate your game interface. Using slides allow players to figure out what is happening on the game, as if they were playing with the real boardgame.<br />
<br />
The parameters are:<br />
* mobile_obj: the ID of the object to move. This object must be "relative" or "absolute" positioned.<br />
* target_obj: the ID of the target object. This object must be "relative" or "absolute" positioned. Note that it is not mandatory that mobile_obj and target_obj have the same size. If their size are different, the system slides the center of mobile_obj to the center of target_obj.<br />
* duration: (optional) defines the duration in millisecond of the slide. The default is 500 milliseconds.<br />
* delay: (optional). If you defines a delay, the slide will start only after this delay. This is particularly useful when you want to slide several object from the same position to the same position: you can give a 0ms delay to the first object, a 100ms delay to the second one, a 200ms delay to the third one, ... this way they won't be superposed during the slide.<br />
<br />
BE CAREFUL: The method returns an dojo.fx animation, so you can combine it with other animation if you want to. It means that you have to call the "play()" method, otherwise the animation WON'T START.<br />
<br />
Example:<br />
<pre><br />
this.slideToObject( "some_token", "some_place_on_board" ).play();<br />
</pre><br />
<br />
<br />
'''this.slideToObjectPos( mobile_obj, target_obj, target_x, target_y, duration, delay )'''<br />
<br />
This method does exactly the same as "slideToObject", except than you can specify some (x,y) coordinates. This way, "mobile_obj" will slide to the specified x,y position relatively to "target_obj".<br />
<br />
Example: slide a token to some place on the board, 10 pixels to the bottom:<br />
<pre><br />
this.slideToObjectPos( "some_token", "some_place_on_board", 0, 10 ).play();<br />
</pre><br />
<br />
'''this.slideTemporaryObject( mobile_obj_html, mobile_obj_parent, from, to, duration, delay )'''<br />
<br />
This method is useful when you want to slide a temporary HTML object from one place to another. As this object does not exists before the animation and won't remain after, it could be complex to create this object (with dojo.place), to place it at its origin (with placeOnObject) to slide it (with slideToObject) and to make it disappear at the end.<br />
<br />
slideTemporaryObject does all of this for you:<br />
* mobile_obj_html is a piece of HTML code that represent the object to slide.<br />
* mobile_obj_parent is the ID of an HTML element of your interface that will be the parent of this temporary HTML object.<br />
* from is the ID of the origin of the slide.<br />
* to is the ID of the target of the slide.<br />
* duration/delay works exactly like in "slideToObject"<br />
<br />
Example:<br />
<pre><br />
this.slideTemporaryObject( '<div class="token_icon"></div>', 'tokens', 'my_origin_div', 'my_target_div' ).play();<br />
</pre><br />
<br />
'''this.slideToObjectAndDestroy: function( node, to, time, delay )'''<br />
<br />
This method is a handy shortcut to slide an existing HTML object to some place then destroy it upon arrival. It can be used for example to move a victory token or a card from the board to the player panel to show that the player earns it, then destroy it when we don't need to keep it visible on the player panel.<br />
<br />
It works the same as this.slideToObject and takes the same arguments. <br />
<br />
Example:<br />
<pre><br />
this.slideToObjectAndDestroy( "some_token", "some_place_on_board", 1000, 0 );<br />
</pre><br />
<br />
'''this.fadeOutAndDestroy( node, duration, delay )'''<br />
<br />
This function fade out the target HTML node, then destroy it.<br />
<br />
Example:<br />
<pre><br />
this.fadeOutAndDestroy( "a_card_that_must_disappear" );<br />
</pre><br />
<br />
CAREFUL: the HTML node still exists until during few milliseconds, until the fadeOut has been completed.<br />
<br />
'''Rotating elements'''<br />
<br />
You can check here [http://jimfulton.info/demos/dojo-animated-rotate.html an example of use] of Dojo to make an element rotate.<br />
<br />
This example combines "Dojo.Animation" method and a CSS3 property that allow you to rotate the element.<br />
<br />
IMPORTANT: to asses browser compatibility, you must select the CSS property to use just like in the example (see sourcecode below):<br />
<br />
<pre><br />
var transform;<br />
dojo.forEach(<br />
['transform', 'WebkitTransform', 'msTransform',<br />
'MozTransform', 'OTransform'],<br />
function (name) {<br />
if (typeof dojo.body().style[name] != 'undefined') {<br />
transform = name;<br />
}<br />
});<br />
// ... and then use "transform" as the name of your CSS property for rotation<br />
</pre><br />
<br />
'''Animation Callbacks'''<br />
<br />
If you wish to run some code only after an animation has completed you can do this by linking a callback method.<br />
<br />
<pre><br />
var animation_id = this.slideToObject( mobile_obj, target_obj, duration, delay );<br />
dojo.connect(animation_id, 'onEnd', dojo.hitch(this, 'callback_function', parameters));<br />
animation_id.play();<br />
<br />
âŠ<br />
<br />
callback_function: function(params) {<br />
// this will be called after the animation ends<br />
},<br />
</pre><br />
<br />
If you wish to call a second animation after the first (rather than general code) then you can use a dojo animation chain (see tutorial referenced above).<br />
<br />
=== Moving elements ===<br />
<br />
'''this.placeOnObject( mobile_obj, target_obj )'''<br />
<br />
placeOnObject works exactly like "slideToObject", except that the effect is immediate.<br />
<br />
This is not really an animation, but placeOnObject is frequently used before starting an animation.<br />
<br />
Example:<br />
<pre><br />
// (We just created an object "my_new_token")<br />
<br />
// Place the new token on current player board<br />
this.placeOnObject( "my_new_token", "overall_player_board_"+this.player_id );<br />
<br />
// Then slide it to its position on the board<br />
this.slideToObject( "my_new_token", "a_place_on_board" ).play();<br />
</pre><br />
<br />
'''this.placeOnObjectPos( mobile_obj, target_obj, target_x, target_y )'''<br />
<br />
This method works exactly like placeOnObject, except than you can specify some (x,y) coordinates. This way, "mobile_obj" will be placed to the specified x,y position relatively to "target_obj".<br />
<br />
'''this.attachToNewParent( mobile_obj, target_obj )'''<br />
<br />
With this method, you change the HTML parent of "mobile_obj" element. "target_obj" is the new parent of this element. The beauty of <br />
attachToNewParent is that the mobile_obj element DOES NOT MOVE during this process.<br />
<br />
Note: what happens is that the method calculate a relative position of mobile_obj to make sure it does not move after the HTML parent changes.<br />
<br />
Why using this method?<br />
<br />
Changing the HTML parent of an element can be useful for the following reasons:<br />
* When the HTML parent moves, all its child are moving with them. If some game elements is no more linked with a parent HTML object, you may want to attach it to another place.<br />
* The z_order (vertical order of display) depends on the position in the DOM, so you may need to change the parent of some game elements when they are moving in your game area.<br />
<br />
CAREFUL: this function destroys original object and places a clone onto a new parent, this will break all references to this HTML element (ex: dojo.connect).<br />
<br />
== Players input ==<br />
<br />
<br />
'''dojo.connect'''<br />
<br />
Used to associate a player event with one of your notification methods.<br />
<br />
Example: associate a click on an element ("my_element") with one of our methods ("onClickOnMyElement"):<br />
<pre><br />
dojo.connect( $('my_element'), 'onclick', this, 'onClickOnMyElement' );<br />
</pre><br />
Same idea but base on query (i.e. all element of 'pet' class)<br />
dojo.query(".pet").connect('onclick', this, 'onPet');<br />
<br />
Note: the methods described here are the only correct ways to associate a player input event to your code, and you should not use anything else.<br />
<br />
'''this.connect'''<br />
function(element, event, handler)<br />
<br />
Used to associate a player event with one of your notification methods.<br />
<br />
this.connect( $('my_element'), 'onclick', 'onClickOnMyElement' );<br />
<br />
Or you can use an in-place handler<br />
<br />
this.connect( $('my_element'), 'onclick', (e) => { console.log('boo'); } );<br />
<br />
Note that this function stores the connection handler. That is the only real difference between '''this.connect''' and '''dojo.connect'''. If you plan to destroy the element you connected, you '''must''' call this.disconnect() to prevent memory leaks.<br />
<br />
<br />
'''this.connectClass'''<br />
function( cssClassName, event, handler )<br />
<br />
Same as connect(), but for all the nodes set with the specified cssClassName.<br />
<pre><br />
this.connectClass('pet', 'onclick', 'onPet');<br />
</pre><br />
'''this.disconnect'''<br />
function( element, event )<br />
<br />
Disconnect event handler (previously registered with this.connect or this.connectClass).<br />
<br />
this.disconnect( $('my_element'), 'onclick');<br />
<br />
Note: dynamic connect/disconnect is for advanced cases ONLY, you should always connect elements statically if possible, i.e. in setup() method.<br />
<br />
'''this.disconnectAll'''<br />
<br />
Disconnect all previously registed event handlers (registered via this.connect or this.connectClass)<br />
<br />
'''this.checkAction'''<br />
function( action, nomessage )<br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
<br />
return true if action is authorized (i.e: the action is listed as a "possibleaction" in current game state).<br />
<br />
return false and display an error message if not authorized (display no message if nomessage parameter is true). <br />
The displayed error message could be either "This move is not allowed at this moment" or "An action is already in progress".<br />
<br />
Example:<br />
<pre><br />
function onClickOnGameElement( evt ) {<br />
if( this.checkAction( "my_action" ) ) {<br />
// Do the action<br />
}<br />
}<br />
</pre><br />
<br />
'''this.checkPossibleActions'''<br />
function( action, nomessage )<br />
<br />
* this is independent of the player being active, so can be used instead of this.checkAction(). This is particularly useful for multiplayer states when the player is not active in a 'player may like to change their mind' scenario. <br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
<br />
'''this.ajaxcall( url, parameters, obj_callback, callback, callback_error )'''<br />
<br />
This method must be used to send a player input to the game server. '''It should not be triggered programmatically''', especially not in loops, in callbacks, in notifications, or in onEnteringState/onUpdateActionButtons/onLeavingState, in order not to create race conditions.<br />
<br />
* url: the url of the action to perform. For a game, it must be: "/<mygame>/<mygame>/myAction.html"<br />
* parameters: an array of parameter to send to the game server. <br />
** Note that "lock: true" must always be specified in this list of parameters in order the interface can be locked during the server call.<br />
** Note: Restricted parameter names (please don't use them):<br />
*** "action"<br />
*** "module"<br />
*** "class"<br />
* obj_callback: must be set to "this".<br />
* callback: a function to trigger when the server returns and everything went fine (not used, as all data handling is done via notifications).<br />
* callback_error: (optional and rarely used) a function to trigger when the server returns an error. if no error this function is called with parameter value false.<br />
<br />
Usage:<br />
<pre><br />
this.ajaxcall( '/mygame/mygame/myaction.html', { lock: true, <br />
arg1: myarg1, <br />
arg2: myarg2, <br />
...<br />
}, this, function( result ) {<br />
// Do some stuff after a successful call<br />
// NB : usually not needed as changes must be handled by notifications<br />
// You should NOT modify the interface in a callback or it will most likely break the framework replays (or make it inaccurate)<br />
// You should NOT make another ajaxcall in a callback in order not to create race conditions<br />
} );<br />
</pre><br />
<br />
Note: to reduce the boilerplate code you can define your own wrapper, which will do checking, locking and allow to skip parameters, for example<br />
<pre><br />
ajaxcallwrapper: function(action, args, handler) {<br />
if (!args) {<br />
args = [];<br />
}<br />
args.lock = true;<br />
<br />
if (this.checkAction(action)) {<br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", args,// <br />
this, (result) => { }, handler);<br />
}<br />
},<br />
</pre><br />
This can be called like this which is a lot more compact<br />
<pre><br />
this.ajaxcallwrapper('playDraw');<br />
this.ajaxcallwrapper('playMove', {card: id})<br />
</pre><br />
<br />
<br />
'''this.isInterfaceLocked()'''<br />
<br />
When using "lock: true" in ajax call you can use this function to check if interface is in lock state (it will be locked during server call and notification processing).<br />
This check can be used to block some other interactions which do not result in ajaxcall or if you want to suppress errors.<br />
<br />
<br />
'''this.addActionButton( id, label, method, (opt)destination, (opt)blinking, (opt)color )'''<br />
<br />
You can use this method to add an action button in the main action status bar.<br />
<br />
Arguments:<br />
* id: an element ID that should be unique in your HTML DOM document.<br />
* label: the text of the button. Should be translatable (use _() function).<br />
* method: the name of your method that must be triggered when the player clicks on this button.<br />
* destination (optional): deprecated, do not use this. Use '''null''' as value if you need to specify other arguments.<br />
* blinking (optional): if set to '''true''', the button is going blink to catch player's attention. Please don't abuse of blinking button.<br />
* color: could be '''blue''' (default), '''red''' or '''gray'''.<br />
<br />
You should only use this method in your "onUpdateActionButtons" method. Usually, you use it like this (from Hearts example):<br />
<br />
<pre><br />
onUpdateActionButtons: function( stateName, args ) {<br />
<br />
if (this.isCurrentPlayerActive()) { <br />
switch( stateName ) {<br />
case 'giveCards':<br />
this.addActionButton( 'giveCards_button', _('Give selected cards'), 'onGiveCards' ); <br />
break;<br />
}<br />
}<br />
}, <br />
</pre><br />
<br />
In the example above, we are adding a "Give selected cards" button in the case we are on game state "giveCards". When player clicks on this button, it triggers our "onGiveCards" method.<br />
<br />
Example using blinking red button:<br />
<pre><br />
this.addActionButton( 'commit_button', _('Confirm'), 'onConfirm', null, true, 'red'); <br />
</pre><br />
<br />
Note: at least in studio example above will make button huge, because it sets it display of blinking things to '''block''', <br />
if you don't like it you have to change css display value<br />
of the button to inline-block (the id of the button is the first argument, i.e 'commit_button' in example above)<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Notifications ==<br />
<br />
When something happens on the server side, your game interface Javascript logic received a notification.<br />
<br />
Here's how you can handle these notifications on the client side.<br />
<br />
=== Subscribe to notifications ===<br />
<br />
Your Javascript "setupNotifications" method is the place where you can subscribe to notifications from your PHP code.<br />
<br />
Here's how you associate one of your Javascript method to a notification "playDisc" (from Reversi example):<br />
<br />
<pre><br />
// In setupNotifications method:<br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
</pre><br />
<br />
Note: the "playDisc" corresponds to the name of the notification you define it in your PHP code, in your "notifyAllPlayers" or "notifyPlayer" method.<br />
<br />
Then, you have to define your "notif_playDisc" method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addDiscOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
In a notification handler like our "notif_playDisc" method, you can access to all notifications arguments with "notif.args".<br />
<br />
Example:<br />
<pre><br />
// If you did this on PHP side:<br />
self::notifyAllPlayers( "myNotification", '', array( "myArgument" => 3 ) );<br />
<br />
// On Javascript side, you can access the "myArgument" like this:<br />
notif_myNotification: function( notif )<br />
{<br />
alert( "myArgument = " + notif.args.myArgument );<br />
}<br />
</pre><br />
<br />
<br />
=== Synchronous notifications ===<br />
<br />
When several notifications are received by your game interface, these notifications are processed immediately, one after the other, in the same exact order they have been generated in your PHP game logic.<br />
<br />
However, sometimes, you need to give some time to the players to figure out what happened on the game before jumping to the next notification. Indeed, in many games, they are a lot of automatic actions, and the computer is going to resolve all these actions very fast if you don't tell it not to do so.<br />
<br />
As an example, for Reversi, when someone is playing a disc, we want to wait 500 milliseconds before doing anything else in order the opponent player can figure out what move has been played.<br />
<br />
Here's how we do this, right after our subscription:<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 ); // Wait 500 milliseconds after executing the playDisc handler<br />
</pre><br />
<br />
-----<br />
<br />
It is also possible to control the delay timing dynamically (e.g., using notification args). As an example, maybe your notification 'cardPlayed' should pause for a different amount of time depending on the number or type of cards played.<br />
<br />
For this case, use '''setSynchronous''' without specifying the duration and use '''setSynchronousDuration''' within the notification callback.<br />
<br />
* NOTE: If you forget to invoke '''setSynchronousDuration''', the game will remain paused forever!<br />
<br />
<pre><br />
setupNotifications: function () {<br />
dojo.subscribe( 'cardPlayed', this, 'notif_cardPlayed' );<br />
this.notifqueue.setSynchronous( 'cardPlayed' ); // wait time is dynamic<br />
...<br />
},<br />
<br />
notif_cardPlayed: function (notif) {<br />
// MUST call setSynchronousDuration<br />
<br />
// Example 1: From notification args (PHP)<br />
this.notifqueue.setSynchronousDuration(notif.args.duration);<br />
...<br />
<br />
// Or, example 2: Match the duration to a Dojo animation<br />
var anim = dojo.fx.combine([<br />
...<br />
]);<br />
anim.play();<br />
this.notifqueue.setSynchronousDuration(anim.duration);<br />
},<br />
</pre><br />
<br />
=== Pre-defined notification types ===<br />
<br />
'''tableWindow''' - This defines notification to display [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialogs]], see below.<br />
<br />
'''message''' - This defines notification that shows on players log and have no other effect<br />
<br />
// You can call this on php side without doing anything on client side<br />
self::notifyAllPlayers( 'message', 'hello', [] );<br />
<br />
'''simplePause''' - This notification will just delay other notifications, maybe useful if you know you need some extra time for animation or something. Requires a time parameter.<br />
<br />
<pre><br />
self::notifyAllPlayers( 'simplePause', '', [ 'time' => 500] ); // time is in milliseconds<br />
</pre><br />
<br />
== Tooltips ==<br />
<br />
'''this.addTooltip( nodeId, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to the DOM node.<br />
<br />
Specify 'helpString' to display some information about "what is this game element?".<br />
Specify 'actionString' to display some information about "what happens when I click on this element?".<br />
<br />
You must specify both helpString and actionString. Most of the time, you should use only one and specify a void string ("") for the other one.<br />
<br />
Usually, _() must be used for the text to be marked for translation.<br />
<br />
"Delay" is an optional parameter. Usually, it is primarily used to specify a zero delay for some game element when the tooltip gives really important information for the game - but remember: no essential information must be placed in tooltips as they won't be displayed in some browsers (see [[BGA_Studio_Guidelines|Guidelines]]).<br />
<br />
Example:<br />
<pre><br />
this.addTooltip( 'cardcount', _('Number of cards in hand'), '' );<br />
</pre><br />
<br />
'''this.addTooltipHtml( nodeId, html, delay )'''<br />
<br />
Add an HTML tooltip to the DOM node (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
'''this.addTooltipToClass( cssClass, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to all the DOM nodes set with this cssClass. <br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips.<br />
<br />
'''this.addTooltipHtmlToClass( cssClass, html, delay )'''<br />
<br />
Add an HTML tooltip to to all the DOM nodes set with this cssClass (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips<br />
<br />
'''this.removeTooltip( nodeId )'''<br />
<br />
Remove a tooltip from the DOM node.<br />
<br />
== Dialogs, warning messages, confirmation dialogs, ... ==<br />
<br />
=== Warning messages ===<br />
<br />
Sometimes, there is something important that is happening on the game and you have to make sure all players get the message. Most of the time, the evolution of the game situation or the game log is enough, but sometimes you need something more visible.<br />
<br />
Ex: someone fulfill one of the end of the game condition, so this is the last turn.<br />
<br />
'''this.showMessage( msg, type )'''<br />
<br />
showMessage shows a message in a big rectangular area on the top of the screen of current player.<br />
<br />
* "msg" is the string to display. It should be translated.<br />
* "type" can be set to "info" or "error". If set to "info", the message will be an informative message on a white background. If set to "error", the message will be an error message on a red background.<br />
<br />
Important: the normal way to inform players about the progression of the game is the game log. "showMessage" is intrusive and should not be used often.<br />
<br />
=== Confirmation dialog ===<br />
<br />
'''confirmationDialog( message, yesHandler, noHandler )'''<br />
<br />
<br />
When an important action with a lot of consequences is triggered by the player, you may want to propose a confirmation dialog.<br />
<br />
CAREFUL: the general guidelines of BGA is to AVOID the use of confirmation dialog. Confirmation dialogs slow down the game and bother players. The players knows that they have to pay attention about each move when they are playing online.<br />
<br />
The situation where you should use a confirmation dialog are the following:<br />
* It must not happen very often during a game.<br />
* It must be linked to an action that can really "kill a game" if the player do not pay attention.<br />
* It must be something that can be done by mistake (ex: a link on the action status bar).<br />
<br />
How to display a confirmation dialog:<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to bake the pie?'), dojo.hitch( this, function() {<br />
this.bakeThePie();<br />
} ) ); <br />
return; // nothing should be called or done after calling this, all action must be done in the handler <br />
</pre><br />
<br />
=== Multiple choice dialog ===<br />
You can use this dialog to give user a choice with small amount of options:<br />
<pre><br />
var keys = [1,5,10];<br />
this.multipleChoiceDialog(<br />
_('How many bugs to fix?'), keys, <br />
dojo.hitch(this, function(choice) {<br />
var bugchoice = keys[choice];<br />
console.log('dialog callback with '+bugchoice);<br />
this.ajaxcall( '/mygame/mygame/fixBugs.html', { bugs: bugchoice}, this, function( result ) {} ); }));<br />
</pre><br />
<br />
=== Dialogs ===<br />
<br />
As a general rule, you shouldn't use dialogs windows.<br />
<br />
BGA guidelines specify that all game elements should be displayed on the main screen. Players can eventually scroll down to see game elements they don't need to see anytime, and you may eventually create anchors to move between game area section. Of course dialogs windows are very practical, but the thing is: all players know how to scroll down, and not all players know how to show up your dialog window. In addition, when the dialog shows up, players can't access the other game components.<br />
<br />
Sometimes although, you need to display a dialog window. Here is how you do this:<br />
<br />
<br />
<br />
// Create the new dialog over the play zone. You should store the handler in a member variable to access it later<br />
this.myDlg = new ebg.popindialog();<br />
this.myDlg.create( 'myDialogUniqueId' );<br />
this.myDlg.setTitle( _("my dialog title to translate") );<br />
this.myDlg.setMaxWidth( 500 ); // Optional<br />
<br />
// Create the HTML of my dialog. <br />
// The best practice here is to use [[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]<br />
var html = this.format_block( 'jstpl_myDialogTemplate', { <br />
arg1: myArg1,<br />
arg2: myArg2,<br />
...<br />
} ); <br />
<br />
// Show the dialog<br />
this.myDlg.setContent( html ); // Must be set before calling show() so that the size of the content is defined before positioning the dialog<br />
this.myDlg.show();<br />
<br />
// Now that the dialog has been displayed, you can connect your method to some dialog elements<br />
// Example, if you have an "OK" button in the HTML of your dialog:<br />
dojo.connect( $('my_ok_button'), 'onclick', this, function(evt){<br />
evt.preventDefault();<br />
this.myDlg.destroy();<br />
} );<br />
<br />
If necessary, you can remove the default top right corner 'close' icon, or replace the function called when it is clicked:<br />
// Removes the default close icon<br />
this.myDlg.hideCloseIcon();<br />
<br />
// Replace the function call when it's clicked<br />
this.myDlg.replaceCloseCallback( function() { ... } );<br />
<br />
=== Scoring dialogs ===<br />
<br />
Sometimes at the end of a round you want to display a big table that details the points wins in each section of the game.<br />
<br />
Example: in Hearts game, we display at the end of each round the number of "heart" cards collected by each player, the player who collected the Queen of Spades, and the total number of points loose by each player.<br />
<br />
Scoring dialogs are managed entirely on '''PHP side''', but they are described here as their effects are visible only on client side.<br />
<br />
Displaying a scoring dialog is quite simple and is using a special notification type: "tableWindow":<br />
<pre><br />
// on PHP side:<br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table<br />
) ); <br />
</pre><br />
<br />
The "table" argument is a 2 dimensional PHP array that describe the table you want to display, line by line and column by column.<br />
<br />
Example: display an 3x3 array of strings<br />
<pre><br />
$table = array(<br />
array( "one", "two", "three" ), // This is my first line<br />
array( "four", "five", "six" ), // This is my second line<br />
array( "seven", "height", "nine" ) // This is my third line<br />
);<br />
</pre><br />
<br />
As you can see above, in each "cell" of your array you can display a simple string value. But you can also display a complex value with a template and associated arguments like this:<br />
<pre><br />
$table = array(<br />
array( "one", "two", array( "str" => clienttranslate("a string with an ${argument}"), "args" => array( 'argument' => 'argument_value' ) ) ),<br />
array( "four", "five", "six" ), <br />
array( "seven", "height", "nine" )<br />
);<br />
</pre><br />
<br />
This is especially useful when you want to display player names with colors. Example from "Hearts":<br />
<pre><br />
$firstRow = array( '' );<br />
foreach( $players as $player_id => $player )<br />
{<br />
$firstRow[] = array( 'str' => '${player_name}',<br />
'args' => array( 'player_name' => $player['player_name'] ),<br />
'type' => 'header'<br />
);<br />
}<br />
$table[] = $firstRow;<br />
</pre><br />
<br />
You can also use three extra attributes in the parameter array for the notification:<br />
<br />
<pre><br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table,<br />
"header" => array('str' => clienttranslate('Table header with parameter ${number}'),<br />
'args' => array( 'number' => 3 ),<br />
),<br />
"footer" => '<div>Some footer</div>',<br />
"closing" => clienttranslate( "Closing button label" )<br />
) ); <br />
</pre><br />
<br />
*'''header''': the content for this parameter will display before the table (also, the html will be parsed and player names will be colored according to the current game colors). <br />
*'''footer''': the content for this parameter will display after the table (no parsing for coloring the player names)<br />
*'''closing''': if this parameter is used, a button will be displayed with this label at the bottom of the popup and will allow players to close it (more easily than by clicking the top right 'cross' icon).<br />
<br />
=== Scoring animated display ===<br />
<br />
Sometimes (Terra Mystica final scoring for example), you may want to display a score value over an element to make the scoring easier to follow for the players.<br />
You can do it with:<br />
<br />
<pre><br />
this.displayScoring( anchor_id, color, score, duration, offset_x, offset_y );<br />
</pre><br />
<br />
'''anchor_id''': ID of the element to place the animated score onto (without the '#') <br />
<br />
'''color''': hexadecimal RGB representation of the color (should be the color of the scoring player), but without a leading '#'. For instance, 'ff0000' for red.<br />
<br />
'''score''': numeric score to display, prefixed by a '+'<br />
<br />
'''duration''': animation duration in milliseconds<br />
<br />
'''offset_x''' and '''offset_y''': if both offset_x and offset_y are defined and not null, apply the following offset (in pixels) to the scoring animation<br />
<br />
<br />
Note: if you want to display successively each score, you can use ''this.notifqueue.setSynchronous()'' function.<br />
<br />
=== Speech bubble ===<br />
<br />
For better interactivity in some games (Love Letter for example), you may use comic book style speech bubbles to express the players voices.<br />
This is done with:<br />
<br />
<pre><br />
this.showBubble(anchor_id, text, delay, duration, custom_class)<br />
</pre><br />
<br />
text - what to put in bubble, can be html actually not just text<br />
<br />
delay - in milliseconds is optional (default 0)<br />
<br />
duration - in milliseconds is optional (default 3000)<br />
<br />
custom_class - extra class to add to bubble is optional, if you need to override the default bubble style<br />
<br />
'''Warning''': if your bubble could overlap other active elements of the interface (buttons in particular), as it stays in place even after disappearing, you should use a custom class to give it the style "pointer-events: none;" in order to intercept click events.<br />
<br />
Note: If you want this visually, but want to take complete control over this bubble and its animation (for example to make it permanent) you can just use div with 'discussion_bubble' class on it, and content of div is what will be shown.<br />
<br />
== Update players score ==<br />
<br />
The column player_score from the player table is automatically loaded into this.scoreCtrl and therefore into the stars location on the player board. This occurs sometime after the <gamename>.js setup() function. However this score must be updated as the game progresses through player notifications (notifs).<br />
<br />
<br />
Increase a player score (with a positive or negative number):<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].incValue( score_delta );<br />
</pre><br />
<br />
Set a player score to a specific value:<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].setValue( new_score );<br />
</pre><br />
<br />
Set a player score to a specific value with animation :<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].toValue( new_score );<br />
</pre><br />
<br />
Typical usage would be (that will process 'score' notification):<br />
<pre><br />
setupNotifications : function() {<br />
...<br />
dojo.subscribe('score', this, "notif_score");<br />
},<br />
notif_score: function(notif) {<br />
this.scoreCtrl[notif.args.player_id].setValue(notif.args.player_score);<br />
},<br />
</pre><br />
<br />
== Players panels ==<br />
<br />
=== Adding stuff to player's panel ===<br />
<br />
At first, create a new "JS template" string in your template (tpl) file:<br />
<br />
(from Gomoku example)<br />
<pre><br />
var jstpl_player_board = '\<div class="cp_board">\<br />
<div id="stoneicon_p${id}" class="gmk_stoneicon gmk_stoneicon_${color}"></div><span id="stonecount_p${id}">0</span>\<br />
</div>';<br />
</pre><br />
<br />
Then, you add this piece of code in your JS file to add this template to each player panel:<br />
<br />
<pre><br />
// Setting up player boards<br />
for( var player_id in gamedatas.players )<br />
{<br />
var player = gamedatas.players[player_id];<br />
<br />
// Setting up players boards if needed<br />
var player_board_div = $('player_board_'+player_id);<br />
dojo.place( this.format_block('jstpl_player_board', player ), player_board_div );<br />
}<br />
</pre><br />
<br />
(Note: the code above is of course from your "setup" function in your Javascript).<br />
<br />
Very often, you have to distinguish current player and others players. In this case, you just have to create another JS template (ex: jstpl_otherplayer_board) and use it when "player_id" is different than "this.player_id".<br />
<br />
=== Player's panel disabling/enabling ===<br />
<br />
'''this.disablePlayerPanel( player_id )'''<br />
<br />
Disable given player panel (the panel background become gray).<br />
<br />
Usually, this is used to signal that this played passes, or will be inactive during a while.<br />
<br />
Note that the only effect of this is visual. There are no consequences on the behaviour of the panel itself.<br />
<br />
'''this.enablePlayerPanel( player_id )'''<br />
<br />
Enable a player panel that has been disabled before.<br />
<br />
'''this.enableAllPlayerPanels()'''<br />
<br />
Enable all player panels that has been disabled before.<br />
<br />
== Image loading ==<br />
<br />
See also [[Game_art:_img_directory]].<br />
<br />
'''Be careful''': by default, ALL images of your img directory are loaded on a player's browser when he loads the game. For this reason, don't let in your img directory images that are not useful, otherwise it's going to slowdown the game load.<br />
<br />
'''dontPreloadImage( image_file_name )'''<br />
<br />
Using dontPreloadImage, you tell the interface to not preload a specific image in your img directory.<br />
<br />
Example of use:<br />
<pre><br />
this.dontPreloadImage( 'cards.png' );<br />
</pre><br />
<br />
This is particularly useful if for example you have 2 different themes for a game. To accelerate the loading of the game, you can specify to not preload images corresponding to the other theme.<br />
<br />
Another example of use: in "Gosu" game with Kamakor extension, you play with 5 sets of cards among 10 available. Cards images are organized by sets, and we only preload the images corresponding to the 5 current sets with '''ensureSpecificGameImageLoading( image_file_names_array )'''.<br />
<pre><br />
// By default, do not preload anything<br />
this.dontPreloadImage( 'cards.png' );<br />
this.dontPreloadImage( 'clan1.png' );<br />
this.dontPreloadImage( 'clan2.png' );<br />
this.dontPreloadImage( 'clan3.png' );<br />
this.dontPreloadImage( 'clan4.png' );<br />
this.dontPreloadImage( 'clan5.png' );<br />
this.dontPreloadImage( 'clan6.png' );<br />
this.dontPreloadImage( 'clan7.png' );<br />
this.dontPreloadImage( 'clan8.png' );<br />
this.dontPreloadImage( 'clan9.png' );<br />
this.dontPreloadImage( 'clan10.png' );<br />
var to_preload = [];<br />
for( i in this.gamedatas.clans )<br />
{<br />
var clan_id = this.gamedatas.clans[i];<br />
to_preload.push( 'clan'+clan_id+'.png' );<br />
}<br />
if( to_preload.length == 5 )<br />
{<br />
this.ensureSpecificGameImageLoading( to_preload );<br />
}<br />
</pre><br />
<br />
'''Note:''' You don't need to specify to not preload game box images (game_box.png, game_box75.png...) since they are not preloaded by default.<br />
<br />
== Other useful stuff ==<br />
<br />
<br />
'''dojo.hitch'''<br />
<br />
With dojo.hitch, you can create a callback function that will run with your game object context whatever happen.<br />
<br />
Typical example: display a BGA confirmation dialog with a callback function created with dojo.hitch:<br />
<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), dojo.hitch( this, function() {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, function( result ) {} );<br />
} ) ); <br />
</pre><br />
<br />
In the example above, using dojo.hitch, we ensure that the "this" object will be set when the callback is called.<br />
<br />
NOTE: In modern JS there are lambdas that eliminate need for that, the example above will look like this<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), () => {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, (result) => {} );<br />
} ); <br />
</pre><br />
<br />
; updateCounters(counters)<br />
: Useful for updating game counters in the player panel (such as resources). <br />
: 'counters' arg is an associative array [counter_name_value => [ 'counter_name' => counter_name_value, 'counter_value' => counter_value_value], ... ]<br />
: All counters must be referenced in this.gamedatas.counters and will be updated.<br />
: DOM objects referenced by 'counter_name' will have their innerHTML updated with 'counter_value'.<br />
<br />
<br />
;'''onScreenWidthChange()'''<br />
:This function can be overridden in your game to manage some resizing on the client side when the browser window is resized. This function is also triggered at load time, so it can be used to adapt to the :viewport size at the start of the game too.<br />
<br />
<br />
;'''updatePageTitle()'''<br />
:This function allows to update the current page title and turn description according to the game state. If the current game state description this.gamedatas.gamestate.descriptionmyturn is modified before :calling the function, it allows to update the turn description without changing state.<br />
<br />
Example from Terra Mystica:<br />
<pre><br />
onClickFavorTile: function( evt ) {<br />
...<br />
if ( ... ) {<br />
this.gamedatas.gamestate.descriptionmyturn = _('Special action: ') + _('Advance 1 space on a Cult track');<br />
this.updatePageTitle();<br />
this.removeActionButtons();<br />
this.addActionButton( ... );<br />
...<br />
return;<br />
}<br />
...<br />
<br />
}<br />
</pre><br />
<br />
== BGA GUI components ==<br />
<br />
BGA framework provides some useful ready-to-use components for the game interface:<br />
<br />
[[Studio#BGA_Studio_game_components_reference]]<br />
<br />
Note that each time you are using an additional component, you must declare it at the top of your Javascript file in the list of modules used.<br />
<br />
Example if you are using "ebg.stock":<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <=== we are using ebg.stock module<br />
],<br />
</pre><br />
<br />
== BGA Buttons ==<br />
<br />
You can create a custom button, but the BGA framework provides a standard button that requires only .css classes: '''bgabutton''' and '''bgabutton_${color}'''.<br />
<br />
'''Examples:'''<br />
<br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_blue"><span>My blue button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_gray"><span>My gray button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_red"><span>My red button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_red bgabutton_big"><span>My big red button</span></a><br />
</pre><br />
<br />
<br />
'''Note''': To see it in action, check out ''Coloretto''.<br />
<br />
== Sounds ==<br />
<br />
Add a custom sound and make it load with your interface:<br />
<br />
Add this in your template (.tpl) file:<br />
<pre><br />
<audio id="audiosrc_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<gamename>_<yoursoundname>.mp3" preload="none" autobuffer></audio><br />
<audio id="audiosrc_o_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<gamename>_<yoursoundname>.ogg" preload="none" autobuffer></audio><br />
</pre><br />
<br />
Note: this is a requirement to provide both a mp3 and a ogg file.<br />
<br />
Play the sound (from your .js file):<br />
<br />
<pre><br />
playSound('<gamename>_<yoursoundname>'); <br />
</pre><br />
<br />
<br />
Disable the standard "move" sound for this move (to replace it with your custom sound):<br />
<br />
Add this to your notification handler:<br />
<br />
<pre><br />
this.disableNextMoveSound();<br />
</pre><br />
<br />
Note: it only disable the sound for the next move.</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Game_interface_logic:_yourgamename.js&diff=6817
Game interface logic: yourgamename.js
2021-01-09T16:40:21Z
<p>Cpasbanal: /* Players input */ fixed typo</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game interface. Here you will define:<br />
<br />
* Which actions on the page will generate calls to the server.<br />
* What happens when you get a notification for a change from the server and how it will show in the browser. <br />
<br />
== File structure ==<br />
<br />
The details of how the file is structured are described below with comments on the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* '''constructor''': here you can define global variables for your whole interface.<br />
* '''setup''': this method is called when the page is refreshed, and sets up the game interface.<br />
* '''onEnteringState''': this method is called when entering a new game state. You can use it to customize the view for each game state.<br />
* '''onLeavingState''': this method is called when leaving a game state.<br />
* '''onUpdateActionButtons''': called when entering a new state, in order to add action buttons to the status bar.<br />
* ''(utility methods)'': this is where you can define your utility methods.<br />
* ''(player's actions)'': this is where you can write your handlers for player actions on the interface (example: click on an item).<br />
* '''setupNotifications''': this method associates notifications with notification handlers. For each game notification, you can trigger a javascript method to handle it and update the game interface.<br />
* ''(notification handlers)'': this is where you define the notifications handlers associated with notifications in '''setupNotifications''', above.<br />
<br />
<br />
More details:<br />
<br />
<br />
;onEnteringState(stateName, args)<br />
This method is called each time we are entering into a new game state.<br />
You can use this method to perform some user interface changes at this moment.<br />
To access state arguments passed via calling arg* method use args.args.<br />
Typically you would do something only for active player, using this.isCurrentPlayerActive() check.<br />
<br />
'''Warning''': for multipleactiveplayer states:<br />
the active players are NOT actives yet so you must use onUpdateActionButtons to perform the client side operation which depends on a player active/unactive status.<br />
If you are doing initialization of some structure which do not depend on active player, you can just replace <br />
if (this.isCurrentPlayerActive()) {<br />
into<br />
if (!this.isSpectator) {<br />
for the main switch in that method.<br />
<br />
;onLeavingState(stateName)<br />
This method is called each time we are leaving a game state.<br />
You can use this method to perform some user interface changes at this moment.<br />
<br />
;onUpdateActionButtons(stateName, args)<br />
In this method you can manage "action buttons" that are displayed in the action status bar.<br />
To access state arguments passed via calling arg* method use args parameter. Note: args can be null! For game states and when you don't supply state args function it is null.<br />
This method is called when active or multiactive player changes. In classic "activePlayer" state this method is called before the onEnteringState state.<br />
In multipleactiveplayer state it is a mess. The sequencing of call would depends on either you get into that from transition from reloading the whole game (i.e. F5).<br />
<br />
See more details in [[Your_game_state_machine:_states.inc.php#Diffrence_between_Single_active_and_Multi_active_states]]<br />
<br />
== General tips ==<br />
<br />
; this.player_id<br />
: ID of the player on whose browser the code is running.<br />
<br />
; this.isSpectator<br />
: Flag set to true if the user at the table is a spectator (not a player).<br />
: Note: This is a variable, not a function.<br />
: Note: If you want to hide an element from spectators, you should use [[Game_interface_stylesheet:_yourgamename.css#spectatorMode|CSS 'spectatorMode' class]].<br />
<br />
; this.gamedatas<br />
: Contains the initial set of data to init the game, created at game start or by game refresh (F5).<br />
: You can update it as needed to keep an up-to-date reference of the game on the client side if you need it. (Most of the time this is unnecessary).<br />
<br />
; this.isCurrentPlayerActive()<br />
: Returns true if the player on whose browser the code is running is currently active (it's his turn to play).<br />
<br />
; this.getActivePlayerId()<br />
: Return the ID of the active player, or null if we are not in an "activeplayer" type state.<br />
<br />
; this.getActivePlayers()<br />
: Return an array with the IDs of players who are currently active (or an empty array if there are none).<br />
<br />
; this.bRealtime<br />
: Return true if the game is in realtime. Note that having a distinct behavior in realtime and turn-based should be exceptional.<br />
<br />
; typeof g_replayFrom != 'undefined'<br />
: Returns true if the game is in instant replay mode (replay during the game)<br />
<br />
; g_archive_mode<br />
: Returns true if the game is in archive mode (advanced replay after the game has ended)<br />
<br />
---<br />
<br />
You may consider making a function like this, to detect if the game is in a read-only state:<br />
<br />
// Returns true for spectators, instant replay (during game), archive mode (after game end)<br />
isReadOnly: function () {<br />
return this.isSpectator || typeof g_replayFrom != 'undefined' || g_archive_mode;<br />
}<br />
<br />
== Dojo framework ==<br />
<br />
BGA uses the [http://dojotoolkit.org/ Dojo Javascript framework].<br />
<br />
The Dojo framework allows us to do complex things more easily. The BGA framework uses Dojo extensively.<br />
<br />
To implement a game, you only need to use a few parts of the Dojo framework. All the Dojo methods you need are described on this page.<br />
<br />
== Javascript minimization (before July 2020) ==<br />
<br />
For performance reasons, when deploying a game the js code is minimized using ShrinkSafe (based on ECMASCRIPT version 3). Some advanced syntax may not be compatible with this process. In particular:<br />
* You should not use reserved keywords from the javascript language as variables.<br />
* You should not declare default argument values in function declarations. The following syntax is invalid for ShrinkSafe: '''function myFunc(requiredArg, optionalArg = 'defaultValue') {}'''<br />
* You should not use '''let''' or '''const''' to declare variables.<br />
<br />
'''Tip:''' a developer encountering some problems with this has successfully used [http://plugins.netbeans.org/plugin/58580/jshint JSHint] on NetBeans to evaluate code to make it compatible for ECMAScript 3. With the plugin installed, set the below options in '''.jshintrc''' file, and then open '''Action Items''' window (in NetBeans):<pre>{ "maxerr": 999, "esversion": 3 }</pre><br />
<br />
'''Tip:''' some online tools also allow to convert between different versions of javascript, such as https://www.typescriptlang.org/play or https://babeljs.io/ or https://extendsclass.com/javascript-fiddle.html<br />
<br />
== Javascript minimization (after July 2020) ==<br />
<br />
For performance reasons, when deploying a game the javascript code is minimized using '''terser''' (https://github.com/terser/terser). This minifier works with modern javascript syntax. From your project "Manage game" page, you can now test a minified version of your javascript on the studio (and revert to the original).<br />
<br />
NB: it has been reported that there is an issue with this minifier and percentage values for opacity.<br />
<br />
== Accessing and manipulating the DOM ==<br />
<br />
'''$('some_html_element_id')'''<br />
<br />
The $() function is used to get an HTML element using its "id" attribute.<br />
<br />
Example 1: modify the content of a "span" element:<br />
<br />
<pre><br />
In your HTML code:<br />
<span id="a_value_in_the_game_interface">1234</span><br />
<br />
In your Javascript code:<br />
$('a_value_in_the_game_interface').innerHTML = "9999";<br />
</pre><br />
<br />
Note: $() is the standard method to access some HTML element with the BGA Framework. You should not use the '''getElementById''' function.<br />
<br />
<br />
'''dojo.style'''<br />
<br />
With dojo.style you can modify the CSS property of any HTML element in your interface.<br />
<br />
Examples:<br />
<br />
<pre><br />
// Make an element disappear<br />
dojo.style( 'my_element', 'display', 'none' );<br />
<br />
// Give an element a 2px border<br />
dojo.style( 'my_element', 'borderWidth', '2px' );<br />
<br />
// Change the background position of an element<br />
// (very practical when you are using CSS sprites to transform an element to another)<br />
dojo.style( 'my_element', 'backgroundPosition', '-20px -50px' );<br />
</pre><br />
<br />
Note: you must always use dojo.style to modify the CSS properties of HTML elements.<br />
<br />
NoteÂČ: if you have to modify several CSS properties of an element, or if you have a complex CSS transformation to do, you should consider using dojo.addClass/dojo.removeClass (see below).<br />
<br />
<br />
'''dojo.addClass''', '''dojo.removeClass''', '''dojo.hasClass''', '''dojo.toggleClass'''<br />
<br />
In many situations, many small CSS property updates can be replaced by a CSS class change (i.e., you add a CSS class to your element instead of applying all modifications manually).<br />
<br />
Advantages are:<br />
* All your CSS stuff remains in your CSS file.<br />
* You can add/remove a list of CSS modifications with a simple function and without error.<br />
* You can test whether you applied the CSS to an element with the '''dojo.hasClass''' method.<br />
<br />
Example from ''Reversi'':<br />
<br />
<pre><br />
// We add "possibleMove" to an element<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
<br />
// In our CSS file, the class is defined as:<br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
<br />
// So we've applied 4 CSS property changes in one line of code.<br />
<br />
// ... and when we need to check if a square is a possible move on the client side:<br />
if( dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{ ... }<br />
<br />
// ... and if we want to remove all possible moves in one line of code (see "dojo.query" method):<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
</pre><br />
<br />
Conclusion: We encourage you to use '''dojo.addClass''', '''dojo.removeClass''' and '''dojo.hasClass''' to make your life easier :)<br />
<br />
'''dojo.attr'''<br />
<br />
With dojo.attr you can access or change the value of an attribute or property of any HTML element in your interface.<br />
<br />
Exemple:<br />
<pre><br />
// Get the title of a node<br />
var title = dojo.attr( id, 'title' );<br />
// Change the height of a node<br />
dojo.attr( 'img_growing_tree', 'height', 100 );<br />
</pre><br />
<br />
'''dojo.query'''<br />
<br />
With dojo.query, you can query a bunch of HTML elements with a single function, with a "CSS selector" style.<br />
<br />
Example:<br />
<pre><br />
// All elements with class "possibleMove":<br />
var elements = dojo.query( '.possibleMove' );<br />
<br />
// Count number of tokens (i.e., elements of class "token") on the board (i.e., the element with id "board"):<br />
dojo.query( '#board .token' ).length;<br />
</pre><br />
<br />
But what is really cool with dojo.query is that you can combine it with almost all methods above.<br />
<br />
Examples:<br />
<pre><br />
// Trigger a method when the mouse enter in any element with class "meeple":<br />
dojo.query( '.meeple' ).connect( 'onmouseenter', this, 'myMethodToTrigger' );<br />
<br />
// Hide all meeples who are on the board<br />
dojo.query( '#board .meeple' ).style( 'display', 'none' );<br />
</pre><br />
<br />
'''dojo.place'''<br />
<br />
dojo.place is the best function to insert HTML code somewhere in your game interface without breaking something. It is much better to use than the '''innerHTML=''' method if you must insert HTML tags and not only values.<br />
<br />
<pre><br />
// Insert your HTML code as a child of a container element<br />
dojo.place( "<your html code>", "your_container_element_id" );<br />
<br />
// Replace the container element with your new html<br />
dojo.place( "<your html code>", "your_container_element_id", "replace" );<br />
<br />
</pre><br />
<br />
The third parameter of dojo.place can take various interesting values:<br />
<br />
"replace" : (see description above).<br />
<br />
"first" : Places the node as a child of the reference node. The node is placed as the first child.<br />
<br />
"last" (default) : Places the node as a child of the reference node. The node is placed as the last child.<br />
<br />
"before" : places the node right before the reference node.<br />
<br />
"after" : places the node right after the reference node.<br />
<br />
"only" : replaces all children of the reference node with the node.<br />
<br />
positif integer : This parameter can be a positif integer. In this case, the node will be placed as a child of the reference node with this number (counting from 0). If the number is more than number of children, the node will be appended to the reference node making it the last child. <br />
<br />
See also full doc on dojo.place : [https://dojotoolkit.org/reference-guide/1.7/dojo/place.html]<br />
<br />
Usually, when you want to insert some piece of HTML in your game interface, you should use "[[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]".<br />
<br />
'''dojo.empty'''<br />
<br />
Remove all children of the node element<br />
<br />
'''dojo.destroy'''<br />
<br />
Remove the element<br />
<br />
dojo.query(".green", mynode).forEach(dojo.destroy); // this remove all subnode of class green from mynode<br />
<br />
'''dojo.create'''<br />
<br />
Create element<br />
<br />
dojo.create("div", { class: "yellow_arrow" }, parent); // this creates div with class yellow_array and places it in "parent"<br />
<br />
'''addStyleToClass: function( cssClassName, cssProperty, propertyValue )'''<br />
<br />
Same as dojo.style(), but for all the nodes set with the specified cssClassName<br />
<br />
=== Animations ===<br />
<br />
'''Dojo Animations'''<br />
<br />
BGA animations is based on Dojo Animation ([http://dojotoolkit.org/documentation/tutorials/1.8/animation/ see tutorial here]).<br />
<br />
However, most of the time, you can just use methods below, which are built on top of Dojo Animation.<br />
<br />
Note: one interesting method from Dojo that could be useful from time to time is "Dojo.Animation". It allows you to make any CSS property "slide" from one value to another.<br />
<br />
Note 2: the slideTo methods are not compatible with CSS transform (scale, zoom, rotate...). If possible, avoid using CSS transform on nodes that are being slided. Eventually, the only possible solution to make these 2 compatible is to disable all CSS transform properties, use slideToObjectPos/placeOnObjectPos, and then apply them again.<br />
<br />
<br />
'''this.slideToObject( mobile_obj, target_obj, duration, delay )'''<br />
<br />
You can use slideToObject to "slide" an element to a target position.<br />
<br />
Sliding element on the game area is the recommended and the most used way to animate your game interface. Using slides allow players to figure out what is happening on the game, as if they were playing with the real boardgame.<br />
<br />
The parameters are:<br />
* mobile_obj: the ID of the object to move. This object must be "relative" or "absolute" positioned.<br />
* target_obj: the ID of the target object. This object must be "relative" or "absolute" positioned. Note that it is not mandatory that mobile_obj and target_obj have the same size. If their size are different, the system slides the center of mobile_obj to the center of target_obj.<br />
* duration: (optional) defines the duration in millisecond of the slide. The default is 500 milliseconds.<br />
* delay: (optional). If you defines a delay, the slide will start only after this delay. This is particularly useful when you want to slide several object from the same position to the same position: you can give a 0ms delay to the first object, a 100ms delay to the second one, a 200ms delay to the third one, ... this way they won't be superposed during the slide.<br />
<br />
BE CAREFUL: The method returns an dojo.fx animation, so you can combine it with other animation if you want to. It means that you have to call the "play()" method, otherwise the animation WON'T START.<br />
<br />
Example:<br />
<pre><br />
this.slideToObject( "some_token", "some_place_on_board" ).play();<br />
</pre><br />
<br />
<br />
'''this.slideToObjectPos( mobile_obj, target_obj, target_x, target_y, duration, delay )'''<br />
<br />
This method does exactly the same as "slideToObject", except than you can specify some (x,y) coordinates. This way, "mobile_obj" will slide to the specified x,y position relatively to "target_obj".<br />
<br />
Example: slide a token to some place on the board, 10 pixels to the bottom:<br />
<pre><br />
this.slideToObjectPos( "some_token", "some_place_on_board", 0, 10 ).play();<br />
</pre><br />
<br />
'''this.slideTemporaryObject( mobile_obj_html, mobile_obj_parent, from, to, duration, delay )'''<br />
<br />
This method is useful when you want to slide a temporary HTML object from one place to another. As this object does not exists before the animation and won't remain after, it could be complex to create this object (with dojo.place), to place it at its origin (with placeOnObject) to slide it (with slideToObject) and to make it disappear at the end.<br />
<br />
slideTemporaryObject does all of this for you:<br />
* mobile_obj_html is a piece of HTML code that represent the object to slide.<br />
* mobile_obj_parent is the ID of an HTML element of your interface that will be the parent of this temporary HTML object.<br />
* from is the ID of the origin of the slide.<br />
* to is the ID of the target of the slide.<br />
* duration/delay works exactly like in "slideToObject"<br />
<br />
Example:<br />
<pre><br />
this.slideTemporaryObject( '<div class="token_icon"></div>', 'tokens', 'my_origin_div', 'my_target_div' );<br />
</pre><br />
<br />
'''this.slideToObjectAndDestroy: function( node, to, time, delay )'''<br />
<br />
This method is a handy shortcut to slide an existing HTML object to some place then destroy it upon arrival. It can be used for example to move a victory token or a card from the board to the player panel to show that the player earns it, then destroy it when we don't need to keep it visible on the player panel.<br />
<br />
It works the same as this.slideToObject and takes the same arguments. <br />
<br />
Example:<br />
<pre><br />
this.slideToObjectAndDestroy( "some_token", "some_place_on_board", 1000, 0 );<br />
</pre><br />
<br />
'''this.fadeOutAndDestroy( node, duration, delay )'''<br />
<br />
This function fade out the target HTML node, then destroy it.<br />
<br />
Example:<br />
<pre><br />
this.fadeOutAndDestroy( "a_card_that_must_disappear" );<br />
</pre><br />
<br />
CAREFUL: the HTML node still exists until during few milliseconds, until the fadeOut has been completed.<br />
<br />
'''Rotating elements'''<br />
<br />
You can check here [http://jimfulton.info/demos/dojo-animated-rotate.html an example of use] of Dojo to make an element rotate.<br />
<br />
This example combines "Dojo.Animation" method and a CSS3 property that allow you to rotate the element.<br />
<br />
IMPORTANT: to asses browser compatibility, you must select the CSS property to use just like in the example (see sourcecode below):<br />
<br />
<pre><br />
var transform;<br />
dojo.forEach(<br />
['transform', 'WebkitTransform', 'msTransform',<br />
'MozTransform', 'OTransform'],<br />
function (name) {<br />
if (typeof dojo.body().style[name] != 'undefined') {<br />
transform = name;<br />
}<br />
});<br />
// ... and then use "transform" as the name of your CSS property for rotation<br />
</pre><br />
<br />
'''Animation Callbacks'''<br />
<br />
If you wish to run some code only after an animation has completed you can do this by linking a callback method.<br />
<br />
<pre><br />
var animation_id = this.slideToObject( mobile_obj, target_obj, duration, delay );<br />
dojo.connect(animation_id, 'onEnd', dojo.hitch(this, 'callback_function', parameters));<br />
animation_id.play();<br />
<br />
âŠ<br />
<br />
callback_function: function(params) {<br />
// this will be called after the animation ends<br />
},<br />
</pre><br />
<br />
If you wish to call a second animation after the first (rather than general code) then you can use a dojo animation chain (see tutorial referenced above).<br />
<br />
=== Moving elements ===<br />
<br />
'''this.placeOnObject( mobile_obj, target_obj )'''<br />
<br />
placeOnObject works exactly like "slideToObject", except that the effect is immediate.<br />
<br />
This is not really an animation, but placeOnObject is frequently used before starting an animation.<br />
<br />
Example:<br />
<pre><br />
// (We just created an object "my_new_token")<br />
<br />
// Place the new token on current player board<br />
this.placeOnObject( "my_new_token", "overall_player_board_"+this.player_id );<br />
<br />
// Then slide it to its position on the board<br />
this.slideToObject( "my_new_token", "a_place_on_board" ).play();<br />
</pre><br />
<br />
'''this.placeOnObjectPos( mobile_obj, target_obj, target_x, target_y )'''<br />
<br />
This method works exactly like placeOnObject, except than you can specify some (x,y) coordinates. This way, "mobile_obj" will be placed to the specified x,y position relatively to "target_obj".<br />
<br />
'''this.attachToNewParent( mobile_obj, target_obj )'''<br />
<br />
With this method, you change the HTML parent of "mobile_obj" element. "target_obj" is the new parent of this element. The beauty of <br />
attachToNewParent is that the mobile_obj element DOES NOT MOVE during this process.<br />
<br />
Note: what happens is that the method calculate a relative position of mobile_obj to make sure it does not move after the HTML parent changes.<br />
<br />
Why using this method?<br />
<br />
Changing the HTML parent of an element can be useful for the following reasons:<br />
* When the HTML parent moves, all its child are moving with them. If some game elements is no more linked with a parent HTML object, you may want to attach it to another place.<br />
* The z_order (vertical order of display) depends on the position in the DOM, so you may need to change the parent of some game elements when they are moving in your game area.<br />
<br />
CAREFUL: this function destroys original object and places a clone onto a new parent, this will break all references to this HTML element (ex: dojo.connect).<br />
<br />
== Players input ==<br />
<br />
<br />
'''dojo.connect'''<br />
<br />
Used to associate a player event with one of your notification method.<br />
<br />
Example: associate a click on an element ("my_element") with one of our method ("onClickOnMyElement"):<br />
<pre><br />
dojo.connect( $('my_element'), 'onclick', this, 'onClickOnMyElement' );<br />
</pre><br />
Same idea but base on query (i.e. all element of 'pet' class)<br />
dojo.query(".pet").connect('onclick', this, 'onPet');<br />
<br />
Note: the methods described here are only correct ways to associate a player input event to your code, and you must not use anything else.<br />
<br />
'''this.connect'''<br />
function(element, event, handler)<br />
<br />
Used to associate a player event with one of your notification method.<br />
<br />
this.connect( $('my_element'), 'onclick', 'onClickOnMyElement' );<br />
<br />
Or you can use in-place handler<br />
<br />
this.connect( $('my_element'), 'onclick', (e) => { console.log('boo'); } );<br />
<br />
Note this function store connection halder (and this is the only real diffrence between this one and dojo.connect), if you planning on destroying the element you connected you MUST call this.disconnect() to prevent resource leaks.<br />
<br />
<br />
'''this.connectClass'''<br />
function( cssClassName, event, handler )<br />
<br />
Same as connect(), but for all the nodes set with the specified cssClassName.<br />
<br />
this.connectClass('pet', 'onclick', 'onPet');<br />
<br />
'''this.disconnect'''<br />
function( element, event )<br />
<br />
Disconnect event handler (previously registered with this.connect or this.connectClass).<br />
<br />
this.disconnect( $('my_element'), 'onclick');<br />
<br />
Note: dynamic connect/disconnect is for advanced cases ONLY, you should always connect elements statically if possible, i.e. in setup() method.<br />
<br />
'''this.disconnectAll'''<br />
<br />
Disconnect all previously registed event handlers (registered via this.connect or this.connectClass)<br />
<br />
'''this.checkAction'''<br />
function( action, nomessage )<br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
<br />
return true if action is authorized (i.e: the action is listed as a "possibleaction" in current game state).<br />
<br />
return false and display an error message if not authorized (display no message if nomessage parameter is true). <br />
The displayed error message could be either "This move is not allowed at this moment" or "An action is already in progress".<br />
<br />
Example:<br />
<pre><br />
function onClickOnGameElement( evt ) {<br />
if( this.checkAction( "my_action" ) ) {<br />
// Do the action<br />
}<br />
}<br />
</pre><br />
<br />
'''this.checkPossibleActions'''<br />
function( action, nomessage )<br />
<br />
* this is independent of the player being active, so can be used instead of this.checkAction(). This is particularly useful for multiplayer states when the player is not active in a 'player may like to change their mind' scenario. <br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
<br />
'''this.ajaxcall( url, parameters, obj_callback, callback, callback_error )'''<br />
<br />
This method must be used to send a player input to the game server.<br />
<br />
* url: the url of the action to perform. For a game, it must be: "/<mygame>/<mygame>/myAction.html"<br />
* parameters: an array of parameter to send to the game server. <br />
** Note that "lock: true" must always be specified in this list of parameters in order the interface can be locked during the server call.<br />
** Note: Restricted parameter names (please don't use them):<br />
*** "action"<br />
*** "module"<br />
*** "class"<br />
* obj_callback: must be set to "this".<br />
* callback: a function to trigger when the server returns and everything went fine (not used, as all data handling is done via notifications).<br />
* callback_error: (optional and rarely used) a function to trigger when the server returns an error. if no error this function is called with parameter value false.<br />
<br />
Usage:<br />
<pre><br />
this.ajaxcall( '/mygame/mygame/myaction.html', { lock: true, <br />
arg1: myarg1, <br />
arg2: myarg2, <br />
...<br />
}, this, function( result ) {<br />
// Do some stuff after a successful call<br />
} );<br />
</pre><br />
<br />
Note: to reduce the boilerplate code its recommended to define your own wrapper, which will do checking, locking and allow to skip parameters<br />
<pre><br />
ajaxcallwrapper: function(action, args, handler) {<br />
if (!args) {<br />
args = [];<br />
}<br />
args.lock = true;<br />
<br />
if (this.checkAction(action)) {<br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", args,// <br />
this, (result) => { }, handler);<br />
}<br />
},<br />
</pre><br />
This can be called like this which is a lot more compact<br />
<pre><br />
this.ajaxcallwrapper('playDraw');<br />
this.ajaxcallwrapper('playMove', {card: id})<br />
</pre><br />
<br />
<br />
'''this.isInterfaceLocked()'''<br />
<br />
When using "lock: true" in ajax call you can use this function to check if interface is in lock state (it will be locked during server call and notification processing).<br />
This check can be used to block some other interactions which do not result in ajaxcall or if you want to suppress errors.<br />
<br />
<br />
'''this.addActionButton( id, label, method, (opt)destination, (opt)blinking, (opt)color )'''<br />
<br />
You can use this method to add an action button in the main action status bar.<br />
<br />
Arguments:<br />
* id: an element ID that should be unique in your HTML DOM document.<br />
* label: the text of the button. Should be translatable (use _() function).<br />
* method: the name of your method that must be triggered when the player clicks on this button.<br />
* destination (optional): deprecated, do not use this. Use '''null''' as value if you need to specify other arguments.<br />
* blinking (optional): if set to '''true''', the button is going blink to catch player's attention. Please don't abuse of blinking button.<br />
* color: could be '''blue''' (default), '''red''' or '''gray'''.<br />
<br />
You should only use this method in your "onUpdateActionButtons" method. Usually, you use it like this (from Hearts example):<br />
<br />
<pre><br />
onUpdateActionButtons: function( stateName, args ) {<br />
<br />
if (this.isCurrentPlayerActive()) { <br />
switch( stateName ) {<br />
case 'giveCards':<br />
this.addActionButton( 'giveCards_button', _('Give selected cards'), 'onGiveCards' ); <br />
break;<br />
}<br />
}<br />
}, <br />
</pre><br />
<br />
In the example above, we are adding a "Give selected cards" button in the case we are on game state "giveCards". When player clicks on this button, it triggers our "onGiveCards" method.<br />
<br />
Example using blinking red button:<br />
<pre><br />
this.addActionButton( 'commit_button', _('Confirm'), 'onConfirm', null, true, 'red'); <br />
</pre><br />
<br />
Note: at least in studio example above will make button huge, because it sets it display of blinking things to '''block''', <br />
if you don't like it you have to change css display value<br />
of the button to inline-block (the id of the button is the first argument, i.e 'commit_button' in example above)<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Notifications ==<br />
<br />
When something happens on the server side, your game interface Javascript logic received a notification.<br />
<br />
Here's how you can handle these notifications on the client side.<br />
<br />
=== Subscribe to notifications ===<br />
<br />
Your Javascript "setupNotifications" method is the place where you can subscribe to notifications from your PHP code.<br />
<br />
Here's how you associate one of your Javascript method to a notification "playDisc" (from Reversi example):<br />
<br />
<pre><br />
// In setupNotifications method:<br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
</pre><br />
<br />
Note: the "playDisc" corresponds to the name of the notification you define it in your PHP code, in your "notifyAllPlayers" or "notifyPlayer" method.<br />
<br />
Then, you have to define your "notif_playDisc" method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addDiscOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
In a notification handler like our "notif_playDisc" method, you can access to all notifications arguments with "notif.args".<br />
<br />
Example:<br />
<pre><br />
// If you did this on PHP side:<br />
self::notifyAllPlayers( "myNotification", '', array( "myArgument" => 3 ) );<br />
<br />
// On Javascript side, you can access the "myArgument" like this:<br />
notif_myNotification: function( notif )<br />
{<br />
alert( "myArgument = " + notif.args.myArgument );<br />
}<br />
</pre><br />
<br />
<br />
=== Synchronous notifications ===<br />
<br />
When several notifications are received by your game interface, these notifications are processed immediately, one after the other, in the same exact order they have been generated in your PHP game logic.<br />
<br />
However, sometimes, you need to give some time to the players to figure out what happened on the game before jumping to the next notification. Indeed, in many games, they are a lot of automatic actions, and the computer is going to resolve all these actions very fast if you don't tell it not to do so.<br />
<br />
As an example, for Reversi, when someone is playing a disc, we want to wait 500 milliseconds before doing anything else in order the opponent player can figure out what move has been played.<br />
<br />
Here's how we do this, right after our subscription:<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 ); // Wait 500 milliseconds after executing the playDisc handler<br />
</pre><br />
<br />
<br />
=== Pre-defined notification types ===<br />
<br />
'''tableWindow''' - This defines notification to display [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialogs]], see below.<br />
<br />
'''message''' - This defines notification that shows on players log and have no other effect<br />
<br />
// You can call this on php side without doing anything on client side<br />
self::notifyAllPlayers( 'message', 'hello', array( ) );<br />
<br />
== Tooltips ==<br />
<br />
'''this.addTooltip( nodeId, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to the DOM node.<br />
<br />
Specify 'helpString' to display some information about "what is this game element?".<br />
Specify 'actionString' to display some information about "what happens when I click on this element?".<br />
<br />
You must specify both helpString and actionString. Most of the time, you should use only one and specify a void string ("") for the other one.<br />
<br />
Usually, _() must be used for the text to be marked for translation.<br />
<br />
"Delay" is an optional parameter. Usually, it is primarily used to specify a zero delay for some game element when the tooltip gives really important information for the game - but remember: no essential information must be placed in tooltips as they won't be displayed in some browsers (see [[BGA_Studio_Guidelines|Guidelines]]).<br />
<br />
Example:<br />
<pre><br />
this.addTooltip( 'cardcount', _('Number of cards in hand'), '' );<br />
</pre><br />
<br />
'''this.addTooltipHtml( nodeId, html, delay )'''<br />
<br />
Add an HTML tooltip to the DOM node (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
'''this.addTooltipToClass( cssClass, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to all the DOM nodes set with this cssClass. <br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips.<br />
<br />
'''this.addTooltipHtmlToClass( cssClass, html, delay )'''<br />
<br />
Add an HTML tooltip to to all the DOM nodes set with this cssClass (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips<br />
<br />
'''this.removeTooltip( nodeId )'''<br />
<br />
Remove a tooltip from the DOM node.<br />
<br />
== Dialogs, warning messages, confirmation dialogs, ... ==<br />
<br />
=== Warning messages ===<br />
<br />
Sometimes, there is something important that is happening on the game and you have to make sure all players get the message. Most of the time, the evolution of the game situation or the game log is enough, but sometimes you need something more visible.<br />
<br />
Ex: someone fulfill one of the end of the game condition, so this is the last turn.<br />
<br />
'''this.showMessage( msg, type )'''<br />
<br />
showMessage shows a message in a big rectangular area on the top of the screen of current player.<br />
<br />
* "msg" is the string to display. It should be translated.<br />
* "type" can be set to "info" or "error". If set to "info", the message will be an informative message on a white background. If set to "error", the message will be an error message on a red background.<br />
<br />
Important: the normal way to inform players about the progression of the game is the game log. "showMessage" is intrusive and should not be used often.<br />
<br />
=== Confirmation dialog ===<br />
<br />
'''confirmationDialog( message, yesHandler, noHandler )'''<br />
<br />
<br />
When an important action with a lot of consequences is triggered by the player, you may want to propose a confirmation dialog.<br />
<br />
CAREFUL: the general guidelines of BGA is to AVOID the use of confirmation dialog. Confirmation dialogs slow down the game and bother players. The players knows that they have to pay attention about each move when they are playing online.<br />
<br />
The situation where you should use a confirmation dialog are the following:<br />
* It must not happen very often during a game.<br />
* It must be linked to an action that can really "kill a game" if the player do not pay attention.<br />
* It must be something that can be done by mistake (ex: a link on the action status bar).<br />
<br />
How to display a confirmation dialog:<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to bake the pie?'), dojo.hitch( this, function() {<br />
this.bakeThePie();<br />
} ) ); <br />
return; // nothing should be called or done after calling this, all action must be done in the handler <br />
</pre><br />
<br />
=== Multiple choice dialog ===<br />
You can use this dialog to give user a choice with small amount of options:<br />
<pre><br />
var keys = [1,5,10];<br />
this.multipleChoiceDialog(<br />
_('How many bugs to fix?'), keys, <br />
dojo.hitch(this, function(choice) {<br />
var bugchoice = keys[choice];<br />
console.log('dialog callback with '+bugchoice);<br />
this.ajaxcall( '/mygame/mygame/fixBugs.html', { bugs: bugchoice}, this, function( result ) {} ); }));<br />
</pre><br />
<br />
=== Dialogs ===<br />
<br />
As a general rule, you shouldn't use dialogs windows.<br />
<br />
BGA guidelines specify that all game elements should be displayed on the main screen. Players can eventually scroll down to see game elements they don't need to see anytime, and you may eventually create anchors to move between game area section. Of course dialogs windows are very practical, but the thing is: all players know how to scroll down, and not all players know how to show up your dialog window. In addition, when the dialog shows up, players can't access the other game components.<br />
<br />
Sometimes although, you need to display a dialog window. Here is how you do this:<br />
<br />
<br />
<br />
// Create the new dialog over the play zone. You should store the handler in a member variable to access it later<br />
this.myDlg = new ebg.popindialog();<br />
this.myDlg.create( 'myDialogUniqueId' );<br />
this.myDlg.setTitle( _("my dialog title to translate") );<br />
this.myDlg.setMaxWidth( 500 ); // Optional<br />
<br />
// Create the HTML of my dialog. <br />
// The best practice here is to use [[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]<br />
var html = this.format_block( 'jstpl_myDialogTemplate', { <br />
arg1: myArg1,<br />
arg2: myArg2,<br />
...<br />
} ); <br />
<br />
// Show the dialog<br />
this.myDlg.setContent( html ); // Must be set before calling show() so that the size of the content is defined before positioning the dialog<br />
this.myDlg.show();<br />
<br />
// Now that the dialog has been displayed, you can connect your method to some dialog elements<br />
// Example, if you have an "OK" button in the HTML of your dialog:<br />
dojo.connect( $('my_ok_button'), 'onclick', this, function(evt){<br />
evt.preventDefault();<br />
this.myDlg.destroy();<br />
} );<br />
<br />
If necessary, you can remove the default top right corner 'close' icon, or replace the function called when it is clicked:<br />
// Removes the default close icon<br />
this.myDlg.hideCloseIcon();<br />
<br />
// Replace the function call when it's clicked<br />
this.myDlg.replaceCloseCallback( function() { ... } );<br />
<br />
=== Scoring dialogs ===<br />
<br />
Sometimes at the end of a round you want to display a big table that details the points wins in each section of the game.<br />
<br />
Example: in Hearts game, we display at the end of each round the number of "heart" cards collected by each player, the player who collected the Queen of Spades, and the total number of points loose by each player.<br />
<br />
Scoring dialogs are managed entirely on '''PHP side''', but they are described here as their effects are visible only on client side.<br />
<br />
Displaying a scoring dialog is quite simple and is using a special notification type: "tableWindow":<br />
<pre><br />
// on PHP side:<br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table<br />
) ); <br />
</pre><br />
<br />
The "table" argument is a 2 dimensional PHP array that describe the table you want to display, line by line and column by column.<br />
<br />
Example: display an 3x3 array of strings<br />
<pre><br />
$table = array(<br />
array( "one", "two", "three" ), // This is my first line<br />
array( "four", "five", "six" ), // This is my second line<br />
array( "seven", "height", "nine" ) // This is my third line<br />
);<br />
</pre><br />
<br />
As you can see above, in each "cell" of your array you can display a simple string value. But you can also display a complex value with a template and associated arguments like this:<br />
<pre><br />
$table = array(<br />
array( "one", "two", array( "str" => clienttranslate("a string with an ${argument}"), "args" => array( 'argument' => 'argument_value' ) ) ),<br />
array( "four", "five", "six" ), <br />
array( "seven", "height", "nine" )<br />
);<br />
</pre><br />
<br />
This is especially useful when you want to display player names with colors. Example from "Hearts":<br />
<pre><br />
$firstRow = array( '' );<br />
foreach( $players as $player_id => $player )<br />
{<br />
$firstRow[] = array( 'str' => '${player_name}',<br />
'args' => array( 'player_name' => $player['player_name'] ),<br />
'type' => 'header'<br />
);<br />
}<br />
$table[] = $firstRow;<br />
</pre><br />
<br />
You can also use three extra attributes in the parameter array for the notification:<br />
<br />
<pre><br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table,<br />
"header" => array('str' => clienttranslate('Table header with parameter ${number}'),<br />
'args' => array( 'number' => 3 ),<br />
),<br />
"footer" => '<div>Some footer</div>',<br />
"closing" => clienttranslate( "Closing button label" )<br />
) ); <br />
</pre><br />
<br />
*'''header''': the content for this parameter will display before the table (also, the html will be parsed and player names will be colored according to the current game colors). <br />
*'''footer''': the content for this parameter will display after the table (no parsing for coloring the player names)<br />
*'''closing''': if this parameter is used, a button will be displayed with this label at the bottom of the popup and will allow players to close it (more easily than by clicking the top right 'cross' icon).<br />
<br />
=== Scoring animated display ===<br />
<br />
Sometimes (Terra Mystica final scoring for example), you may want to display a score value over an element to make the scoring easier to follow for the players.<br />
You can do it with:<br />
<br />
<pre><br />
this.displayScoring( anchor_id, color, score, duration, offset_x, offset_y );<br />
</pre><br />
<br />
'''anchor_id''': ID of the element to place the animated score onto (without the '#') <br />
<br />
'''color''': hexadecimal RGB representation of the color (should be the color of the scoring player), but without a leading '#'. For instance, 'ff0000' for red.<br />
<br />
'''score''': numeric score to display, prefixed by a '+'<br />
<br />
'''duration''': animation duration in milliseconds<br />
<br />
'''offset_x''' and '''offset_y''': if both offset_x and offset_y are defined and not null, apply the following offset (in pixels) to the scoring animation<br />
<br />
<br />
Note: if you want to display successively each score, you can use ''this.notifqueue.setSynchronous()'' function.<br />
<br />
=== Speech bubble ===<br />
<br />
For better interactivity in some games (Love Letter for example), you may use comic book style speech bubbles to express the players voices.<br />
This is done with:<br />
<br />
<pre><br />
this.showBubble(anchor_id, text, delay, duration, custom_class)<br />
</pre><br />
<br />
text - what to put in bubble, can be html actually not just text<br />
<br />
delay - in milliseconds is optional (default 0)<br />
<br />
duration - in milliseconds is optional (default 3000)<br />
<br />
custom_class - extra class to add to bubble is optional, if you need to override the default bubble style<br />
<br />
'''Warning''': if your bubble could overlap other active elements of the interface (buttons in particular), as it stays in place even after disappearing, you should use a custom class to give it the style "pointer-events: none;" in order to intercept click events.<br />
<br />
Note: If you want this visually, but want to take complete control over this bubble and its animation (for example to make it permanent) you can just use div with 'discussion_bubble' class on it, and content of div is what will be shown.<br />
<br />
== Update players score ==<br />
<br />
The column player_score from the player table is automatically loaded into this.scoreCtrl and therefore into the stars location on the player board. This occurs sometime after the <gamename>.js setup() function. However this score must be updated as the game progresses through player notifications (notifs).<br />
<br />
<br />
Increase a player score (with a positive or negative number):<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].incValue( score_delta );<br />
</pre><br />
<br />
Set a player score to a specific value:<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].setValue( new_score );<br />
</pre><br />
<br />
Set a player score to a specific value with animation :<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].toValue( new_score );<br />
</pre><br />
<br />
Typical usage would be (that will process 'score' notification):<br />
<pre><br />
setupNotifications : function() {<br />
...<br />
dojo.subscribe('score', this, "notif_score");<br />
},<br />
notif_score: function(notif) {<br />
this.scoreCtrl[notif.args.player_id].setValue(notif.args.player_score);<br />
},<br />
</pre><br />
<br />
== Players panels ==<br />
<br />
=== Adding stuff to player's panel ===<br />
<br />
At first, create a new "JS template" string in your template (tpl) file:<br />
<br />
(from Gomoku example)<br />
<pre><br />
var jstpl_player_board = '\<div class="cp_board">\<br />
<div id="stoneicon_p${id}" class="gmk_stoneicon gmk_stoneicon_${color}"></div><span id="stonecount_p${id}">0</span>\<br />
</div>';<br />
</pre><br />
<br />
Then, you add this piece of code in your JS file to add this template to each player panel:<br />
<br />
<pre><br />
// Setting up player boards<br />
for( var player_id in gamedatas.players )<br />
{<br />
var player = gamedatas.players[player_id];<br />
<br />
// Setting up players boards if needed<br />
var player_board_div = $('player_board_'+player_id);<br />
dojo.place( this.format_block('jstpl_player_board', player ), player_board_div );<br />
}<br />
</pre><br />
<br />
(Note: the code above is of course from your "setup" function in your Javascript).<br />
<br />
Very often, you have to distinguish current player and others players. In this case, you just have to create another JS template (ex: jstpl_otherplayer_board) and use it when "player_id" is different than "this.player_id".<br />
<br />
=== Player's panel disabling/enabling ===<br />
<br />
'''this.disablePlayerPanel( player_id )'''<br />
<br />
Disable given player panel (the panel background become gray).<br />
<br />
Usually, this is used to signal that this played passes, or will be inactive during a while.<br />
<br />
Note that the only effect of this is visual. There are no consequences on the behaviour of the panel itself.<br />
<br />
'''this.enablePlayerPanel( player_id )'''<br />
<br />
Enable a player panel that has been disabled before.<br />
<br />
'''this.enableAllPlayerPanels()'''<br />
<br />
Enable all player panels that has been disabled before.<br />
<br />
== Image loading ==<br />
<br />
See also [[Game_art:_img_directory]].<br />
<br />
'''Be careful''': by default, ALL images of your img directory are loaded on a player's browser when he loads the game. For this reason, don't let in your img directory images that are not useful, otherwise it's going to slowdown the game load.<br />
<br />
'''dontPreloadImage( image_file_name )'''<br />
<br />
Using dontPreloadImage, you tell the interface to not preload a specific image in your img directory.<br />
<br />
Example of use:<br />
<pre><br />
this.dontPreloadImage( 'cards.png' );<br />
</pre><br />
<br />
This is particularly useful if for example you have 2 different themes for a game. To accelerate the loading of the game, you can specify to not preload images corresponding to the other theme.<br />
<br />
Another example of use: in "Gosu" game with Kamakor extension, you play with 5 sets of cards among 10 available. Cards images are organized by sets, and we only preload the images corresponding to the 5 current sets with '''ensureSpecificGameImageLoading( image_file_names_array )'''.<br />
<pre><br />
// By default, do not preload anything<br />
this.dontPreloadImage( 'cards.png' );<br />
this.dontPreloadImage( 'clan1.png' );<br />
this.dontPreloadImage( 'clan2.png' );<br />
this.dontPreloadImage( 'clan3.png' );<br />
this.dontPreloadImage( 'clan4.png' );<br />
this.dontPreloadImage( 'clan5.png' );<br />
this.dontPreloadImage( 'clan6.png' );<br />
this.dontPreloadImage( 'clan7.png' );<br />
this.dontPreloadImage( 'clan8.png' );<br />
this.dontPreloadImage( 'clan9.png' );<br />
this.dontPreloadImage( 'clan10.png' );<br />
var to_preload = [];<br />
for( i in this.gamedatas.clans )<br />
{<br />
var clan_id = this.gamedatas.clans[i];<br />
to_preload.push( 'clan'+clan_id+'.png' );<br />
}<br />
if( to_preload.length == 5 )<br />
{<br />
this.ensureSpecificGameImageLoading( to_preload );<br />
}<br />
</pre><br />
<br />
'''Note:''' You don't need to specify to not preload game box images (game_box.png, game_box75.png...) since they are not preloaded by default.<br />
<br />
== Other useful stuff ==<br />
<br />
<br />
'''dojo.hitch'''<br />
<br />
With dojo.hitch, you can create a callback function that will run with your game object context whatever happen.<br />
<br />
Typical example: display a BGA confirmation dialog with a callback function created with dojo.hitch:<br />
<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), dojo.hitch( this, function() {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, function( result ) {} );<br />
} ) ); <br />
</pre><br />
<br />
In the example above, using dojo.hitch, we ensure that the "this" object will be set when the callback is called.<br />
<br />
NOTE: In modern JS there are lambdas that eliminate need for that, the example above will look like this<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), () => {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, (result) => {} );<br />
} ); <br />
</pre><br />
<br />
; updateCounters(counters)<br />
: Useful for updating game counters in the player panel (such as resources). <br />
: 'counters' arg is an associative array [counter_name_value => [ 'counter_name' => counter_name_value, 'counter_value' => counter_value_value], ... ]<br />
: All counters must be referenced in this.gamedatas.counters and will be updated.<br />
: DOM objects referenced by 'counter_name' will have their innerHTML updated with 'counter_value'.<br />
<br />
<br />
;'''onScreenWidthChange()'''<br />
:This function can be overridden in your game to manage some resizing on the client side when the browser window is resized. This function is also triggered at load time, so it can be used to adapt to the :viewport size at the start of the game too.<br />
<br />
<br />
;'''updatePageTitle()'''<br />
:This function allows to update the current page title and turn description according to the game state. If the current game state description this.gamedatas.gamestate.descriptionmyturn is modified before :calling the function, it allows to update the turn description without changing state.<br />
<br />
Example from Terra Mystica:<br />
<pre><br />
onClickFavorTile: function( evt ) {<br />
...<br />
if ( ... ) {<br />
this.gamedatas.gamestate.descriptionmyturn = _('Special action: ') + _('Advance 1 space on a Cult track');<br />
this.updatePageTitle();<br />
this.removeActionButtons();<br />
this.addActionButton( ... );<br />
...<br />
return;<br />
}<br />
...<br />
<br />
}<br />
</pre><br />
<br />
== BGA GUI components ==<br />
<br />
BGA framework provides some useful ready-to-use components for the game interface:<br />
<br />
[[Studio#BGA_Studio_game_components_reference]]<br />
<br />
Note that each time you are using an additional component, you must declare it at the top of your Javascript file in the list of modules used.<br />
<br />
Example if you are using "ebg.stock":<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <=== we are using ebg.stock module<br />
],<br />
</pre><br />
<br />
== BGA Buttons ==<br />
<br />
You can create a custom button, but the BGA framework provides a standard button that requires only .css classes: '''bgabutton''' and '''bgabutton_${color}'''.<br />
<br />
'''Examples:'''<br />
<br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_blue"><span>My blue button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_gray"><span>My gray button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_red"><span>My red button</span></a><br />
</pre><br />
<pre><br />
<a href="#" id="my_button_id" class="bgabutton bgabutton_red bgabutton_big"><span>My big red button</span></a><br />
</pre><br />
<br />
<br />
'''Note''': To see it in action, check out ''Coloretto''.<br />
<br />
== Sounds ==<br />
<br />
Add a custom sound and make it load with your interface:<br />
<br />
Add this in your template (.tpl) file:<br />
<pre><br />
<audio id="audiosrc_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<gamename>_<yoursoundname>.mp3" preload="none" autobuffer></audio><br />
<audio id="audiosrc_o_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<gamename>_<yoursoundname>.ogg" preload="none" autobuffer></audio><br />
</pre><br />
<br />
Note: this is a requirement to provide both a mp3 and a ogg file.<br />
<br />
Play the sound (from your .js file):<br />
<br />
<pre><br />
playSound('<gamename>_<yoursoundname>'); <br />
</pre><br />
<br />
<br />
Disable the standard "move" sound for this move (to replace it with your custom sound):<br />
<br />
Add this to your notification handler:<br />
<br />
<pre><br />
this.disableNextMoveSound();<br />
</pre><br />
<br />
Note: it only disable the sound for the next move.</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Main_game_logic:_yourgamename.game.php&diff=6272
Main game logic: yourgamename.game.php
2020-11-28T16:34:38Z
<p>Cpasbanal: /* Players turn order */ fixed typo</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
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.<br />
<br />
== File Structure ==<br />
<br />
The details of how the file is structured are described directly with comments in the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* Constructor: where you define global variables.<br />
* setupNewGame: initial setup of the game. Takes an array of players, indexed by player_id. Structure of each player is...?<br />
* getAllDatas: where you retrieve all game data during a complete reload of the game.<br />
* getGameProgression: where you compute the game progression indicator.<br />
* Utility functions: your utility functions.<br />
* Player actions: the entry points for players actions. <br />
* 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]).<br />
* 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]).<br />
* zombieTurn: what to do it's the turn of a zombie player.<br />
* upgradeTableDb: function to migrate database if you change it after release on production.<br />
<br />
== Accessing player information ==<br />
<br />
'''Important''': In the following methods, be mindful of the difference between the "active" player and the "current" player. The '''active''' player is the player whose turn it is - not necessarily the player who sent a request! The '''current''' player is the player who sent the request and will see the results returned by your methods: not necessarily the player whose turn it is!<br />
<br />
<br />
; getPlayersNumber()<br />
: Returns the number of players playing at the table<br />
: Note: doesn't work in setupNewGame (use count($players) instead).<br />
<br />
; getActivePlayerId()<br />
: Get the "active_player", whatever what is the current state type.<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multiplayer"<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; getActivePlayerName()<br />
: Get the "active_player" name<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; loadPlayersBasicInfos()<br />
: Get an associative array with generic data about players (ie: not game specific data).<br />
: The key of the associative array is the player id. The returned table is cached, so ok to call multiple times without performance concerns.<br />
: The content of each value is:<br />
: * player_name - the name of the player<br />
: * player_color (ex: ff0000) - the color code of the player<br />
: * player_no - the position of the player at the start of the game in natural table order, i.e. 1,2,3<br />
<br />
; getCurrentPlayerId()<br />
: Get the "current_player". The current player is the one from which the action originated (the one who sent the request).<br />
: '''Be careful''': This is not necessarily the active player!<br />
: In general, you shouldn't use this method, unless you are in "multiplayer" state.<br />
: '''Very important''': in your setupNewGame and zombieTurn function, you must never use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message (these actions are triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to these actions).<br />
<br />
; getCurrentPlayerName()<br />
: Get the "current_player" name<br />
: Be careful using this method (see above).<br />
<br />
; getCurrentPlayerColor()<br />
: Get the "current_player" color<br />
: Be careful using this method (see above).<br />
<br />
; isCurrentPlayerZombie()<br />
: Check the "current_player" zombie status. If true, player is zombie, i.e. left or was kicked out of the game.<br />
<br />
; getActivePlayerColor()<br />
: This function does not seems to exist in API, if you need it here is implementation<br />
function getActivePlayerColor() {<br />
$player_id = self::getActivePlayer();<br />
$players = self::loadPlayersBasicInfos();<br />
if( isset( $players[ $player_id ]) )<br />
return $players[ $player_id ]['player_color'];<br />
else<br />
return null;<br />
}<br />
<br />
== Accessing the database ==<br />
<br />
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.<br />
<br />
'''IMPORTANT'''<br />
<br />
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 (web request, not database request). 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. This also means that you need not (and in fact cannot) use your own transactions for multiple related database operations.<br />
<br />
; DbQuery( $sql )<br />
: This is the generic method to access the database.<br />
: It can execute any type of SELECT/UPDATE/DELETE/REPLACE/INSERT query on the database.<br />
: You should use it for UPDATE/DELETE/REPLACE/INSERT queries. For SELECT queries, the specialized methods below are much better.<br />
<br />
; getUniqueValueFromDB( $sql )<br />
: Returns a unique value from DB or null if no value is found.<br />
: $sql must be a SELECT query.<br />
: Raise an exception if more than 1 row is returned.<br />
<br />
; getCollectionFromDB( $sql, $bSingleValue=false )<br />
: Returns an associative array of rows for a sql SELECT query.<br />
: The key of the resulting associative array is the first field specified in the SELECT query.<br />
: The value of the resulting associative array is an associative array with all the field specified in the SELECT query and associated values.<br />
: First column must be a primary or alternate key.<br />
: The resulting collection can be empty.<br />
: If you specified $bSingleValue=true and if your SQL query request 2 fields A and B, the method returns an associative array "A=>B"<br />
<br />
Example 1:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
1234 => array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
1235 => array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
1234 => 'myuser0',<br />
1235 => 'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getNonEmptyCollectionFromDB( $sql )<br />
: Idem than previous one, but raise an exception if the collection is empty<br />
<br />
; getObjectFromDB( $sql )<br />
: Returns one row for the sql SELECT query as an associative array or null if there is no result<br />
: Raise an exception if the query return more than one row<br />
<br />
Example:<br />
<pre><br />
self::getObjectFromDB( "SELECT player_id id, player_name name, player_score score FROM player WHERE player_id='$player_id'" );<br />
<br />
Result:<br />
array(<br />
'id'=>1234, 'name'=>'myuser0', 'score'=>1 <br />
)<br />
</pre><br />
<br />
; getNonEmptyObjectFromDB( $sql )<br />
: Idem than previous one, but raise an exception if no row is found<br />
<br />
; getObjectListFromDB( $sql, $bUniqueValue=false )<br />
: Return an array of rows for a sql SELECT query.<br />
: the result if the same than "getCollectionFromDB" except that the result is a simple array (and not an associative array).<br />
: The result can be empty.<br />
: If you specified $bUniqueValue=true and if your SQL query request 1 field, the method returns directly an array of values.<br />
<br />
Example 1:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
'myuser0',<br />
'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getDoubleKeyCollectionFromDB( $sql, $bSingleValue=false )<br />
: Return an associative array of associative array, from a SQL SELECT query.<br />
: First array level correspond to first column specified in SQL query.<br />
: Second array level correspond to second column specified in SQL query.<br />
: If bSingleValue = true, keep only third column on result<br />
<br />
<br />
; DbGetLastId()<br />
: Return the PRIMARY key of the last inserted row (see PHP mysql_insert_id function).<br />
<br />
; DbAffectedRow()<br />
: Return the number of row affected by the last operation<br />
<br />
; escapeStringForDB( $string )<br />
: You must use this function on every string type data in your database that contains unsafe data.<br />
: (unsafe = can be modified by a player).<br />
: This method makes sure that no SQL injection will be done through the string used.<br />
: Note: if you using standard types in ajax actions, like AT_alphanum it is sanitized before arrival,<br />
: this is only needed if you manage to get unchecked string, like in the games where user has to enter text as a response.<br />
<br />
<pre><br />
<br />
<br />
</pre><br />
<br />
Note: see Editing [[Game database model: dbmodel.sql]] to know how to define your database model.<br />
<br />
== Use globals ==<br />
<br />
Sometimes, you want a single global integer value for your game, and you don't want to create a DB table specifically for it.<br />
<br />
You can do this with the BGA framework "global." Your value will be stored in the "global" table in the database, and you can access it with simple methods.<br />
<br />
'''initGameStateLabels'''<br />
<br />
This method should be located at the beginning of ''yourgamename.php.'' This is where you define the globals used in your game logic, by assigning them IDs.<br />
<br />
You can define up to 79 globals, with IDs from 10 to 89 (inclusive). You must '''not''' use globals outside this range, as those values are used by other components of the framework.<br />
<br />
<pre><br />
self::initGameStateLabels( array( <br />
"my_first_global_variable" => 10,<br />
"my_second_global_variable" => 11<br />
) );<br />
</pre><br />
<br />
'''setGameStateInitialValue( $value_label, $value_value )'''<br />
<br />
Initialize your global value. Must be called before any use of your global, so you should call this method from your "setupNewGame" method.<br />
<br />
'''getGameStateValue( $value_label )'''<br />
<br />
Retrieve the current value of a global.<br />
<br />
'''setGameStateValue( $value_label, $value_value )'''<br />
<br />
Set the current value of a global.<br />
<br />
'''incGameStateValue( $value_label, $increment )'''<br />
<br />
Increment the current value of a global. If increment is negative, decrement the value of the global.<br />
<br />
Return the final value of the global.<br />
<br />
== Game states and active players ==<br />
<br />
<br />
=== Activate player handling ===<br />
<br />
; $this->activeNextPlayer()<br />
: Make the next player active in the natural player order.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->activePrevPlayer()<br />
: Make the previous player active (in the natural player order).<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->gamestate->changeActivePlayer( $player_id )<br />
: You can call this method to make any player active.<br />
: Note: you CANNOT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->getActivePlayerId()<br />
: Return the "active_player" id<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multipleactiveplayer"<br />
: Note: avoid using this method in a "multipleactiveplayer" state because it does not mean anything.<br />
<br />
=== Multiple activate player handling ===<br />
<br />
; $this->gamestate->setAllPlayersMultiactive()<br />
: All playing players are made active. Update notification is sent to all players (triggers onUpdateActionButtons).<br />
: 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. Do not use method if you going to do some more chages in active player list, i.e. if you want to take away multi-active right after, use setPlayersMultiactive instead.<br />
<br />
Example of usage:<br />
<pre><br />
function st_MultiPlayerInit() {<br />
$this->gamestate->setAllPlayersMultiactive();<br />
}<br />
</pre><br />
And this is declaration of state:<br />
<pre><br />
2 => array(<br />
"name" => "playerTurnPlace",<br />
"description" => clienttranslate('Other player must place ships'),<br />
"descriptionmyturn" => clienttranslate('${you} must place ships (click on YOUR SHIPS board to place)'),<br />
"type" => "multipleactiveplayer",<br />
'action' => 'st_MultiPlayerInit',<br />
'args' => 'arg_playerTurnPlace',<br />
"possibleactions" => array( "actionBla" ),<br />
"transitions" => array( "next" => 4, "last" => 99)<br />
),<br />
</pre><br />
<br />
; $this->gamestate->setAllPlayersNonMultiactive( $next_state )<br />
: All playing players are made inactive. Transition to next state<br />
<br />
; $this->gamestate->setPlayersMultiactive( $players, $next_state, $bExclusive = false )<br />
: Make a specific list of players active during a multiactive gamestate. Update notification is sent to all players who's state changed.<br />
: "players" is the array of player id that should be made active.<br />
: 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 "$players" array<br />
<br />
: In case "players" is empty, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
<br />
; $this->gamestate->setPlayerNonMultiactive( $player_id, $next_state )<br />
: During a multiactive game state, make the specified player inactive.<br />
: Usually, you call this method during a multiactive game state after a player did his action. It is also possible to call it directly from multiplayer action handler.<br />
: If this player was the last active player, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
Example of usage (see state declaration of playerTurnPlace above):<br />
<pre><br />
function actionBla($args) {<br />
self::checkAction('actionBla');<br />
// handle the action using $this->getCurrentPlayerId()<br />
$this->gamestate->setPlayerNonMultiactive( $this->getCurrentPlayerId(), 'next');<br />
}<br />
</pre><br />
<br />
; $this->gamestate->getActivePlayerList()<br />
: With this method you can retrieve the list of the active player at any time.<br />
: During a "game" type gamestate, it will return a void array.<br />
: During a "activeplayer" type gamestate, it will return an array with one value (the active player id).<br />
: During a "multipleactiveplayer" type gamestate, it will return an array of the active players id.<br />
: Note: you should only use this method in the latter case.<br />
<br />
<br />
<br />
; $this->gamestate->updateMultiactiveOrNextState( $next_state_if_none )<br />
: 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.<br />
Example: you have player teams and you want to activate all players in one team<br />
<pre><br />
$sql = "UPDATE player SET player_is_multiactive='0'";<br />
self::DbQuery( $sql );<br />
$sql = "UPDATE player SET player_is_multiactive='1' WHERE player_id='$player_id' AND player_team='$team_no'";<br />
self::DbQuery( $sql );<br />
<br />
$this->gamestate->updateMultiactiveOrNextState( 'error' );<br />
</pre><br />
<br />
; updating database manually<br />
: Use this helper function to change multiactive state without sending notification<br />
<pre><br />
/**<br />
* Changes values of multiactivity in db, does not sent notifications.<br />
* To send notifications after use updateMultiactiveOrNextState<br />
* @param number $player_id, player id <=0 or null - means ALL<br />
* @param number $value - 1 multiactive, 0 non multiactive<br />
*/<br />
function dbSetPlayerMultiactive($player_id = -1, $value = 1) {<br />
if (! $value)<br />
$value = 0;<br />
else<br />
$value = 1;<br />
$sql = "UPDATE player SET player_is_multiactive = '$value' WHERE player_zombie = 0 and player_eliminated = 0";<br />
if ($player_id > 0) {<br />
$sql .= " AND player_id = $player_id";<br />
}<br />
self::DbQuery($sql);<br />
}<br />
</pre><br />
<br />
=== States functions ===<br />
; $this->gamestate->nextState( $transition )<br />
: Change current state to a new state. Important: the $transition parameter 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.<br />
<br />
; $this->checkAction( $actionName, $bThrowException=true )<br />
: Check if the current player can perform a specific action in the current game state, and optionally throw an exception if they can't.<br />
: The action is valid if it is listed in the "possibleactions" array for the current game state (see game state description).<br />
: This method MUST be the first one called in ALL your PHP methods that handle player actions, in order to make sure a player doesn't perform an action not allowed by the rules at the point in the game. It should not be called from methods where the current player is not necessarily the active player, otherwise it may fail with an "It is not your turn" exception.<br />
: If "bThrowException" is set to "false", the function returns '''false''' in case of failure instead of throwing an exception. This is useful when several actions are possible, in order to test each of them without throwing exceptions.<br />
<br />
; $this->gamestate->checkPossibleAction( $action )<br />
: (rarely used)<br />
: This works exactly like "checkAction" (above), except that it does NOT check if the current player is active.<br />
: This is used specifically in certain game states when you want to authorize additional actions for players that are not active at the moment.<br />
: Example: in ''Libertalia'', you want to authorize players to change their mind about the card played. They are of course not active at the time they change their mind, so you cannot use "checkAction"; use "checkPossibleAction" instead.<br />
<br />
This is how PHP action looks that returns player to active state (only for multiplayeractive states). To be able to execute on js side do not checkAction on js side for this specific one.<br />
<br />
function actionUnpass() {<br />
$this->gamestate->checkPossibleAction('actionUnpass'); // player chane mind about passing while others were thinking<br />
$this->gamestate->setPlayersMultiactive(array ($this->getCurrentPlayerId() ), 'error', false);<br />
}<br />
<br />
; $this->gamestate->state()<br />
: Get an associative array of current game state attributes, see [[Your game state machine: states.inc.php]] for state attributes.<br />
$state=$this->gamestate->state(); if( $state['name'] == 'myGameState' ) {...}<br />
<br />
== Players turn order ==<br />
<br />
'''getNextPlayerTable()'''<br />
<br />
Return an associative array which associate each player with the next player around the table.<br />
<br />
In addition, key 0 is associated to the first player to play.<br />
<br />
Example: if three player with ID 1, 2 and 3 are around the table, in this order, the method returns:<br />
<pre><br />
array( <br />
1 => 2, <br />
2 => 3, <br />
3 => 1, <br />
0 => 1 <br />
);<br />
</pre><br />
<br />
'''getPrevPlayerTable()'''<br />
<br />
Same as above, but the associative array associate the previous player around the table. However there no 0 index here.<br />
<br />
'''getPlayerAfter( $player_id )'''<br />
<br />
Get player playing after given player in natural playing order.<br />
<br />
'''getPlayerBefore( $player_id )'''<br />
<br />
Get player playing before given player in natural playing order.<br />
<br />
Note: There is no API to modify this order, if you have custom player order you have to maintain it in your database<br />
and have custom function to access it.<br />
<br />
'''createNextPlayerTable( $players, $bLoop=true )'''<br />
<br />
Using $players array creates a map of current => next as in example from getNextPlayerTable(), however you can use custom order here. <br />
Parmeter $bLoop is true if last player points to first, false otherwise.<br />
In any case index 0 points to first player (first element of $players array). $players is array of player ids in desired order.<br />
<br />
Example of usage:<br />
<pre><br />
function getNextPlayerTableCustom() {<br />
$starting = $this->getStartingPlayer(); // custom function to get starting player<br />
$player_ids = $this->getPlayerIdsInOrder($starting); // custom function to create players array starting from starting player<br />
return self::createNextPlayerTable($player_ids, false); // create next player table in custom order<br />
}<br />
</pre><br />
<br />
== Notify players ==<br />
<br />
To understand notifications, please read [http://www.slideshare.net/boardgamearena/the-bga-framework-at-a-glance The BGA Framework at a glance] first.<br />
<br />
'''IMPORTANT'''<br />
<br />
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.<br />
Notifications sent between the game start (setupNewGame) and the end of the "action" method of the first active state will never reach their destination.<br />
<br />
'''notifyAllPlayers( $notification_type, $notification_log, $notification_args )'''<br />
<br />
Send a notification to all players of the game.<br />
<br />
* notification_type:<br />
A string that defines the type of your notification.<br />
<br />
Your game interface Javascript logic will use this to know what is the type of the received notification (and to trigger the corresponding method).<br />
<br />
* notification_log:<br />
A string that defines what is to be displayed in the game log.<br />
<br />
You can use an empty string here (""). In this case, nothing is displayed in the game log.<br />
<br />
If you define a real string here, you should use "clienttranslate" method to make sure it can be translate.<br />
<br />
You can use arguments in your notification_log strings, that refers to values defines in the "notification_args" argument (see below). <br />
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.<br />
<br />
<br />
<br />
* notification_args:<br />
The arguments of your notifications, as an associative array.<br />
<br />
This array will be transmitted to the game interface logic, in order the game interface can be updated.<br />
<br />
Complete notifyAllPlayers example (from "Reversi"):<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y<br />
) );<br />
</pre><br />
<br />
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.<br />
<br />
'''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.<br />
<br />
'''Important''': this array is serialized to be sent to the browsers, and will be saved with the notification to be able to replay the game later. If it is too big, it can make notifications slower / less reliable, and replay archives very big (to the point of failing). So as a general rule, you should send only the minimum of information necessary to update the client interface with no overhead in order to keep the notifications as light as possible.<br />
<br />
Note: you CAN use some HTML inside your notification log, however it not recommended for many reasons:<br />
* Its bad architecture, ui elements leak into server now you have to manage ui in many places<br />
* If you decided to change something in ui in future version, old games reply and tutorials may not work, since they use stored notifications<br />
* 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)<br />
* Its more data to transfer and store in db<br />
* 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.<br />
<br />
If you still want to have pretty pictures in the log check this [[BGA_Studio_Cookbook#Inject_images_and_styled_html_in_the_log]].<br />
<br />
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<br />
an array itself, which contains 'log' and 'args', i.e.<br />
<br />
$this->notifyAllPlayers('playerLog',clienttranslate('Game moves ${token_name_rec}'),<br />
['token_name_rec'=>['log'=>'${token_name} #${token_number}',<br />
'args'=> ['token_name'=>clienttranslate('Boo'), 'token_number'=>$number, 'i18n'=>['token_name'] ]<br />
]<br />
]);<br />
<br />
<br />
<br />
'''notifyPlayer( $player_id, $notification_type, $notification_log, $notification_args )'''<br />
<br />
Same as above, except that the notification is sent to one player only.<br />
<br />
This method must be used each time some private information must be transmitted to a player.<br />
<br />
Important: the variable for player name must be ${player_name} in order to be highlighted with the player color in the game log<br />
<br />
== About random and randomness ==<br />
<br />
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.<br />
<br />
Here's are a list of techniques you should use in these situations, from the best to the worst.<br />
<br />
=== Dice and bga_rand ===<br />
<br />
'''bga_rand( min, max )''' <br />
This is a BGA framework function that provides you a random number between "min" and "max" (inclusive), using the best available random method available on the system.<br />
<br />
This is the preferred function you should use, because we are updating it when a better method is introduced.<br />
<br />
As of now, bga_rand is based on the PHP function "random_int", which ensures a cryptographic level of randomness.<br />
<br />
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).<br />
<br />
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".<br />
<br />
=== shuffle and cards shuffling ===<br />
<br />
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 ensures that the best available shuffling method is used, and that if in the future we improve it your game will be up to date.<br />
<br />
As of now, the Deck component shuffle method is based on PHP "shuffle" method, which has quite good randomness (even it is not as good as bga_rand). In consequence, we accept other shuffling methods during reviews, as long as they are based on PHP "shuffle" function (or similar, like "array_rand").<br />
<br />
=== Other methods ===<br />
<br />
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.<br />
<br />
== Game statistics ==<br />
<br />
There are 2 types of statistics:<br />
* a "player" statistic is a statistic associated to a player<br />
* a "table" statistic is a statistic not associated to a player (global statistic for this game).<br />
<br />
See [[Game statistics: stats.inc.php]] to see how you define statistics for your game.<br />
<br />
<br />
'''initStat( $table_or_player, $name, $value, $player_id = null )'''<br />
<br />
Create a statistic entry with a default value.<br />
<br />
This method must be called for each statistic of your game, in your setupNewGame method.<br />
If you neglect to call this for a statistic, and also do not update the value during the course of a certain game using setStat or incStat, the value of the stat will be undefined rather than 0. This will result in it being ignored at the end of the game, as if it didn't apply to that particular game, and excluded from cumulative statistics.<br />
<br />
<br />
'$table_or_player' must be set to "table" if this is a table statistic, or "player" if this is a player statistic.<br />
<br />
'$name' is the name of your statistic, as it has been defined in your stats.inc.php file.<br />
<br />
'$value' is the initial value of the statistic. If this is a player statistic and if the player is not specified by "$player_id" argument, the value is set for ALL players.<br />
<br />
<br />
'''setStat( $value, $name, $player_id = null )'''<br />
<br />
Set a statistic $name to $value.<br />
<br />
If "$player_id" is not specified, setStat consider it is a TABLE statistic.<br />
<br />
If "$player_id" is specified, setStat consider it is a PLAYER statistic.<br />
<br />
<br />
'''incStat( $delta, $name, $player_id = null )'''<br />
<br />
Increment (or decrement) specified statistic value by $delta value. Same behavior as setStat function.<br />
<br />
<br />
'''getStat( $name, $player_id = null )'''<br />
<br />
Return the value of statistic specified by $name. Useful when creating derivative statistics such as average.<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Manage player scores and Tie breaker ==<br />
<br />
=== Normal scoring ===<br />
<br />
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...<br />
<br />
During the game, you update player's score directly by updating "player_score" field of "player" table in database.<br />
<br />
Examples:<br />
<pre><br />
<br />
// +2 points to active player<br />
self::DbQuery( "UPDATE player SET player_score=player_score+2 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
// Set score of active player to 5<br />
self::DbQuery( "UPDATE player SET player_score=5 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
</pre><br />
<br />
Note: don't forget to notify the client side in order the score control can be updated accordingly.<br />
<br />
=== Tie breaker ===<br />
<br />
Tie breaker is used when two players get the same score at the end of a game.<br />
<br />
Tie breaker is using "player_score_aux" field of "player" table. It is updated exactly like the "player_score" field.<br />
<br />
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.<br />
<br />
When you are using "player_score_aux" functionality, you must describe the formula to use in your gameinfos.inc.php file like this:<br />
<br />
<pre><br />
'tie_breaker_description' => totranslate("Describe here your tie breaker formula"),<br />
</pre><br />
<br />
This description will be used as a tooltip to explain to players how this auxiliary score has been calculated.<br />
<br />
=== Co-operative game ===<br />
<br />
To make everyone win/lose together in a full-coop game:<br />
<br />
Add the following in gameinfos.inc.php :<br />
'is_coop' => 1, // full cooperative<br />
<br />
Assign a score of zero to everyone if it's a loss.<br />
Assign the same score > 0 to everyone if it's a win.<br />
<br />
=== Semi-coop ===<br />
<br />
If the game is not full-coop, then everyone loses = everyone is tied. I.e. set score to 0 to everybody.<br />
<br />
=== Only "winners" and "losers" ===<br />
<br />
For some games, there is only a group (or a single) "winner", and everyone else is a "loser", with no "end of game rank" (1st, 2nd, 3rd...).<br />
<br />
Examples:<br />
* Coup<br />
* Not Alone<br />
* Werewolves<br />
* Quantum<br />
<br />
In this case:<br />
* Set the scores so that the winner has the best score, and the other players have the same (lower) score.<br />
* Add the following lines to gameinfos.php:<br />
<br />
<pre><br />
// 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 <br />
// The game end result will display "Winner" for the 1st player and "Loser" for all other players<br />
'losers_not_ranked' => true,<br />
</pre><br />
<br />
Werewolves and Coup are implemented like this, as you can see here:<br />
* https://boardgamearena.com/#!gamepanel?game=werewolves&section=lastresults<br />
* https://boardgamearena.com/#!gamepanel?game=coupcitystate&section=lastresults<br />
<br />
Adding this has the following effects:<br />
* On game results for this game, "Winner" or "Loser" is going to appear instead of the usual "1st, 2nd, 3rd, ...".<br />
* When a game is over, the result of the game will be "End of game: Victory" or "End of game: Defeat" depending on the result of the CURRENT player (instead of the usual "Victory of XXX").<br />
* When calculating ELO points, if there is at least one "Loser", no "victorious" player can lose ELO points, and no "losing" player can win ELO point. Usually it may happened because being tie with many players with a low rank is considered as a tie and may cost you points. If losers_not_ranked is set, we prevent this behavior and make sure you only gain/loss ELO when you get the corresponding results.<br />
<br />
Important: this SHOULD NOT be used for cooperative games (see is_coop parameter), or for 2 players games (it makes no sense in this case).<br />
<br />
=== Solo ===<br />
<br />
If game supports solo variant, a negative or zero score means defeat, a positive score means victory.<br />
<br />
=== Player elimination ===<br />
<br />
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.<br />
<br />
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").<br />
<br />
Usage:<br />
<br />
* Player to eliminate should NOT be active anymore (preferably use the feature in a "game" type game state).<br />
* In your PHP code:<br />
self::eliminatePlayer( <player_to_eliminate_id> );<br />
* 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.<br />
* 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.<br />
<br />
=== Scoring Helper functions ===<br />
<br />
These functions should have been API but they are not, just add them to your php game and use for every game.<br />
<br />
// get score<br />
function dbGetScore($player_id) {<br />
return $this->getUniqueValueFromDB("SELECT player_score FROM player WHERE player_id='$player_id'");<br />
}<br />
<br />
// set score<br />
function dbSetScore($player_id, $count) {<br />
$this->DbQuery("UPDATE player SET player_score='$count' WHERE player_id='$player_id'");<br />
}<br />
<br />
// set aux score (tie breaker)<br />
function dbSetAuxScore($player_id, $score) {<br />
$this->DbQuery("UPDATE player SET player_score_aux=$score WHERE player_id='$player_id'");<br />
}<br />
<br />
// increment score (can be negative too)<br />
function dbIncScore($player_id, $inc) {<br />
$count = $this->dbGetScore($player_id);<br />
if ($inc != 0) {<br />
$count += $inc;<br />
$this->dbSetScore($player_id, $count);<br />
}<br />
return $count;<br />
}<br />
<br />
== Reflexion time ==<br />
<br />
; function giveExtraTime( $player_id, $specific_time=null )<br />
: Give standard extra time to this player.<br />
: Standard extra time depends on the speed of the game (small with "slow" game option, bigger with other options).<br />
: You can also specify an exact time to add, in seconds, with the "specified_time" argument (rarely used).<br />
<br />
<br />
== Undo moves ==<br />
<br />
Please read our [[BGA Undo policy]] before.<br />
<br />
<br />
'''Important''': Before using these methods, you must also add the following to your "gameinfos.inc.php" file, otherwise these methods are ineffective:<br />
'db_undo_support' => true<br />
<br />
<br />
; function undoSavepoint( )<br />
: Save the whole game situation inside an "Undo save point".<br />
: There is only ONE undo save point available (see BGA Undo policy). Cannot use in multiactivate state or in game state where next state is multiactive.<br />
: Note: this function does not actually do anything when it is called, it only raises the flag to store the database AFTER transaction is over. So the actual state will be saved when you exit the function calling it (technically before first queued notification is sent, which matter if you transition to game state not to user state after), which may affect what you end up saved.<br />
<br />
<br />
; function undoRestorePoint()<br />
: Restore the situation previously saved as an "Undo save point".<br />
: You must make sure that the active player is the same after and before the undoRestorePoint (ie: this is your responsibility to ensure that the player that is active when this method is called is exactly the same than the player that was active when the undoSavePoint method has been called).<br />
<br />
== Managing errors and exceptions ==<br />
<br />
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.<br />
<br />
; throw new BgaUserException ( $error_message)<br />
: Base class to notify a user error<br />
: You must throw this exception when a player wants to do something that he is not allowed to do.<br />
: The error message will be shown to the player as a "red message", so it must be translated.<br />
: Throwing such an exception is NOT considered a bug, so it is not traced in BGA error logs.<br />
<br />
Example from Gomoku:<br />
<pre><br />
throw new BgaUserException( self::_("There is already a stone on this intersection, you can't play there") );<br />
</pre><br />
<br />
<br />
; throw new BgaVisibleSystemException ( $error_message)<br />
: You must throw this exception when you detect something that is not supposed to happened in your code.<br />
: The error message is shown to the user as an "Unexpected error", in order that he can report it in the forum.<br />
: The error message is logged in BGA error logs. If it happens regularly, we will report it to you.<br />
<br />
; throw new BgaSystemException ( $error_message)<br />
: 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.<br />
: 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.<br />
<br />
== Zombie mode ==<br />
<br />
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.<br />
<br />
While developing your zombie mode, keep in mind that:<br />
* Do not refer to the rules, because this situation is not planned by the rules.<br />
* 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?<br />
* The idea is NOT to develop an artificial intelligence for the game.<br />
* Do not try to end the game early, even in a two-player game. The zombie is there to allow the game to continue, not to end it. Trying to end the game is not supported by the framework and will likely cause unexpected errors.<br />
<br />
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.<br />
<br />
Each time a zombie player must play, your "zombieTurn" method is called.<br />
<br />
Parameters:<br />
* $state: the name of the current game state.<br />
* $active_player: the id of the active player.<br />
<br />
Most of the time, your zombieTurn method looks like this:<br />
<br />
<pre><br />
function zombieTurn( $state, $active_player )<br />
{<br />
$statename = $state['name'];<br />
<br />
if( $statename == 'myFirstGameState'<br />
|| $statename == 'my2ndGameState'<br />
|| $statename == 'my3rdGameState'<br />
....<br />
)<br />
{<br />
$this->gamestate->nextState( "zombiePass" );<br />
}<br />
else<br />
throw new BgaVisibleSystemException( "Zombie mode not supported at this game state: ".$statename );<br />
}<br />
</pre><br />
<br />
Note that in the example above, all corresponding game state should implement "zombiePass" as a transition.<br />
<br />
'''Very important''': your zombie code will be called when the player leaves the game. This action is triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to this action. In your zombieTurn function, you must '''never''' use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message.<br />
<br />
== Player color preferences ==<br />
<br />
<br />
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.<br />
<br />
Making your game compatible with colors preferences is very easy and requires only 1 line of PHP and 1 configuration change :<br />
<br />
On your gameinfos.inc.php file, add the following lines :<br />
<br />
// Favorite colors support : if set to "true", support attribution of favorite colors based on player's preferences (see reattributeColorsBasedOnPreferences PHP method)<br />
// NB: this parameter is used only to flag games supporting this feature; you must use (or not use) reattributeColorsBasedOnPreferences PHP method to actually enable or disable the feature.<br />
'favorite_colors_support' => true,<br />
<br />
Then, on your main <your_game>.game.php file, find the "reloadPlayersBasicInfos" call in your "setupNewGame" method and replace :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reloadPlayersBasicInfos();<br />
<br />
By :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reattributeColorsBasedOnPreferences( $players, array( /* LIST HERE THE AVAILABLE COLORS OF YOUR GAME INSTEAD OF THESE ONES */"ff0000", "008000", "0000ff", "ffa500", "773300" ) );<br />
self::reloadPlayersBasicInfos();<br />
<br />
<br />
The "reattributeColorsBasedOnPreferences" method reattributes all colors, taking into account players color preferences and available colors.<br />
<br />
Note that you must update the colors to indicate the colors available for your game.<br />
<br />
2 important remarks :<br />
* 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.<br />
* 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.<br />
<br />
Colours currently listed as a choice in preferences:<br />
<br />
* #ff0000 Red<br />
* #008000 Green<br />
* #0000ff Blue<br />
* #ffa500 Yellow<br />
* #000000 Black<br />
* #ffffff White<br />
* #e94190 Pink<br />
* #982fff Purple<br />
* #72c3b1 Cyan<br />
* #f07f16 Orange<br />
* #bdd002 Khaki green<br />
* #7b7b7b Gray<br />
<br />
== Legacy games API ==<br />
<br />
For some very specific games ("legacy", "campaign"), you need to keep some informations from a game to another.<br />
<br />
This should be an exceptional situation: the legacy API is costing resources on Board Game Arena databases, and is slowing down the game setup process + game end of game process. Please do not use it for things like:<br />
* keeping a player preference/settings (=> player preferences and game options should be used instead)<br />
* keeping a statistics, a score, or a ranking, while it is not planned in the physical board game, or while there is no added value compared to BGA statistics / rankings.<br />
<br />
You should use it for:<br />
* legacy games: when some components of the game has been altered in a previous game and should be kept as it is.<br />
* "campaign style" games: when a player is getting a "reward" at the end of a game, and should be able to use it in further games.<br />
<br />
Important: you cannot store more than 64k of data (serialized as JSON) per player per game. If you go over 64k, storeLegacyData function is going to FAIL, and there is a risk to create a major bug (= players blocked) in your game. You MUST make sure that no more than 64k of data is used for each player for your game. For example, if you are implementing a "campaign style" game and if you allow a player to start multiple campaign, you must LIMIT the number of different campaign so that the total data size to not go over the limit. We strongly recommend you to use this:<br />
<br />
try <br />
{<br />
$this->storeLegacyTeamData( 'my_variable', $my_data );<br />
}<br />
catch( feException $e )<br />
{<br />
if( $e->getCode() == FEX_legacy_size_exceeded )<br />
{<br />
// Do something here to free some space in Legacy data (ex: by removing some variables)<br />
}<br />
else<br />
throw $e;<br />
}<br />
<br />
<br />
; function storeLegacyData( $player_id, $key, $data, $ttl = 365 )<br />
: Store some data associated with $key for the given user / current game<br />
: In the opposite of all other game data, this data will PERSIST after the end of this table, and can be re-used<br />
: in a future table with the same game.<br />
: IMPORTANT: The only possible place where you can use this method is when the game is over at your table (last game action). Otherwise, there is a risk of conflicts between ongoing games. <br />
: TTL is a time-to-live: the maximum, and default, is 365 days.<br />
: In any way, the total data (= all keys) you can store for a given user+game is 64k (note: data is store serialized as JSON data)<br />
<br />
; function retrieveLegacyData( $player_id, $key )<br />
: Get data associated with $key for the current game<br />
: This data is common to ALL tables from the same game for this player, and persist from one table to another.<br />
: Note: calling this function has an important cost => please call it few times (possibly: only ONCE) for each player for 1 game if possible<br />
: Note: you can use '%' in $key to retrieve all keys matching the given patterns<br />
<br />
; function removeLegacyData( $player_id, $key )<br />
: Remove some legacy data with the given key<br />
: (useful to free some data to avoid going over 64k)<br />
<br />
<br />
<br />
; function storeLegacyTeamData( $data, $ttl = 365 )<br />
: Same as storeLegacyData, except that it stores some data for the whole team within the current table<br />
: Ie: if players A, B and C are at a table, the legacy data will be saved for future table with (exactly) A, B and C on the table.<br />
: This is useful for games which are intended to be played several time by the same team.<br />
: Note: the data total size is still limited, so you must implement catch the FEX_legacy_size_exceeded exception if it happens<br />
<br />
; function retrieveLegacyTeamData()<br />
: Same as retrieveLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
; function removeLegacyTeamData()<br />
: Same as removeLegacyData, except that it retrieves some data for the whole team within the current table (set by storeLegacyTeamData)<br />
<br />
== Language dependent games API ==<br />
<br />
This API is used for games that are heavily language dependent. Two most common use cases are:<br />
* Games that have a language dependent component that are not necessarily translatable, typically a list of words. (Think of games like Codenames, Decrypto, Just One...)<br />
* Games with massive communication where players would like to ensure that all participants speak the same language. (Think of games like Werewolf, The Resistance, maybe even dixit...)<br />
<br />
If this option is used, the table created will be limited only to users that have specific language in their profile. Player starting the game would be able to chose one of the languages he speaks.<br />
<br />
There is a new property language_dependency in gameinfos.inc.php which can be set like this:<br />
'language_dependecy' => false, //or if the property is missing, the game is not language dependent<br />
'language_dependecy' => true, //all players at the table must speak the same language<br />
'language_dependecy' => array( 1 => 'en', 2 => 'fr', 3 => 'it' ), //1-based list of supported languages<br />
<br />
In the gamename.game.php file, you can get the id of selected language with the method '''getGameLanguage'''.<br />
; function getGameLanguage()<br />
: Returns an index of the selected language as defined in gameinfos.inc.php.<br />
<br />
Languages currently available on BGA are:<br />
'ar' => array( 'name' => "ۧÙŰč۱ۚÙŰ©", 'code' => 'ar_AE' ), // arabic<br />
'be' => array( 'name' => "бДлаŃŃŃĐșĐ°Ń ĐŒĐŸĐČĐ°", 'code' => 'be_BY' ), // Belarusian<br />
'bn' => array( 'name' => "àŠŹàŠŸàŠàŠČàŠŸ", 'code' => 'bn_BD' ), // bengali<br />
'bg' => array( 'name' => "бŃлгаŃŃĐșĐž ДзОĐș", 'code' => 'bg_BG' ), // bulgarian<br />
'ca' => array( 'name' => "catalĂ ", 'code' => 'ca_ES' ), // catalan<br />
'cs' => array( 'name' => "ÄeĆĄtina", 'code' => 'cs_CZ' ), // czech<br />
'da' => array( 'name' => "dansk", 'code' => 'da_DK' ), // danish<br />
'de' => array( 'name' => "deutsch", 'code' => 'de_DE' ), // deutsch<br />
'el' => array( 'name' => "ÎλληΜÎčÎșÎŹ", 'code' => 'el_GR' ), // greek<br />
'en' => array( 'name' => "English", 'code' => 'en_US' ), // english<br />
'es' => array( 'name' => "español", 'code' => 'es_ES' ), // spanish<br />
'et' => array( 'name' => "eesti keel", 'code' => 'et_EE' ), // estonian <br />
'fi' => array( 'name' => "suomi", 'code' => 'fi_FI' ), // finnish<br />
'fr' => array( 'name' => "français", 'code' => 'fr_FR' ), // french<br />
'he' => array( 'name' => "ŚąŚŚšŚŚȘ", 'code' => 'he_IL' ), // hebrew <br />
'hi' => array( 'name' => "à€čà€żà€šà„à€Šà„", 'code' => 'hi_IN' ), // hindi<br />
'hr' => array( 'name' => "Hrvatski", 'code' => 'hr_HR' ), // croatian<br />
'hu' => array( 'name' => "magyar", 'code' => 'hu_HU' ), // hungarian<br />
'id' => array( 'name' => "Bahasa Indonesia", 'code' => 'id_ID' ), // indonesian<br />
'ms' => array( 'name' => "Bahasa Malaysia", 'code' => 'ms_MY' ), // malaysian<br />
'it' => array( 'name' => "italiano", 'code' => 'it_IT' ), // italian<br />
'ja' => array( 'name' => "æ„æŹèȘ", 'code' => 'ja_JP' ), // japanese<br />
'jv' => array( 'name' => "Basa Jawa", 'code' => 'jv_JV' ), // javanese <br />
'ko' => array( 'name' => "íê”ìŽ", 'code' => 'ko_KR' ), // korean<br />
'lt' => array( 'name' => "lietuviĆł", 'code' => 'lt_LT' ), // lithuanian<br />
'lv' => array( 'name' => "latvieĆĄu", 'code' => 'lv_LV' ), // latvian<br />
'nl' => array( 'name' => "nederlands", 'code' => 'nl_NL' ), // dutch<br />
'no' => array( 'name' => "norsk", 'code' => 'nb_NO' ), // Norwegian<br />
'oc' => array( 'name' => "occitan", 'code' => 'oc_FR' ), // Occitan<br />
'pl' => array( 'name' => "polski", 'code' => 'pl_PL' ), // polish<br />
'pt' => array( 'name' => "portuguĂȘs", 'code' => 'pt_PT' ), // portugese<br />
'ro' => array( 'name' => "romĂąnÄ", 'code' => 'ro_RO' ), // romanian<br />
'ru' => array( 'name' => "Đ ŃŃŃĐșĐžĐč ŃĐ·ŃĐș", 'code' => 'ru_RU' ), // russian<br />
'sk' => array( 'name' => "slovenÄina", 'code' => 'sk_SK' ), // slovak<br />
'sl' => array( 'name' => "slovenĆĄÄina", 'code' => 'sl_SI' ), // slovenian <br />
'sr' => array( 'name' => "ĐĄŃĐżŃĐșĐž", 'code' => 'sr_RS' ), // serbian <br />
'sv' => array( 'name' => "svenska", 'code' => 'sv_SE' ), // swedish<br />
'tr' => array( 'name' => "TĂŒrkçe", 'code' => 'tr_TR' ), // turkish <br />
'uk' => array( 'name' => "ĐŁĐșŃĐ°ŃĐœŃŃĐșĐ° ĐŒĐŸĐČĐ°", 'code' => 'uk_UA' ), // Ukrainian<br />
'zh' => array( 'name' => "äžæ (æŒą)", 'code' => 'zh_TW' ), // Traditional Chinese (Hong Kong, Macau, Taiwan)<br />
'zh-cn' => array( 'name' => "äžæ (æ±)", 'code' => 'zh_CN' ), // Simplified Chinese (Mainland China, Singapore)<br />
<br />
== Debugging and Tracing ==<br />
<br />
To debug php code you can use some tracing functions available from the parent class such as debug, trace, error, warn, dump.<br />
<br />
self::debug("Ahh!");<br />
self::dump('my_var',$my_var);<br />
<br />
See [[Practical_debugging]] section for complete information about debugging interfaces and where to find logs.</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Counter&diff=6201
Counter
2020-11-20T13:37:33Z
<p>Cpasbanal: /* Setup a counter */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is very simple control that allow to set/get numeric value from inner html of div/span, and provides animation on from/to value.<br />
<br />
== Dependency ==<br />
<br />
Don't forget to add '''ebg/counter''' as a dependency:<br />
<br />
// in your game js<br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter" /// <==== HERE],<br />
<br />
== Setup a counter ==<br />
<br />
<pre><br />
var counter = new ebg.counter();<br />
counter.create(targetId);<br />
</pre><br />
<br />
Where '''targetId''' either string or dom element, which already exists in the template, which will be used to display the counter value.<br />
For example:<br />
<pre><br />
<span id="my_counter"></span><br />
</pre><br />
<br />
In reality you will have a counter per player, so probably want to create a counter per player, in this case target Id can be 'hand_size_player_' + player_id and you create it in the loop for all players and store as class member. See example below on how to inject per-player sections.<br />
<br />
== Functions on counter ==<br />
;create(target)<br />
:associate counter with existing target dom element<br />
<br />
;getValue()<br />
:return current value<br />
<br />
;incValue(by);<br />
:increment value by "by" and animate from previous value<br />
<br />
;setValue(value);<br />
:set value, no animation<br />
<br />
;toValue(value);<br />
:set value with animation<br />
<br />
;disable()<br />
:display - instead. Note it just changes display value once, it does not actually disables it, i.e. if you set it again, it will be shown again<br />
<br />
<br />
<br />
== Players panels ==<br />
<br />
=== Adding stuff to player's panel ===<br />
<br />
First, create a new JS template string in your template (tpl) file.<br />
<br />
From the ''Gomoku'' example:<br />
<br />
<pre><br />
var jstpl_player_board = '\<div class="cp_board">\<br />
<div id="stoneicon_p${id}" class="gmk_stoneicon gmk_stoneicon_${color}"></div><span id="stonecount_p${id}">0</span>\<br />
</div>';<br />
</pre><br />
<br />
Then, add this piece of code in the '''setup''' function of your JS file to add this template to each player panel:<br />
<br />
<pre><br />
// Setting up player boards<br />
this.stone_counters={};<br />
for( var player_id in gamedatas.players ) {<br />
var player = gamedatas.players[player_id];<br />
<br />
// Setting up players boards if needed<br />
var player_board_div = $('player_board_'+player_id);<br />
dojo.place( this.format_block('jstpl_player_board', player ), player_board_div );<br />
<br />
// create counter per player<br />
this.stone_counters[player_id]=new ebg.counter();<br />
this.stone_counters[player_id].create('stoneicon_p'+player_id);<br />
}<br />
</pre><br />
<br />
Often, you have to distinguish between the current player and other players. In this case, create another JS template (ex: jstpl_otherplayer_board) and use it where "player_id" is different than "this.player_id".</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Practical_debugging&diff=5976
Practical debugging
2020-10-26T20:28:07Z
<p>Cpasbanal: /* Debugging Toolips CSS/Layout */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This page gives you practical tips to debug your game during development. Don't hesitate to share your difficulties with us so that we can improve this section.<br />
<br />
== Tools ==<br />
<br />
To work on BGA Studio, we recommend that you use [http://www.google.com/chrome Google Chrome] as it's currently the fastest browser for the BGA platform, and it's available for all OSes.<br />
<br />
Another reason to use Chrome is that it embeds all the tools you need to work on BGA Studio. You can see them by pressing "F12" or from the menu ("Tools > Development tools").<br />
<br />
A good practice is to use a second browser to develop the game, in order to verify that your game is working fine on this browser too.<br />
<br />
To debug with Firefox browser, we advise you to use these 2 extensions:<br />
* [https://addons.mozilla.org/firefox/addon/firebug/ Firebug]<br />
* [https://addons.mozilla.org/firefox/addon/web-developer/ Web developer]<br />
<br />
To debug with other browsers (IE, Edge, Opera), we advise you to use one of the most recent versions. Latest versions of the browser will likely have better development tools than the previous ones...<br />
<br />
== General tip for debugging ==<br />
<br />
In general for debugging, think of using the '[[Tools_and_tips_of_BGA_Studio#Save_.26_restore_state|save & restore]] state' functionality. It enables you to save the state of your game just before the issue you are investigating, then come back to that point with one click as many times as needed to understand what is going wrong.<br />
<br />
You can save up to 3 different states.<br />
<br />
== Debugging my game when it cannot start ==<br />
<br />
If your game won't start because of an error, you are probably in one of these situations:<br />
* There is a SQL error in your dbmodel.sql file.<br />
* You have a syntax error in your PHP file.<br />
* Your PHP "setup" - or any method used during the game initial states - generates an exception.<br />
<br />
If the error is not explicitly displayed when you click on "Express start", you should check the "Gameserver error log" as per [[Studio logs]].<br />
More cases of why game can't start are described on the [[Troubleshooting]] page.<br />
<br />
== Debugging my PHP game logic (or my view) ==<br />
<br />
Most of the time, debugging PHP is quite easy. Here's what I do when I want to develop/debug some game logic that is triggered by some game action:<br />
<br />
* At first, I make sure that I can reproduce the needed game situation with one click. To do this, I use the "[[Tools_and_tips_of_BGA_Studio#Save_.26_restore_state|save & restore]]" function.<br />
* Another possibility for this is to place a '''die('ok');''' PHP statement right after the PHP I am developing/debugging. This way, I make sure that every request will fail and then nothing will be committed to the database.<br />
* Then, I use the '''var_dump''' function to dump PHP variables and check what's wrong, until it works.<br />
<br />
Example:<br />
<pre><br />
<br />
// (...my code to debug)<br />
<br />
var_dump( $my_variable );<br />
die('ok');<br />
<br />
// (...my code to debug)<br />
<br />
</pre><br />
<br />
=== Add traces to your code ===<br />
<br />
You can use the following functions in your game to add server side logging:<br />
<br />
'''self::dump( 'name_of_variable', $variable );''' // dump variable, like var_dump but in the log debug level logging, goes to [[Studio_logs|BGA request&SQL logs]]<br />
<br />
'''self::debug( $message );''' // debug level logging, goes to [[Studio_logs|BGA request&SQL logs]]<br />
<br />
'''self::trace( $message );''' // info level logging, goes to [[Studio_logs|BGA request&SQL logs]]<br />
<br />
'''self::warn( $message );''' // warning level logging, goes to [[Studio_logs#BGA_unexpected_exceptions_logs|BGA unexpected exceptions log]]<br />
<br />
'''self::error( $message );''' // error level logging, goes to [[Studio_logs#BGA_unexpected_exceptions_logs|BGA unexpected exceptions log]]<br />
<br />
Check [[Studio logs]] for more details on how to access your logs.<br />
<br />
This can be useful when you need to follow the flow of your code and not just stop it to see how it goes at some point.<br />
<br />
Only the error log level will appear in production. This level should be used only for critical problems. <br />
Other levels will show only in the development environment and can be used as you see fit.<br />
<br />
Note: tracing likely won't work in constructor and game setup, use other methods (such as dumping on stdout)<br />
<br />
== Debugging my HTML/CSS layout ==<br />
<br />
Example situations<br />
<br />
* Why doesn't my game element show up in the interface?<br />
* Why hasn't my CSS property been applied to this element?<br />
* Why is this game element displayed at this position?<br />
<br />
A useful tip when an element does not show up in the interface is to give it a red background:<br />
<pre><br />
#my_element {<br />
... some CSS definitions ...<br />
background-color: red;<br />
}<br />
</pre><br />
<br />
This way, you know if the element is not visible because of some CSS property or because of something else.<br />
<br />
Another tip: sometimes, changing a CSS property has no visible effect on your interface. In that case, add a "display:none" property. If your element does not disappear, the bug probably comes from your CSS selector and not from your CSS property.<br />
<br />
Using Chrome "Elements" tab (the first one), you can:<br />
* See the CURRENT HTML of your page. Remember that the classical "show page source" is inefficient with BGA as you are modifying the page source with your Javascript code.<br />
* Using the "magnifying glass", you can click on any part of your game interface and check its HTML code and associated CSS styles.<br />
* You can even modify directly some CSS properties and see how it looks immediately in the game interface.<br />
<br />
When changing css you have to force reload (Ctrl+R). But it also reloads all images which is long, if you want it faster you can define this function in js file, and call it from Browser js console<br />
<pre><br />
function reloadCss() {<br />
var links = document.getElementsByTagName("link");<br />
for (var cl in links) {<br />
var link = links[cl];<br />
if (link.rel === "stylesheet")<br />
link.href += "?";<br />
}<br />
}<br />
</pre><br />
=== Debugging Toolips CSS/Layout ===<br />
<br />
To inspect tooltip you need to pin it so it does not disappear.<br />
Best way to do that is to inspect element which you have tooltip for, and in dev tools (or similar) force state to hover, i.e. select ":hov" icon and select :hover checkbox.<br />
<br />
If this does not work: open the console (dev tools) large enough window, hover to make the tooltip appear and alt + tab to focus the console that will cover the tooltip. That way the onmouseout will never be triggered.<br />
<br />
=== Debugging my Javascript game interface logic ===<br />
<br />
Compared to PHP debugging, Javascript debugging can sometimes be painful.<br />
<br />
Here are some tips to make your life easier while developing and debugging Javascript:<br />
<br />
=== Do complex things on the PHP side ===<br />
<br />
The most frequent case is the following: you want to compute possible moves in a game situation. Doing it in Javascript is a nightmare. Do it in PHP, and transfer the results to your client interface using the "args" game state property.<br />
<br />
Note: See the Reversi tutorial for an example.<br />
<br />
=== Add traces in your code ===<br />
<br />
You can use the following:<br />
<br />
'''console.log( variable_to_inspect )'''<br />
<br />
It will give you the object structure of the variable in the Javascript console, without blocking the execution.<br />
<br />
It's often a good idea to precede this call with a console.log( '### HERE ###' ); to find more easily the appropriate line in the console log.<br />
<br />
'''alert( variable_to_inspect )'''<br />
<br />
It will popup what you wish and pause the execution until you click ok.<br />
<br />
This won't be useful for complex structures; only native types will be plainly displayed. But this is sometimes useful just with messages to make sure which way the execution goes.<br />
<br />
=== Use Browser Debugger (e.g. Chrome) ===<br />
<br />
Modern browsers also allow you to put breakpoints in your js code. <br />
<br />
This will stop code execution on that line and will launch the JavaScript debugger.<br />
<br />
In Chrome, to add a breakpoint: add a line to your .js file<br />
<br />
<pre>debugger; </pre><br />
<br />
Refresh the page F5, and make sure you have the Developer tools window open, press F12. <br />
When the break-point is hit you can then step through your code and visualise variables, etc.<br />
<br />
== Online format checkers ==<br />
Copy and paste code for a quick code sanity check like the right number of brackets.<br />
<br />
PHP: [https://phpcodechecker.com/ https://phpcodechecker.com/]<br />
<br />
JS: [http://esprima.org/demo/validate.html http://esprima.org/demo/validate.html]<br />
<br />
== Some frequent errors ==<br />
<br />
See [[Troubleshooting]].<br />
<br />
== Get the database matching a bug report ==<br />
<br />
For a way to automate the below steps, see this post: https://boardgamearena.com/forum/viewtopic.php?f=12&t=16454#p63167<br />
<br />
---<br />
<br />
When a player creates a bug report in production, a snapshot of the game database is taken. You can get access to this snapshot from the studio by following the steps below:<br />
* Create a table in the studio with the same game and number of players as the table for which the report has been written. Launch this table.<br />
* Open another tab on the studio and go to "Manage game" page for your project (you have to be admin for this project)<br />
* In the "Errors in production" section, fill up the fields "Bug report ID" (this is the ID of the bug report in production) and "Studio table ID" (this is the ID of the table you created above) then click the "âš Load bug report state into this table save slot #1" button.<br />
* If the snapshot is correctly retrieved, you see a "Done!" message.<br />
* Go back to the tab with your studio table and click "Load 1".<br />
* The page refreshes automatically and is broken. This is normal, as the player ids from the snapshot are the player ids of the production, not those of the studio. We'll need to update them.<br />
** '''Important note:''' if you see a "Done!" message but clicking "Load 1" doesn't bring any change, it's that the snapshot is unfortunately not available (most likely because the bug report was declared too long after the game ended and the database had already been garbage collected to reclaim space).<br />
* Click on the "Go to game database" button<br />
* For each table using player_ids, you'll need to update the player_ids from the production to use the player_ids from the studio. You can see the player_ids from the table page before entering the game by hovering over the player names.<br />
* Tables to update:<br />
** player<br />
** global (value with ID 2 is the active player)<br />
** stats<br />
** tables specific to your schema that use player_ids<br />
* If your changes to player_ids are not taken into account, it may be a cache problem: use the "Clear PHP cache" button on your "Manage game" page.<br />
* Then you should be able to play with the same state of the game as when the report was created in production.<br />
* If the game has ended, you can place it again in the game state you want to debug by setting the value with ID 1 in the global table to the appropriate state value, and the value with ID 2 to the player you want active).<br />
*<br />
* Below is an example php function you may want to make. You can call this function from the chat window: LoadDebug() <br />
* change instances of 2308257, and 2308258 to you own BGA Studio logins YourLogin0 and YourLogin1<br />
* change $id0 and $id1 to the player_ids from the table you want to debug, and have recently imported.<br />
* Before you load Slot1, open a second tab with the table, because after loading the slot, that tab will be unusable. In the second tab you can call LoadDebug() in the chat window<br />
<br />
<pre><br />
public function LoadDebug()<br />
{<br />
<br />
// These are the id's from the BGAtable I need to debug.<br />
$id0 = '85268563';<br />
$id1 = '85278138'; <br />
<br />
//player<br />
self::DbQuery("UPDATE player SET player_id=2308257 WHERE player_id = '" . $id0 . "'" );<br />
self::DbQuery("UPDATE player SET player_id=2308258 WHERE player_id = '" . $id1 . "'" );<br />
<br />
//global <br />
self::DbQuery("UPDATE global SET global_value=2308257 WHERE global_value = '" . $id0 . "'" );<br />
self::DbQuery("UPDATE global SET global_value=2308258 WHERE global_value = '" . $id1 . "'" );<br />
<br />
//stats<br />
self::DbQuery("UPDATE stats SET stats_player_id=2308257 WHERE stats_player_id = '" . $id0 . "'" );<br />
self::DbQuery("UPDATE stats SET stats_player_id=2308258 WHERE stats_player_id = '" . $id1 . "'" ); <br />
<br />
// 'other' game specific tables. example:<br />
// tables specific to your schema that use player_ids<br />
self::DbQuery("UPDATE card SET card_location_arg=2308257 WHERE card_location_arg = '" . $id0 . "'" );<br />
self::DbQuery("UPDATE card SET card_location_arg=2308258 WHERE card_location_arg = '" . $id1 . "'" );<br />
}<br />
</pre></div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Game_interface_logic:_yourgamename.js&diff=5245
Game interface logic: yourgamename.js
2020-08-12T07:26:29Z
<p>Cpasbanal: /* Scoring animated display */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game interface. Here you will define:<br />
<br />
* Which actions on the page will generate calls to the server.<br />
* What happens when you get a notification for a change from the server and how it will show in the browser. <br />
<br />
== File structure ==<br />
<br />
The details of how the file is structured are described below with comments on the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* '''constructor''': here you can define global variables for your whole interface.<br />
* '''setup''': this method is called when the page is refreshed, and sets up the game interface.<br />
* '''onEnteringState''': this method is called when entering a new game state. You can use it to customize the view for each game state.<br />
* '''onLeavingState''': this method is called when leaving a game state.<br />
* '''onUpdateActionButtons''': called when entering a new state, in order to add action buttons to the status bar.<br />
* ''(utility methods)'': this is where you can define your utility methods.<br />
* ''(player's actions)'': this is where you can write your handlers for player actions on the interface (example: click on an item).<br />
* '''setupNotifications''': this method associates notifications with notification handlers. For each game notification, you can trigger a javascript method to handle it and update the game interface.<br />
* ''(notification handlers)'': this is where you define the notifications handlers associated with notifications in '''setupNotifications''', above.<br />
<br />
== General tips ==<br />
<br />
; this.player_id<br />
: ID of the player on whose browser the code is running.<br />
<br />
; this.isSpectator<br />
: Flag set to true if the user at the table is a spectator (not a player).<br />
: Note: This is a variable, not a function.<br />
: Note: If you want to hide an element from spectators, you should use [[Game_interface_stylesheet:_yourgamename.css#spectatorMode|CSS 'spectatorMode' class]].<br />
<br />
; this.gamedatas<br />
: Contains the initial set of data to init the game, created at game start or by game refresh (F5).<br />
: You can update it as needed to keep an up-to-date reference of the game on the client side if you need it. (Most of the time this is unnecessary).<br />
<br />
; this.isCurrentPlayerActive()<br />
: Returns true if the player on whose browser the code is running is currently active (it's his turn to play).<br />
<br />
; this.getActivePlayerId()<br />
: Return the ID of the active player, or null if we are not in an "activeplayer" type state.<br />
<br />
; this.getActivePlayers()<br />
: Return an array with the IDs of players who are currently active (or an empty array if there are none).<br />
<br />
; this.bRealtime<br />
: Return true if the game is in realtime. Note that having a distinct behavior in realtime and turn-based should be exceptional.<br />
<br />
; typeof g_replayFrom != 'undefined'<br />
: Returns true if the game is in instant replay mode (replay during the game)<br />
<br />
; g_archive_mode<br />
: Returns true if the game is in archive mode (advanced replay after the game has ended)<br />
<br />
== Dojo framework ==<br />
<br />
BGA uses the [http://dojotoolkit.org/ Dojo Javascript framework].<br />
<br />
The Dojo framework allows us to do complex things more easily. The BGA framework uses Dojo extensively.<br />
<br />
To implement a game, you only need to use a few parts of the Dojo framework. All the Dojo methods you need are described on this page.<br />
<br />
== Javascript minimization (before July 2020) ==<br />
<br />
For performance reasons, when deploying a game the js code is minimized using ShrinkSafe (based on ECMASCRIPT version 3). Some advanced syntax may not be compatible with this process. In particular:<br />
* You should not use reserved keywords from the javascript language as variables.<br />
* You should not declare default argument values in function declarations. The following syntax is invalid for ShrinkSafe: '''function myFunc(requiredArg, optionalArg = 'defaultValue') {}'''<br />
* You should not use '''let''' or '''const''' to declare variables.<br />
<br />
'''Tip:''' a developer encountering some problems with this has successfully used [http://plugins.netbeans.org/plugin/58580/jshint JSHint] on NetBeans to evaluate code to make it compatible for ECMAScript 3. With the plugin installed, set the below options in '''.jshintrc''' file, and then open '''Action Items''' window (in NetBeans):<pre>{ "maxerr": 999, "esversion": 3 }</pre><br />
<br />
'''Tip:''' some online tools also allow to convert between different versions of javascript, such as https://www.typescriptlang.org/play or https://babeljs.io/ or https://extendsclass.com/javascript-fiddle.html<br />
<br />
== Javascript minimization (after July 2020) ==<br />
<br />
For performance reasons, when deploying a game the javascript code is minimized using '''terser''' (https://github.com/terser/terser). This minifier works with modern javascript syntax. From your project "Manage game" page, you can now test a minified version of jour javascript on the studio (and revert to the original).<br />
<br />
== Accessing and manipulating the DOM ==<br />
<br />
'''$('some_html_element_id')'''<br />
<br />
The $() function is used to get an HTML element using its "id" attribute.<br />
<br />
Example 1: modify the content of a "span" element:<br />
<br />
<pre><br />
In your HTML code:<br />
<span id="a_value_in_the_game_interface">1234</span><br />
<br />
In your Javascript code:<br />
$('a_value_in_the_game_interface').innerHTML = "9999";<br />
</pre><br />
<br />
Note: $() is the standard method to access some HTML element with the BGA Framework. You should not use the '''getElementById''' function.<br />
<br />
<br />
'''dojo.style'''<br />
<br />
With dojo.style you can modify the CSS property of any HTML element in your interface.<br />
<br />
Examples:<br />
<br />
<pre><br />
// Make an element disappear<br />
dojo.style( 'my_element', 'display', 'none' );<br />
<br />
// Give an element a 2px border<br />
dojo.style( 'my_element', 'borderWidth', '2px' );<br />
<br />
// Change the background position of an element<br />
// (very practical when you are using CSS sprites to transform an element to another)<br />
dojo.style( 'my_element', 'backgroundPosition', '-20px -50px' );<br />
</pre><br />
<br />
Note: you must always use dojo.style to modify the CSS properties of HTML elements.<br />
<br />
NoteÂČ: if you have to modify several CSS properties of an element, or if you have a complex CSS transformation to do, you should consider using dojo.addClass/dojo.removeClass (see below).<br />
<br />
'''dojo CSS classes manipulation'''<br />
<br />
In many situations, many small CSS property updates can be replaced by a CSS class change (i.e., you add a CSS class to your element instead of applying all modifications manually).<br />
<br />
Advantages are:<br />
* All your CSS stuff remains in your CSS file.<br />
* You can add/remove a list of CSS modifications with a simple function and without error.<br />
* You can test whether you applied the CSS to an element with the '''dojo.hasClass''' method.<br />
<br />
Example from ''Reversi'':<br />
<br />
<pre><br />
// We add "possibleMove" to an element<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
<br />
// In our CSS file, the class is defined as:<br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
<br />
// So we've applied 4 CSS property changes in one line of code.<br />
<br />
// ... and when we need to check if a square is a possible move on the client side:<br />
if( dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{ ... }<br />
<br />
// ... and if we want to remove all possible moves in one line of code (see "dojo.query" method):<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
</pre><br />
<br />
Conclusion: We encourage you to use '''dojo.addClass''', '''dojo.removeClass''' and '''dojo.hasClass''' to make your life easier :)<br />
<br />
'''dojo.query'''<br />
<br />
With dojo.query, you can query a bunch of HTML elements with a single function, with a "CSS selector" style.<br />
<br />
Example:<br />
<pre><br />
// All elements with class "possibleMove":<br />
var elements = dojo.query( '.possibleMove' );<br />
<br />
// Count number of tokens (i.e., elements of class "token") on the board (i.e., the element with id "board"):<br />
dojo.query( '#board .token' ).length;<br />
</pre><br />
<br />
But what is really cool with dojo.query is that you can combine it with almost all methods above.<br />
<br />
Examples:<br />
<pre><br />
// Trigger a method when the mouse enter in any element with class "meeple":<br />
dojo.query( '.meeple' ).connect( 'onmouseenter', this, 'myMethodToTrigger' );<br />
<br />
// Hide all meeples who are on the board<br />
dojo.query( '#board .meeple' ).style( 'display', 'none' );<br />
</pre><br />
<br />
'''dojo.place'''<br />
<br />
dojo.place is the best function to insert HTML code somewhere in your game interface without breaking something. It is much better to use than the '''innerHTML=''' method if you must insert HTML tags and not only values.<br />
<br />
<pre><br />
// Insert your HTML code as a child of a container element<br />
dojo.place( "<your html code>", "your_container_element_id" );<br />
<br />
// Replace the container element with your new html<br />
dojo.place( "<your html code>", "your_container_element_id", "replace" );<br />
<br />
</pre><br />
<br />
The third parameter of dojo.place can take various interesting values:<br />
<br />
"replace" : (see description above).<br />
<br />
"first" : Places the node as a child of the reference node. The node is placed as the first child.<br />
<br />
"last" (default) : Places the node as a child of the reference node. The node is placed as the last child.<br />
<br />
"before" : places the node right before the reference node.<br />
<br />
"after" : places the node right after the reference node.<br />
<br />
"only" : replaces all children of the reference node with the node.<br />
<br />
positif integer : This parameter can be a positif integer. In this case, the node will be placed as a child of the reference node with this number (counting from 0). If the number is more than number of children, the node will be appended to the reference node making it the last child. <br />
<br />
See also full doc on dojo.place : [https://dojotoolkit.org/reference-guide/1.7/dojo/place.html]<br />
<br />
Usually, when you want to insert some piece of HTML in your game interface, you should use "[[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]".<br />
<br />
'''addStyleToClass: function( cssClassName, cssProperty, propertyValue )'''<br />
<br />
Same as dojo.style(), but for all the nodes set with the specified cssClassName<br />
<br />
=== Animations ===<br />
<br />
'''Dojo Animations'''<br />
<br />
BGA animations is based on Dojo Animation ([http://dojotoolkit.org/documentation/tutorials/1.8/animation/ see tutorial here]).<br />
<br />
However, most of the time, you can just use methods below, which are built on top of Dojo Animation.<br />
<br />
Note: one interesting method from Dojo that could be useful from time to time is "Dojo.Animation". It allows you to make any CSS property "slide" from one value to another.<br />
<br />
Note 2: the slideTo methods are not compatible with CSS transform (scale, zoom, rotate...). If possible, avoid using CSS transform on nodes that are being slided. Eventually, the only possible solution to make these 2 compatible is to disable all CSS transform properties, use slideToObjectPos/placeOnObjectPos, and then apply them again.<br />
<br />
<br />
'''this.slideToObject( mobile_obj, target_obj, duration, delay )'''<br />
<br />
You can use slideToObject to "slide" an element to a target position.<br />
<br />
Sliding element on the game area is the recommended and the most used way to animate your game interface. Using slides allow players to figure out what is happening on the game, as if they were playing with the real boardgame.<br />
<br />
The parameters are:<br />
* mobile_obj: the ID of the object to move. This object must be "relative" or "absolute" positioned.<br />
* target_obj: the ID of the target object. This object must be "relative" or "absolute" positioned. Note that it is not mandatory that mobile_obj and target_obj have the same size. If their size are different, the system slides the center of mobile_obj to the center of target_obj.<br />
* duration: (optional) defines the duration in millisecond of the slide. The default is 500 milliseconds.<br />
* delay: (optional). If you defines a delay, the slide will start only after this delay. This is particularly useful when you want to slide several object from the same position to the same position: you can give a 0ms delay to the first object, a 100ms delay to the second one, a 200ms delay to the third one, ... this way they won't be superposed during the slide.<br />
<br />
BE CAREFUL: The method returns an dojo.fx animation, so you can combine it with other animation if you want to. It means that you have to call the "play()" method, otherwise the animation WON'T START.<br />
<br />
Example:<br />
<pre><br />
this.slideToObject( "some_token", "some_place_on_board" ).play();<br />
</pre><br />
<br />
<br />
'''this.slideToObjectPos( mobile_obj, target_obj, target_x, target_y, duration, delay )'''<br />
<br />
This method does exactly the same as "slideToObject", except than you can specify some (x,y) coordinates. This way, "mobile_obj" will slide to the specified x,y position relatively to "target_obj".<br />
<br />
Example: slide a token to some place on the board, 10 pixels to the bottom:<br />
<pre><br />
this.slideToObjectPos( "some_token", "some_place_on_board", 0, 10 ).play();<br />
</pre><br />
<br />
'''this.slideTemporaryObject( mobile_obj_html, mobile_obj_parent, from, to, duration, delay )'''<br />
<br />
This method is useful when you want to slide a temporary HTML object from one place to another. As this object does not exists before the animation and won't remain after, it could be complex to create this object (with dojo.place), to place it at its origin (with placeOnObject) to slide it (with slideToObject) and to make it disappear at the end.<br />
<br />
slideTemporaryObject does all of this for you:<br />
* mobile_obj_html is a piece of HTML code that represent the object to slide.<br />
* mobile_obj_parent is the ID of an HTML element of your interface that will be the parent of this temporary HTML object.<br />
* from is the ID of the origin of the slide.<br />
* to is the ID of the target of the slide.<br />
* duration/delay works exactly like in "slideToObject"<br />
<br />
Example:<br />
<pre><br />
this.slideTemporaryObject( '<div class="token_icon"></div>', 'tokens', 'my_origin_div', 'my_target_div' );<br />
</pre><br />
<br />
'''this.slideToObjectAndDestroy: function( node, to, time, delay )'''<br />
<br />
This method is a handy shortcut to slide an existing HTML object to some place then destroy it upon arrival. It can be used for example to move a victory token or a card from the board to the player panel to show that the player earns it, then destroy it when we don't need to keep it visible on the player panel.<br />
<br />
It works the same as this.slideToObject and takes the same arguments. <br />
<br />
Example:<br />
<pre><br />
this.slideToObjectAndDestroy( "some_token", "some_place_on_board", 1000, 0 );<br />
</pre><br />
<br />
'''this.fadeOutAndDestroy( node, duration, delay )'''<br />
<br />
This function fade out the target HTML node, then destroy it.<br />
<br />
Example:<br />
<pre><br />
this.fadeOutAndDestroy( "a_card_that_must_disappear" );<br />
</pre><br />
<br />
CAREFUL: the HTML node still exists until during few milliseconds, until the fadeOut has been completed.<br />
<br />
'''Rotating elements'''<br />
<br />
You can check here [http://jimfulton.info/demos/dojo-animated-rotate.html an example of use] of Dojo to make an element rotate.<br />
<br />
This example combines "Dojo.Animation" method and a CSS3 property that allow you to rotate the element.<br />
<br />
IMPORTANT: to asses browser compatibility, you must select the CSS property to use just like in the example (see sourcecode below):<br />
<br />
<pre><br />
var transform;<br />
dojo.forEach(<br />
['transform', 'WebkitTransform', 'msTransform',<br />
'MozTransform', 'OTransform'],<br />
function (name) {<br />
if (typeof dojo.body().style[name] != 'undefined') {<br />
transform = name;<br />
}<br />
});<br />
// ... and then use "transform" as the name of your CSS property for rotation<br />
</pre><br />
<br />
'''Animation Callbacks'''<br />
<br />
If you wish to run some code only after an animation has completed you can do this by linking a callback method.<br />
<br />
<pre><br />
var animation_id = this.slideToObject( mobile_obj, target_obj, duration, delay );<br />
dojo.connect(animation_id, 'onEnd', dojo.hitch(this, 'callback_function', parameters));<br />
animation_id.play();<br />
<br />
âŠ<br />
<br />
callback_function: function(params) {<br />
// this will be called after the animation ends<br />
},<br />
</pre><br />
<br />
If you wish to call a second animation after the first (rather than general code) then you can use a dojo animation chain (see tutorial referenced above).<br />
<br />
=== Moving elements ===<br />
<br />
'''this.placeOnObject( mobile_obj, target_obj )'''<br />
<br />
placeOnObject works exactly like "slideToObject", except that the effect is immediate.<br />
<br />
This is not really an animation, but placeOnObject is frequently used before starting an animation.<br />
<br />
Example:<br />
<pre><br />
// (We just created an object "my_new_token")<br />
<br />
// Place the new token on current player board<br />
this.placeOnObject( "my_new_token", "overall_player_board_"+this.player_id );<br />
<br />
// Then slide it to its position on the board<br />
this.slideToObject( "my_new_token", "a_place_on_board" ).play();<br />
</pre><br />
<br />
'''this.placeOnObjectPos( mobile_obj, target_obj, target_x, target_y )'''<br />
<br />
This method works exactly like placeOnObject, except than you can specify some (x,y) coordinates. This way, "mobile_obj" will be placed to the specified x,y position relatively to "target_obj".<br />
<br />
'''this.attachToNewParent( mobile_obj, target_obj )'''<br />
<br />
With this method, you change the HTML parent of "mobile_obj" element. "target_obj" is the new parent of this element. The beauty of <br />
attachToNewParent is that the mobile_obj element DOES NOT MOVE during this process.<br />
<br />
Note: what happens is that the method calculate a relative position of mobile_obj to make sure it does not move after the HTML parent changes.<br />
<br />
Why using this method?<br />
<br />
Changing the HTML parent of an element can be useful for the following reasons:<br />
* When the HTML parent moves, all its child are moving with them. If some game elements is no more linked with a parent HTML object, you may want to attach it to another place.<br />
* The z_order (vertical order of display) depends on the position in the DOM, so you may need to change the parent of some game elements when they are moving in your game area.<br />
<br />
CAREFUL: this function destroys original object and places a clone onto a new parent, this will break all references to this HTML element (ex: dojo.connect).<br />
<br />
== Players input ==<br />
<br />
'''dojo.connect'''<br />
<br />
Used to associate a player event with one of your notification method.<br />
<br />
Example: associate a click on an element ("my_element") with one of our method ("onClickOnMyElement"):<br />
<pre><br />
dojo.connect( $('my_element'), 'onclick', this, 'onClickOnMyElement' );<br />
</pre><br />
<br />
Note: this is the only possible correct way to associate a player input event to your code, and you must not use anything else.<br />
<br />
'''this.checkAction( "my_action_name" )'''<br />
<br />
Usage: checkAction: function( action, nomessage )<br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
Restricted arguments names (please don't use them):<br />
<br />
* "action"<br />
* "module"<br />
* "class"<br />
return true if action is authorized (ie: the action is listed as a "possibleaction" in current game state).<br />
<br />
return false and display an error message if not (display no message if nomessage parameter is true). The displayed error message could be either "This move is not allowed at this moment" or "An action is already in progress".<br />
<br />
Example:<br />
<pre><br />
function onClickOnGameElement( evt )<br />
{<br />
if( this.checkAction( "my_action" ) )<br />
{<br />
// Do the action<br />
}<br />
}<br />
</pre><br />
<br />
'''this.checkPossibleActions( "my_action_name" )'''<br />
<br />
Usage: checkPossibleActions: function( action, nomessage )<br />
<br />
* this is independent of the player being active, so can be used instead of this.checkAction(). This is particularly useful for multiplayer states when the player is not active in a 'player may like to change their mind' scenario. <br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
Restricted arguments names (please don't use them):<br />
* "action"<br />
* "module"<br />
* "class"<br />
<br />
'''this.ajaxcall( url, parameters, obj_callback, callback, callback_error )'''<br />
<br />
This method must be used to send a player input to the game server.<br />
<br />
* url: the url of the action to perform. For a game, it must be: "/<mygame>/<mygame>/myAction.html"<br />
* parameters: an array of parameter to send to the game server. Note that "lock:true" must always be specified in this list of parameter in order the interface can be locked during the server call.<br />
* obj_callback: must be set to "this".<br />
* callback: a function to trigger when the server returns and everything went fine.<br />
* callback_error: (optional and rarely used) a function to trigger when the server returns an error. if no error this function is called with parameter value false.<br />
<br />
Usage:<br />
<pre><br />
this.ajaxcall( '/mygame/mygame/myaction.html', { lock: true, <br />
arg1: myarg1, <br />
arg2: myarg2, <br />
...<br />
}, this, function( result ) {<br />
// Do some stuff after a successful call<br />
} );<br />
</pre><br />
<br />
'''this.confirmationDialog()'''<br />
<br />
Display a confirmation dialog with a yes/no choice.<br />
<br />
We advice you to NOT use this function unless the player action is really critical and could ruins the game, because it slows down the game and upset players.<br />
<br />
Usage: this.confirmationDialog( "Question to displayed", callback_function_if_click_on_yes );<br />
<br />
Example:<br />
<pre><br />
this.confirmationDialog( _('Are you sure to use this bonus (points penalty at the end of the game) ?'),<br />
dojo.hitch( this, function() {<br />
this.ajaxcall( '/seasons/seasons/useBonus.html',<br />
{ id:bonus_id, lock:true }, this, function( result ) {} );<br />
} ) ); <br />
</pre><br />
<br />
<br />
; addEventToClass: function( cssClassName, eventName, functionName )<br />
: Same as dojo.connect(), but for all the nodes set with the specified cssClassName<br />
<br />
'''this.addActionButton( id, label, method, (opt)destination, (opt)blinking, (opt)color )'''<br />
<br />
You can use this method to add an action button in the main action status bar.<br />
<br />
Arguments:<br />
* id: an element ID that should be unique in your HTML DOM document.<br />
* label: the text of the button. Should be translatable (use _() function).<br />
* method: the name of your method that must be triggered when the player clicks on this button.<br />
* destination (optional): deprecated, do not use this. Use '''null''' as value if you need to specify other arguments.<br />
* blinking (optional): if set to '''true''', the button is going blink to catch player's attention. Please don't abuse of blinking button.<br />
* color: could be '''blue''' (default), '''red''' or '''gray'''.<br />
<br />
You should only use this method in your "onUpdateActionButtons" method. Usually, you use it like this (from Hearts example):<br />
<br />
<pre><br />
onUpdateActionButtons: function( stateName, args ) {<br />
<br />
if (this.isCurrentPlayerActive()) { <br />
switch( stateName ) {<br />
case 'giveCards':<br />
this.addActionButton( 'giveCards_button', _('Give selected cards'), 'onGiveCards' ); <br />
break;<br />
}<br />
}<br />
}, <br />
</pre><br />
<br />
In the example above, we are adding a "Give selected cards" button in the case we are on game state "giveCards". When player clicks on this button, it triggers our "onGiveCards" method.<br />
<br />
Example using blinking red button:<br />
<pre><br />
this.addActionButton( 'commit_button', _('Confirm'), 'onConfirm', null, true, 'red'); <br />
</pre><br />
<br />
Note: at least in studio example above will make button huge, because it sets it display of blinking things to '''block''', <br />
if you don't like it you have to change css display value<br />
of the button to inline-block (the id of the button is the first argument, i.e 'commit_button' in example above)<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Notifications ==<br />
<br />
When something happens on the server side, your game interface Javascript logic received a notification.<br />
<br />
Here's how you can handle these notifications on the client side.<br />
<br />
=== Subscribe to notifications ===<br />
<br />
Your Javascript "setupNotifications" method is the place where you can subscribe to notifications from your PHP code.<br />
<br />
Here's how you associate one of your Javascript method to a notification "playDisc" (from Reversi example):<br />
<br />
<pre><br />
// In setupNotifications method:<br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
</pre><br />
<br />
Note: the "playDisc" corresponds to the name of the notification you define it in your PHP code, in your "notifyAllPlayers" or "notifyPlayer" method.<br />
<br />
Then, you have to define your "notif_playDisc" method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addDiscOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
In a notification handler like our "notif_playDisc" method, you can access to all notifications arguments with "notif.args".<br />
<br />
Example:<br />
<pre><br />
// If you did this on PHP side:<br />
self::notifyAllPlayers( "myNotification", '', array( "myArgument" => 3 ) );<br />
<br />
// On Javascript side, you can access the "myArgument" like this:<br />
notif_myNotification: function( notif )<br />
{<br />
alert( "myArgument = " + notif.args.myArgument );<br />
}<br />
</pre><br />
<br />
<br />
=== Synchronous notifications ===<br />
<br />
When several notifications are received by your game interface, these notifications are processed immediately, one after the other, in the same exact order they have been generated in your PHP game logic.<br />
<br />
However, sometimes, you need to give some time to the players to figure out what happened on the game before jumping to the next notification. Indeed, in many games, they are a lot of automatic actions, and the computer is going to resolve all these actions very fast if you don't tell it not to do so.<br />
<br />
As an example, for Reversi, when someone is playing a disc, we want to wait 500 milliseconds before doing anything else in order the opponent player can figure out what move has been played.<br />
<br />
Here's how we do this, right after our subscription:<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 ); // Wait 500 milliseconds after executing the playDisc handler<br />
</pre><br />
<br />
<br />
=== Pre-defined notification types ===<br />
<br />
'''tableWindow''' - This defines notification to display [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialogs]], see below.<br />
<br />
'''message''' - This defines notification that shows on players log and have no other effect<br />
<br />
// You can call this on php side without doing anything on client side<br />
self::notifyAllPlayers( 'message', 'hello', array( ) );<br />
<br />
== Tooltips ==<br />
<br />
'''this.addTooltip( nodeId, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to the DOM node.<br />
<br />
Specify 'helpString' to display some information about "what is this game element?".<br />
Specify 'actionString' to display some information about "what happens when I click on this element?".<br />
<br />
You must specify both helpString and actionString. Most of the time, you should use only one and specify a void string ("") for the other one.<br />
<br />
Usually, _() must be used for the text to be marked for translation.<br />
<br />
"Delay" is an optional parameter. Usually, it is primarily used to specify a zero delay for some game element when the tooltip gives really important information for the game - but remember: no essential information must be placed in tooltips as they won't be displayed in some browsers (see [[BGA_Studio_Guidelines|Guidelines]]).<br />
<br />
Example:<br />
<pre><br />
this.addTooltip( 'cardcount', _('Number of cards in hand'), '' );<br />
</pre><br />
<br />
'''this.addTooltipHtml( nodeId, html, delay )'''<br />
<br />
Add an HTML tooltip to the DOM node (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
'''this.addTooltipToClass( cssClass, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to all the DOM nodes set with this cssClass. <br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips.<br />
<br />
'''this.addTooltipHtmlToClass( cssClass, html, delay )'''<br />
<br />
Add an HTML tooltip to to all the DOM nodes set with this cssClass (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips<br />
<br />
'''this.removeTooltip( nodeId )'''<br />
<br />
Remove a tooltip from the DOM node.<br />
<br />
== Dialogs, warning messages, confirmation dialogs, ... ==<br />
<br />
=== Warning messages ===<br />
<br />
Sometimes, there is something important that is happening on the game and you have to make sure all players get the message. Most of the time, the evolution of the game situation or the game log is enough, but sometimes you need something more visible.<br />
<br />
Ex: someone fulfill one of the end of the game condition, so this is the last turn.<br />
<br />
'''this.showMessage( msg, type )'''<br />
<br />
showMessage shows a message in a big rectangular area on the top of the screen of current player.<br />
<br />
* "msg" is the string to display. It should be translated.<br />
* "type" can be set to "info" or "error". If set to "info", the message will be an informative message on a white background. If set to "error", the message will be an error message on a red background.<br />
<br />
Important: the normal way to inform players about the progression of the game is the game log. "showMessage" is intrusive and should not be used often.<br />
<br />
=== Confirmation dialog ===<br />
<br />
'''confirmationDialog( message, yesHandler, noHandler )'''<br />
<br />
<br />
When an important action with a lot of consequences is triggered by the player, you may want to propose a confirmation dialog.<br />
<br />
CAREFUL: the general guidelines of BGA is to AVOID the use of confirmation dialog. Confirmation dialogs slow down the game and bother players. The players knows that they have to pay attention about each move when they are playing online.<br />
<br />
The situation where you should use a confirmation dialog are the following:<br />
* It must not happen very often during a game.<br />
* It must be linked to an action that can really "kill a game" if the player do not pay attention.<br />
* It must be something that can be done by mistake (ex: a link on the action status bar).<br />
<br />
How to display a confirmation dialog:<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to bake the pie?'), dojo.hitch( this, function() {<br />
this.bakeThePie();<br />
} ) ); <br />
return; // nothing should be called or done after calling this, all action must be done in the handler <br />
</pre><br />
<br />
=== Multiple choice dialog ===<br />
You can use this dialog to give user a choice with small amount of options:<br />
<pre><br />
var keys = [1,5,10];<br />
this.multipleChoiceDialog(<br />
_('How many bugs to fix?'), keys, <br />
dojo.hitch(this, function(choice) {<br />
var bugchoice = keys[choice];<br />
console.log('dialog callback with '+bugchoice);<br />
this.ajaxcall( '/mygame/mygame/fixBugs.html', { bugs: bugchoice}, this, function( result ) {} ); }));<br />
</pre><br />
<br />
=== Dialogs ===<br />
<br />
As a general rule, you shouldn't use dialogs windows.<br />
<br />
BGA guidelines specify that all game elements should be displayed on the main screen. Players can eventually scroll down to see game elements they don't need to see anytime, and you may eventually create anchors to move between game area section. Of course dialogs windows are very practical, but the thing is: all players know how to scroll down, and not all players know how to show up your dialog window. In addition, when the dialog shows up, players can't access the other game components.<br />
<br />
Sometimes although, you need to display a dialog window. Here is how you do this:<br />
<br />
<br />
<br />
// Create the new dialog over the play zone. You should store the handler in a member variable to access it later<br />
this.myDlg = new ebg.popindialog();<br />
this.myDlg.create( 'myDialogUniqueId' );<br />
this.myDlg.setTitle( _("my dialog title to translate") );<br />
this.myDlg.setMaxWidth( 500 ); // Optional<br />
<br />
// Create the HTML of my dialog. <br />
// The best practice here is to use [[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]<br />
var html = this.format_block( 'jstpl_myDialogTemplate', { <br />
arg1: myArg1,<br />
arg2: myArg2,<br />
...<br />
} ); <br />
<br />
// Show the dialog<br />
this.myDlg.setContent( html ); // Must be set before calling show() so that the size of the content is defined before positioning the dialog<br />
this.myDlg.show();<br />
<br />
// Now that the dialog has been displayed, you can connect your method to some dialog elements<br />
// Example, if you have an "OK" button in the HTML of your dialog:<br />
dojo.connect( $('my_ok_button'), 'onclick', this, function(evt){<br />
evt.preventDefault();<br />
this.myDlg.destroy();<br />
} );<br />
<br />
If necessary, you can remove the default top right corner 'close' icon, or replace the function called when it is clicked:<br />
// Removes the default close icon<br />
this.myDlg.hideCloseIcon();<br />
<br />
// Replace the function call when it's clicked<br />
this.myDlg.replaceQuitCallback( function() { ... } );<br />
<br />
=== Scoring dialogs ===<br />
<br />
Sometimes at the end of a round you want to display a big table that details the points wins in each section of the game.<br />
<br />
Example: in Hearts game, we display at the end of each round the number of "heart" cards collected by each player, the player who collected the Queen of Spades, and the total number of points loose by each player.<br />
<br />
Scoring dialogs are managed entirely on '''PHP side''', but they are described here as their effects are visible only on client side.<br />
<br />
Displaying a scoring dialog is quite simple and is using a special notification type: "tableWindow":<br />
<pre><br />
// on PHP side:<br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table<br />
) ); <br />
</pre><br />
<br />
The "table" argument is a 2 dimensional PHP array that describe the table you want to display, line by line and column by column.<br />
<br />
Example: display an 3x3 array of strings<br />
<pre><br />
$table = array(<br />
array( "one", "two", "three" ), // This is my first line<br />
array( "four", "five", "six" ), // This is my second line<br />
array( "seven", "height", "nine" ) // This is my third line<br />
);<br />
</pre><br />
<br />
As you can see above, in each "cell" of your array you can display a simple string value. But you can also display a complex value with a template and associated arguments like this:<br />
<pre><br />
$table = array(<br />
array( "one", "two", array( "str" => clienttranslate("a string with an ${argument}"), "args" => array( 'argument' => 'argument_value' ) ) ),<br />
array( "four", "five", "six" ), <br />
array( "seven", "height", "nine" )<br />
);<br />
</pre><br />
<br />
This is especially useful when you want to display player names with colors. Example from "Hearts":<br />
<pre><br />
$firstRow = array( '' );<br />
foreach( $players as $player_id => $player )<br />
{<br />
$firstRow[] = array( 'str' => '${player_name}',<br />
'args' => array( 'player_name' => $player['player_name'] ),<br />
'type' => 'header'<br />
);<br />
}<br />
$table[] = $firstRow;<br />
</pre><br />
<br />
You can also use three extra attributes in the parameter array for the notification:<br />
<br />
<pre><br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table,<br />
"header" => array('str' => clienttranslate('Table header with parameter ${number}'),<br />
'args' => array( 'number' => 3 ),<br />
),<br />
"footer" => '<div>Some footer</div>',<br />
"closing" => clienttranslate( "Closing button label" )<br />
) ); <br />
</pre><br />
<br />
*'''header''': the content for this parameter will display before the table (also, the html will be parsed and player names will be colored according to the current game colors). <br />
*'''footer''': the content for this parameter will display after the table (no parsing for coloring the player names)<br />
*'''closing''': if this parameter is used, a button will be displayed with this label at the bottom of the popup and will allow players to close it (more easily than by clicking the top right 'cross' icon).<br />
<br />
=== Scoring animated display ===<br />
<br />
Sometimes (Terra Mystica final scoring for example), you may want to display a score value over an element to make the scoring easier to follow for the players.<br />
You can do it with:<br />
<br />
<pre><br />
this.displayScoring( anchor_id, color, score, duration );<br />
</pre><br />
<br />
'''anchor_id''': ID of the element to place the animated score onto (without the '#') <br />
<br />
'''color''': hexadecimal RGB representation of the color (should be the color of the scoring player), but without a leading '#'. For instance, 'ff0000' for red.<br />
<br />
'''score''': numeric score to display, prefixed by a '+'<br />
<br />
'''duration''': animation duration in milliseconds<br />
<br />
<br />
Note: if you want to display successively each score, you can use ''this.notifqueue.setSynchronous()'' function.<br />
<br />
=== Speech bubble ===<br />
<br />
For better interactivity in some games (Love Letter for example), you may use comic book style speech bubbles to express the players voices.<br />
This is done with:<br />
<br />
<pre><br />
this.showBubble( anchor_id, text, delay, duration, custom_class )<br />
</pre><br />
<br />
delay in milliseconds is optional (default 0)<br />
<br />
duration in milliseconds is optional (default 3000)<br />
<br />
custom_class is optional, if you need to override the default bubble style<br />
<br />
'''Warning''': if your bubble could overlap other active elements of the interface (buttons in particular), as it stays in place even after disappearing, you should use a custom class to give it the style "pointer-events: none;" in order to intercept click events.<br />
<br />
== Update players score ==<br />
<br />
Increase a player score (with a positive or negative number):<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].incValue( score_delta );<br />
</pre><br />
<br />
Set a player score to a specific value:<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].setValue( new_score );<br />
</pre><br />
<br />
Set a player score to a specific value with animation :<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].toValue( new_score );<br />
</pre><br />
<br />
== Players panels ==<br />
<br />
=== Adding stuff to player's panel ===<br />
<br />
At first, create a new "JS template" string in your template (tpl) file:<br />
<br />
(from Gomoku example)<br />
<pre><br />
var jstpl_player_board = '\<div class="cp_board">\<br />
<div id="stoneicon_p${id}" class="gmk_stoneicon gmk_stoneicon_${color}"></div><span id="stonecount_p${id}">0</span>\<br />
</div>';<br />
</pre><br />
<br />
Then, you add this piece of code in your JS file to add this template to each player panel:<br />
<br />
<pre><br />
// Setting up player boards<br />
for( var player_id in gamedatas.players )<br />
{<br />
var player = gamedatas.players[player_id];<br />
<br />
// Setting up players boards if needed<br />
var player_board_div = $('player_board_'+player_id);<br />
dojo.place( this.format_block('jstpl_player_board', player ), player_board_div );<br />
}<br />
</pre><br />
<br />
(Note: the code above is of course from your "setup" function in your Javascript).<br />
<br />
Very often, you have to distinguish current player and others players. In this case, you just have to create another JS template (ex: jstpl_otherplayer_board) and use it when "player_id" is different than "this.player_id".<br />
<br />
=== Player's panel disabling/enabling ===<br />
<br />
'''this.disablePlayerPanel( player_id )'''<br />
<br />
Disable given player panel (the panel background become gray).<br />
<br />
Usually, this is used to signal that this played passes, or will be inactive during a while.<br />
<br />
Note that the only effect of this is visual. There are no consequences on the behaviour of the panel itself.<br />
<br />
'''this.enablePlayerPanel( player_id )'''<br />
<br />
Enable a player panel that has been disabled before.<br />
<br />
'''this.enableAllPlayerPanels()'''<br />
<br />
Enable all player panels that has been disabled before.<br />
<br />
== Image loading ==<br />
<br />
See also [[Game_art:_img_directory]].<br />
<br />
'''Be careful''': by default, ALL images of your img directory are loaded on a player's browser when he loads the game. For this reason, don't let in your img directory images that are not useful, otherwise it's going to slowdown the game load.<br />
<br />
'''dontPreloadImage( image_file_name )'''<br />
<br />
Using dontPreloadImage, you tell the interface to not preload a specific image in your img directory.<br />
<br />
Example of use:<br />
<pre><br />
this.dontPreloadImage( 'cards.png' );<br />
</pre><br />
<br />
This is particularly useful if for example you have 2 different themes for a game. To accelerate the loading of the game, you can specify to not preload images corresponding to the other theme.<br />
<br />
Another example of use: in "Gosu" game with Kamakor extension, you play with 5 sets of cards among 10 available. Cards images are organized by sets, and we only preload the images corresponding to the 5 current sets with '''ensureSpecificGameImageLoading( image_file_names_array )'''.<br />
<pre><br />
// By default, do not preload anything<br />
this.dontPreloadImage( 'cards.png' );<br />
this.dontPreloadImage( 'clan1.png' );<br />
this.dontPreloadImage( 'clan2.png' );<br />
this.dontPreloadImage( 'clan3.png' );<br />
this.dontPreloadImage( 'clan4.png' );<br />
this.dontPreloadImage( 'clan5.png' );<br />
this.dontPreloadImage( 'clan6.png' );<br />
this.dontPreloadImage( 'clan7.png' );<br />
this.dontPreloadImage( 'clan8.png' );<br />
this.dontPreloadImage( 'clan9.png' );<br />
this.dontPreloadImage( 'clan10.png' );<br />
var to_preload = [];<br />
for( i in this.gamedatas.clans )<br />
{<br />
var clan_id = this.gamedatas.clans[i];<br />
to_preload.push( 'clan'+clan_id+'.png' );<br />
}<br />
if( to_preload.length == 5 )<br />
{<br />
this.ensureSpecificGameImageLoading( to_preload );<br />
}<br />
</pre><br />
<br />
'''Note:''' You don't need to specify to not preload game box images (game_box.png, game_box75.png...) since they are not preloaded by default.<br />
<br />
== Other useful stuff ==<br />
<br />
<br />
'''dojo.hitch'''<br />
<br />
With dojo.hitch, you can create a callback function that will run with your game object context whatever happen.<br />
<br />
Typical example: display a BGA confirmation dialog with a callback function created with dojo.hitch:<br />
<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), dojo.hitch( this, function() {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, function( result ) {} );<br />
} ) ); <br />
</pre><br />
<br />
In the example above, using dojo.hitch, we ensure that the "this" object will be set when the callback is called.<br />
<br />
<br />
; updateCounters(counters)<br />
: Useful for updating game counters in the player panel (such as resources). <br />
: 'counters' arg is an associative array [counter_name_value => [ 'counter_name' => counter_name_value, 'counter_value' => counter_value_value], ... ]<br />
: All counters must be referenced in this.gamedatas.counters and will be updated.<br />
: DOM objects referenced by 'counter_name' will have their innerHTML updated with 'counter_value'.<br />
<br />
<br />
'''onScreenWidthChange()'''<br />
This function can be overridden in your game to manage some resizing on the client side when the browser window is resized. This function is also triggered at load time, so it can be used to adapt to the viewport size at the start of the game too.<br />
<br />
<br />
'''updatePageTitle()'''<br />
This function allows to update the current page title and turn description according to the game state. If the current game state description this.gamedatas.gamestate.descriptionmyturn is modified before calling the function, it allows to update the turn description without changing state.<br />
<br />
Example from Terra Mystica:<br />
<pre><br />
onClickFavorTile: function( evt )<br />
{<br />
[...]<br />
<br />
if ( ... ) {<br />
<br />
this.gamedatas.gamestate.descriptionmyturn = _('Special action: ') + _('Advance 1 space on a Cult track');<br />
this.updatePageTitle();<br />
this.removeActionButtons();<br />
<br />
this.addActionButton( 'action_confirm1', _("Fire"),<br />
function() { ... }<br />
);<br />
this.addActionButton( 'action_confirm2', _("Water"),<br />
function() { ... }<br />
);<br />
this.addActionButton( 'action_confirm3', _("Earth"),<br />
function() { ... }<br />
);<br />
this.addActionButton( 'action_confirm4', _("Air"),<br />
function() { ... }<br />
);<br />
<br />
this.addActionButton( 'action_cancel', _("Cancel"), function() { ... }, false, false, 'gray'<br />
);<br />
<br />
return;<br />
}<br />
<br />
[...]<br />
<br />
}<br />
</pre><br />
<br />
== BGA GUI components ==<br />
<br />
BGA framework provides some useful ready-to-use components for the game interface:<br />
<br />
[[Studio#BGA_Studio_game_components_reference]]<br />
<br />
Note that each time you are using an additional component, you must declare it at the top of your Javascript file in the list of modules used.<br />
<br />
Example if you are using "ebg.stock":<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <=== we are using ebg.stock module<br />
],<br />
</pre><br />
<br />
== Sounds ==<br />
<br />
Add a custom sound and make it load with your interface:<br />
<br />
Add this in your template (.tpl) file:<br />
<pre><br />
<audio id="audiosrc_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<filename>.mp3" preload="none" autobuffer></audio><br />
<audio id="audiosrc_o_<gamename>_<yoursoundname>" src="{GAMETHEMEURL}img/<filename>.ogg" preload="none" autobuffer></audio><br />
</pre><br />
<br />
Note: this is a requirement to provide both a mp3 and a ogg file.<br />
<br />
Play the sound (from your .js file):<br />
<br />
<pre><br />
playSound('<gamename>_<yoursoundname>'); <br />
</pre><br />
<br />
<br />
Disable the standard "move" sound for this move (to replace it with your custom sound):<br />
<br />
Add this to your notification handler:<br />
<br />
<pre><br />
this.disableNextMoveSound();<br />
</pre><br />
<br />
Note: it only disable the sound for the next move.</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Deck&diff=5222
Deck
2020-08-07T11:18:38Z
<p>Cpasbanal: /* Auto-reshuffle */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
"Deck" is one of the most useful component on the PHP side. With "Deck", you can manage the cards in your game on the server side.<br />
<br />
Using "deck", you will be able to use the following features without writing a single SQL database request:<br />
* Place cards in a pile, shuffle cards, draw cards one by one or many at a time.<br />
* "Auto-reshuffle" the discard pile into the deck when the deck is empty.<br />
* Move cards between different locations: hands of players, the table, etc.<br />
<br />
<br />
== Using Deck: Hearts example ==<br />
<br />
The Deck component is extensively used in the sample ''Hearts'' card game. You will find in "hearts.game.php" that the object "$this->cards" is used many times.<br />
<br />
== Deck overview ==<br />
<br />
With Deck component, you manage all cards of your game.<br />
<br />
=== The 5 properties of each card ===<br />
<br />
Using the Deck component, each card will have 5 properties:<br />
* '''id''': This is the unique ID of each card.<br />
* '''type''' and '''type_arg''': These two values define the type of your card (i.e., what sort of card is this?).<br />
* '''location''' and '''location_arg''': These two values define where the card is at now.<br />
<br />
The id, type, and type_arg properties are constants throughout the game. location and location_arg change when your cards move from one place to another in the game area.<br />
<br />
'''id''' is the unique ID of each card. Two cards cannot have the same ID. IDs are generated automatically by the Deck component when you create cards during the Setup phase of your game.<br />
<br />
'''type''' and '''type_arg''' defines the type of your card.<br />
<br />
'''type''' is a short string, and '''type_arg''' is an integer.<br />
<br />
You can use these two values as you like to make sure you will be able to identify the different cards in the game. See usage of "type" and "type_arg" below.<br />
<br />
Examples of usage of "type" and "type_arg":<br />
* In ''Hearts'', "type" represents the color (suite) of the card (1 to 4) and "type_arg" is the value of the card (1, 2, ... 10, J, Q, K).<br />
* In ''Seasons'', "type" represents the type of the card (e.g., 1 is Amulet of Air, 2 is Amulet of Fire, etc...). type_arg is not used.<br />
* In ''Takenoko'', a Deck component is used for objective cards. "type" is the kind of objective (irrigation/panda/plot) and "type_arg" is the ID of the specific objective to realize (e.g., "green bamboo x4"). Note that a second Deck component is used in ''Takenoko'' to manage the "garden plot" pile.<br />
<br />
'''location''' and '''location_arg''' define where a card is at now. '''location''' is a short string, and '''location_arg''' is an integer.<br />
<br />
You can use 'location' and 'location_arg' as you like, to move your card within the game area.<br />
<br />
There are 3 special 'location' values that Deck manages automatically. You can choose to use these locations or not, depending on your needs:<br />
* 'deck': the 'deck' location is a standard draw deck. Cards are placed face down in a stack and are drawn in sequential order during the game. 'location_arg' is used to specify where the card is located within the stack (the card with the highest location_arg value is the next to be drawn).<br />
* 'hand': the 'hand' location represents cards in a player's hand. 'location_arg' is set to the ID of each player.<br />
* 'discard': the 'discard' location is used for discard piles. Card in 'discard' may be reshuffled into the deck if needed (see "autoreshuffle").<br />
<br />
<br />
Tips: using the Deck component, you will use generic properties ("location", "type_arg",...) for specific purposes in your game. Thus, during the design step before realizing your game, take a few minutes to write down the exact meaning of each of these generic properties in the context of your game.<br />
<br />
=== Create a new Deck component ===<br />
<br />
For each Deck component in your game, you need to create a dedicated table in the SQL database. This table has a standard format. In practice, if you just want to have a Deck component named "card", you can copy/paste the following into your "dbmodel.sql" file:<br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `card` (<br />
`card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,<br />
`card_type` varchar(16) NOT NULL,<br />
`card_type_arg` int(11) NOT NULL,<br />
`card_location` varchar(16) NOT NULL,<br />
`card_location_arg` int(11) NOT NULL,<br />
PRIMARY KEY (`card_id`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;<br />
</pre><br />
<br />
Note: the database schema of this table does not have to be exactly what is listed above. You can increase the size of the fields or add more fields. For additional fields<br />
you just have to do manual queries.<br />
<br />
Once you have done this (and restarted your game), you can declare the Deck component in your PHP code in your class constructor. For ''Hearts'' for example, I added to the "Hearts()" method:<br />
<br />
<pre><br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
</pre><br />
<br />
Note that we specify "card" here: the name of our previously created table. This means you can create several "Deck" components with multiple tables:<br />
<br />
<pre><br />
$this->firstKindCards = self::getNew( "module.common.deck" );<br />
$this->firstKindCards ->init( "first_kind_card" );<br />
$this->secondKindCards = self::getNew( "module.common.deck" );<br />
$this->secondKindCards ->init( "second_kind_card" );<br />
</pre><br />
<br />
Most of the time this is not useful; a Deck component should manage all objects of the same kind (i.e., all cards in the game).<br />
Note that you need to create a table for each "Deck", table name should be "first_kind_card" but the fields must remain "card_id", "card_type" and so on.<br />
<br />
Afterwards, we can initialize your "Deck" by creating all the cards of the game. Generally, this is done only once during the game, in the "setupNewGame" method.<br />
<br />
The "Deck" component provides a fast way to initialize all your cards at once: createCards. Here is how it is used for "Hearts":<br />
<pre><br />
// Create cards<br />
$cards = array();<br />
foreach( $this->colors as $color_id => $color ) // spade, heart, diamond, club<br />
{<br />
for( $value=2; $value<=14; $value++ ) // 2, 3, 4, ... K, A<br />
{<br />
$cards[] = array( 'type' => $color_id, 'type_arg' => $value, 'nbr' => 1);<br />
}<br />
}<br />
<br />
$this->cards->createCards( $cards, 'deck' );<br />
</pre><br />
<br />
As you can see, "createCards" takes a description of all cards of the game. For each type of card, you have to specify its "type", "type_arg" and the number of cards to create ("nbr"). "createCards" create all cards and place them into the "deck" location (as specified in the second argument).<br />
<br />
Now, you are ready to use "Deck"!<br />
<br />
=== Simple examples using Deck ===<br />
<br />
(Most examples are from "Hearts" game)<br />
<br />
<pre><br />
// In "getAllDatas', we need to send to the current player all the cards he has in hand:<br />
$result['hand'] = $this->cards->getCardsInLocation( 'hand', $player_id );<br />
</pre><br />
<br />
<pre><br />
// At some time we want to check if all the cards (52) are in player's hands:<br />
if( $this->cards->countCardInLocation( 'hand' ) == 52 )<br />
// do something<br />
</pre><br />
<br />
<pre><br />
// When a player plays a card in front of him on the table:<br />
$this->cards->moveCard( $card_id, 'cardsontable', $player_id );<br />
<br />
// Note the use of the custom location 'cardsontable' here to keep track of cards on the table.<br />
</pre><br />
<br />
<br />
<pre><br />
// This is a new hand: let's gather all cards from everywhere in the deck:<br />
$this->cards->moveAllCardsInLocation( null, "deck" );<br />
<br />
// And then shuffle the deck<br />
$this->cards->shuffle( 'deck' );<br />
<br />
// And then deal 13 cards to each player<br />
// Deal 13 cards to each players<br />
// Create deck, shuffle it and give 13 initial cards<br />
$players = self::loadPlayersBasicInfos();<br />
foreach( $players as $player_id => $player )<br />
{<br />
$cards = $this->cards->pickCards( 13, 'deck', $player_id );<br />
<br />
// Notify player about his cards<br />
self::notifyPlayer( $player_id, 'newHand', '', array( <br />
'cards' => $cards<br />
) );<br />
} <br />
<br />
// Note the use of "notifyPlayer" instead of "notifyAllPlayers": new cards is a private information ;) <br />
</pre><br />
<br />
== Deck component reference ==<br />
<br />
=== Initializing Deck component ===<br />
<br />
'''init( $table_name )'''<br />
<br />
Initialize the Deck component.<br />
<br />
Argument:<br />
* table_name: name of the DB table used by this Deck component.<br />
<br />
Must be called before any other Deck method.<br />
<br />
Usually, init is called in your game constructor.<br />
<br />
Example with Hearts:<br />
<br />
<pre><br />
function Hearts( )<br />
{<br />
(...)<br />
<br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
}<br />
</pre> <br />
<br />
'''createCards( $cards, $location='deck', $location_arg=null )'''<br />
<br />
Create card items in your deck component. Usually, all card items are created once, during the setup phase of the game.<br />
<br />
"cards" describe all cards that need to be created. "cards" is an array with the following format:<br />
<br />
<pre><br />
// Create 1 card of type "1" with type_arg=99,<br />
// and 4 cards of type "2" with type_arg=12,<br />
// and 2 cards of type "3" with type_arg=33<br />
<br />
$cards = array(<br />
array( 'type' => 1, 'type_arg' => 99, 'nbr' => 1 ),<br />
array( 'type' => 2, 'type_arg' => 12, 'nbr' => 4 ),<br />
array( 'type' => 3, 'type_arg' => 33, 'nbr' => 2 )<br />
...<br />
);<br />
</pre><br />
<br />
Note: During the "createCards" process, Deck generates unique IDs for all card items.<br />
<br />
Note: createCards is optimized to create a lot of cards at once. Do not use it to create cards one by one.<br />
<br />
If "location" and "location_arg" arguments are not set, newly created cards are placed in the "deck" location. If "location" (and optionally location_arg) is specified, cards are created for this specific location.<br />
<br />
=== Card standard format ===<br />
<br />
When Deck component methods are returning one or several cards, the following format is used:<br />
<br />
<pre><br />
array(<br />
'id' => .., // the card ID<br />
'type' => .., // the card type<br />
'type_arg' => .., // the card type argument<br />
'location' => .., // the card location<br />
'location_arg' => .. // the card location argument<br />
);<br />
</pre><br />
<br />
=== Picking cards ===<br />
<br />
'''pickCard( $location, $player_id )'''<br />
<br />
Pick a card from a "pile" location (ex: "deck") and place it in the "hand" of specified player.<br />
<br />
Return the card picked or "null" if there are no more card in given location.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
'''pickCards( $nbr, $location, $player_id )'''<br />
<br />
Pick "$nbr" cards from a "pile" location (ex: "deck") and place them in the "hand" of specified player.<br />
<br />
Return an array with the cards picked (indexed by the card ID), or "null" if there are no more card in given location.<br />
<br />
Note that the number of cards picked can be less than "$nbr" in case there are not enough cards in the pile location.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below). In case there are not enough cards in the pile, all remaining cards are picked first, then the auto-reshuffle is triggered, then the other cards are picked.<br />
<br />
'''pickCardForLocation( $from_location, $to_location, $location_arg=0 )'''<br />
<br />
This method is similar to 'pickCard', except that you can pick a card for any sort of location and not only the "hand" location.<br />
<br />
* from_location is the "pile" style location from where you are picking a card.<br />
* to_location is the location where you will place the card picked.<br />
* if "location_arg" is specified, the card picked will be set with this "location_arg".<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
'''pickCardsForLocation( $nbr, $from_location, $to_location, $location_arg=0, $no_deck_reform=false )'''<br />
<br />
This method is similar to 'pickCards', except that you can pick cards for any sort of location and not only the "hand" location.<br />
<br />
* from_location is the "pile" style location from where you are picking some cards.<br />
* to_location is the location where you will place the cards picked.<br />
* if "location_arg" is specified, the cards picked will be set with this "location_arg".<br />
* if "no_deck_reform" is set to "true", the auto-reshuffle feature is disabled during this method call.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
=== Moving cards ===<br />
<br />
'''moveCard( $card_id, $location, $location_arg=0 )'''<br />
<br />
Move the specific card to given location.<br />
<br />
* card_id: ID of the card to move.<br />
* location: location where to move the card.<br />
* location_arg: if specified, location_arg where to move the card. If not specified "location_arg" will be set to 0.<br />
<br />
<br />
'''moveCards( $cards, $location, $location_arg=0 )'''<br />
<br />
Move the specific cards to given location.<br />
<br />
* cards: an array of IDs of cards to move.<br />
* location: location where to move the cards.<br />
* location_arg: if specified, location_arg where to move the cards. If not specified "location_arg" will be set to 0.<br />
<br />
'''insertCard( $card_id, $location, $location_arg )'''<br />
<br />
Move a card to a specific "pile" location where card are ordered.<br />
<br />
If location_arg place is already taken, increment all cards after location_arg in order to insert new card at this precise location.<br />
<br />
(note: insertCardOnExtremePosition method below is more useful in most of the case)<br />
<br />
'''insertCardOnExtremePosition( $card_id, $location, $bOnTop )'''<br />
<br />
Move a card on top or at bottom of given "pile" type location.<br />
<br />
'''moveAllCardsInLocation( $from_location, $to_location, $from_location_arg=null, $to_location_arg=0 )'''<br />
<br />
Move all cards in specified "from" location to given location.<br />
<br />
* from_location: where to take the cards<br />
* to_location: where to put the cards<br />
* from_location_arg (optional): if specified, only cards with given "location_arg" are moved.<br />
* to_location_arg (optional): if specified, cards moved "location_arg" is set to given value. Otherwise "location_arg" is set to 0.<br />
<br />
Note: if you want to keep "location_arg" untouched, you should use "moveAllCardsInLocationKeepOrder" below.<br />
<br />
'''moveAllCardsInLocationKeepOrder( $from_location, $to_location )'''<br />
<br />
Move all cards in specified "from" location to given "to" location. This method does not modify the "location_arg" of cards.<br />
<br />
'''playCard( $card_id )'''<br />
<br />
Move specified card at the top of the "discard" location.<br />
<br />
Note: this is an alias for: insertCardOnExtremePosition( $card_id, "discard", true )<br />
<br />
=== Get cards informations ===<br />
<br />
'''getCard( $card_id )'''<br />
<br />
Get specific card information.<br />
<br />
Return null if this card is not found.<br />
<br />
'''getCards( $cards_array )'''<br />
<br />
Get specific cards information.<br />
<br />
$cards_array is an array of card IDs.<br />
<br />
If some cards are not found or if some card IDs are specified multiple times, the method throws an (unexpected) Exception.<br />
<br />
'''getCardsInLocation( $location, $location_arg = null, $order_by = null )'''<br />
<br />
Get all cards in specific location, as an array. Return an empty array if the location is empty.<br />
<br />
* location (string): the location where to get the cards.<br />
* location_arg (optional): if specified, return only cards with the specified "location_arg".<br />
* order_by (optional): if specified, returned cards are ordered by the given database field. Example: "card_id" or "card_type".<br />
<br />
'''countCardInLocation( $location, $location_arg=null )'''<br />
<br />
Return the number of cards in specified location.<br />
<br />
* location (string): the location where to count the cards.<br />
* location_arg (optional): if specified, count only cards with the specified "location_arg".<br />
<br />
'''countCardsInLocations()'''<br />
<br />
Return the number of cards in each location of the game.<br />
<br />
The method returns an associative array with the format "location" => "number of cards".<br />
<br />
Example:<br />
<pre><br />
array(<br />
'deck' => 12,<br />
'hand' => 21,<br />
'discard' => 54,<br />
'ontable' => 3<br />
);<br />
</pre><br />
<br />
'''countCardsByLocationArgs( $location )'''<br />
<br />
Return the number of cards in each "location_arg" for the given location.<br />
<br />
The method returns an associative array with the format "location_arg" => "number of cards".<br />
<br />
Example: count the number of cards in each player's hand:<br />
<pre><br />
countCardsByLocationArgs( 'hand' );<br />
<br />
// Result:<br />
array(<br />
122345 => 5, // player 122345 has 5 cards in hand<br />
123456 => 4 // and player 123456 has 4 cards in hand<br />
);<br />
</pre><br />
<br />
<br />
'''getPlayerHand( $player_id )'''<br />
<br />
Get all cards in given player hand.<br />
<br />
Note: This is an alias for:<br />
getCardsInLocation( "hand", $player_id )<br />
<br />
'''getCardOnTop( $location )'''<br />
<br />
Get the card on top of the given ("pile" style) location, or null if the location is empty.<br />
<br />
Note that the card pile won't be "auto-reshuffled" if there is no more card available.<br />
<br />
'''getCardsOnTop( $nbr, $location )'''<br />
<br />
Get the "$nbr" cards on top of the given ("pile" style) location.<br />
<br />
The method return an array with at most "$nbr" elements (or a void array if there is no card in this location).<br />
<br />
Note that the card pile won't be "auto-reshuffled" if there is not enough cards available.<br />
<br />
'''getExtremePosition( $bGetMax ,$location )'''<br />
<br />
(rarely used)<br />
<br />
Get the position of cards at the top of the given location / at the bottom of the given location.<br />
<br />
Of course this method works only on location in "pile" where you are using "location_arg" to specify the position of each card (example: "deck" location).<br />
<br />
If bGetMax=true, return the location of the top card of the pile.<br />
<br />
If bGetMax=false, return the location of the bottom card of the pile.<br />
<br />
'''getCardsOfType( $type, $type_arg=null )'''<br />
<br />
Get all cards of a specific type (rarely used).<br />
<br />
Return an array of cards, or an empty array if there is no cards of the specified type.<br />
<br />
* type: the type of cards<br />
* type_arg: if specified, return only cards with the specified "type_arg".<br />
<br />
'''getCardsOfTypeInLocation( $type, $type_arg=null, $location, $location_arg = null )<br />
<br />
Get all cards of a specific type in a specific location (rarely used).<br />
<br />
Return an array of cards, or an empty array if there is no cards of the specified type.<br />
<br />
* type: the type of cards<br />
* type_arg: if specified, return only cards with the specified "type_arg".<br />
* location (string): the location where to get the cards.<br />
* location_arg (optional): if specified, return only cards with the specified "location_arg".<br />
<br />
=== Shuffling ===<br />
<br />
'''shuffle( $location )'''<br />
<br />
Shuffle all cards in specific location.<br />
<br />
Shuffle only works on locations where cards are on a "pile" (ex: "deck").<br />
<br />
Please note that all "location_arg" will be reset to reflect the new order of the cards in the pile.<br />
<br />
=== Auto-reshuffle ===<br />
<br />
To enable auto-reshuffle you must do "$this->cards->autoreshuffle = true" during the setup of the component (often in the ''_construct'' function when you ''init()'' the Deck object).<br />
<br />
Every time a card must be retrieved from the "deck" location, if it is empty the "discard" location will be automatically reshuffled into the "deck" location.<br />
<br />
If you need to notify players when the deck is shuffled, you can setup a callback method using this feature: $this->cards->autoreshuffle_trigger = array('obj' => $this, 'method' => 'deckAutoReshuffle');<br />
<br />
If you need to use other locations than "deck" and "discard" for auto-reshuffle feature, you can configure it this way: $this->cards->autoreshuffle_custom = array('deck' => 'discard'); (replace 'deck' and 'discard' with your custom locations).</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Counter&diff=5144
Counter
2020-08-01T14:06:11Z
<p>Cpasbanal: </p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
<br />
== Dependency ==<br />
<br />
Don't forget to add '''ebg/counter''' as a dependency:<br />
<br />
// in your game js<br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter" /// <==== HERE],<br />
<br />
== Setup a counter ==<br />
<br />
<pre><br />
player.handSizeCounter = new ebg.counter();<br />
player.handSizeCounter.create('hand_size_player_' + player_id);<br />
</pre><br />
<br />
Where ''' 'hand_size_player_' + player_id''' is the id of the container that will display the counter value.<br />
<br />
== Functions on counter ==<br />
<br />
<pre><br />
player.handSizeCounter.getValue();<br />
player.handSizeCounter.incValue(by);<br />
player.handSizeCounter.setValue(player.handSize);<br />
</pre><br />
<br />
Where '''by''' and '''player.handSize''' must be integers.<br />
<br />
== Players panels ==<br />
<br />
=== Adding stuff to player's panel ===<br />
<br />
First, create a new JS template string in your template (tpl) file.<br />
<br />
From the ''Gomoku'' example:<br />
<br />
<pre><br />
var jstpl_player_board = '\<div class="cp_board">\<br />
<div id="stoneicon_p${id}" class="gmk_stoneicon gmk_stoneicon_${color}"></div><span id="stonecount_p${id}">0</span>\<br />
</div>';<br />
</pre><br />
<br />
Then, add this piece of code in the '''setup''' function of your JS file to add this template to each player panel:<br />
<br />
<pre><br />
// Setting up player boards<br />
for( var player_id in gamedatas.players )<br />
{<br />
var player = gamedatas.players[player_id];<br />
<br />
// Setting up players boards if needed<br />
var player_board_div = $('player_board_'+player_id);<br />
dojo.place( this.format_block('jstpl_player_board', player ), player_board_div );<br />
}<br />
</pre><br />
<br />
Often, you have to distinguish between the current player and other players. In this case, create another JS template (ex: jstpl_otherplayer_board) and use it where "player_id" is different than "this.player_id".</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Gamehelpbarbu&diff=5021
Gamehelpbarbu
2020-07-19T20:20:04Z
<p>Cpasbanal: /* Scoring */</p>
<hr />
<div>== Goal == <br />
<br />
Your goal is to loose '''the least point''' you can.<br />
<br />
== Playing the game == <br />
<br />
The game is made of multiple hands (number of hands depends on the options you play):<br />
# avoid making '''tricks''' (every trick counts -1 point)<br />
# avoid taking '''hearts''' in tricks (every heart card counts -1 point)<br />
# avoid taking '''queens''' in tricks (each queeen counts -3 points for a 52-cards deck and -2 points when playing with a 32-cards deck)<br />
# avoid taking the '''King of Hearts''' (the ''Barbu'') in your tricks (Barbu counts -12 point for a 52-cards deck and -8 when playing with a 32-cards deck)<br />
<br />
== Options availables ==<br />
<br />
=== Option "geronimo" ===<br />
<br />
When playing with this option, each player at his/her turn will choose the hand he/she wants to play. Yet, during the game, all the players will have to play all the hands.<br />
<br />
=== Option "Double" ===<br />
<br />
After declarer picks a game but before the first trick is led, each of the other players may wager double against one or more of the other players. This operates like a side bet on the relative game score between the two players, who are said to have "business" with each other. The rules for doubling are:<br />
<br />
* Each player gets one opportunity to double<br />
* A player can choose to double only some players (declarer or non-declarer) or pass<br />
* At a positive game, non-dealers may only double the dealer (who may redouble)<br />
* Each non-declarer must double against declarer '''at least twice''' during the game for each declarer.<br />
* Any player receiving a wager of double may redouble; "maximum" implies redoubling all received wagers as well as doubling the remaining players<br />
* Declarer can not double, but may redouble as above. A "maximum" from the dealer therefore redoubles received wagers only<br />
<br />
After the hand is played out and scores are tallied, modifications are made for doubles as follows:<br />
<br />
* Between each pair of players who doubled, the difference in their scores is calculated. Player with the higher score for that round is awarded the amount of the difference and player with the lower score had the same amount subtracted from his or her score.<br />
* Redoubled wagers are figured the same way, with the difference in scores doubled before awards and penalties are tallied.<br />
<br />
=== Option "Salade" ===<br />
<br />
Play an extra hand, the '''Salade''' when everything counts, so avoid making tricks, taking hearts, taking queens or the King of Hearts. All the point counts so Queen of Hearts is worth -4 points (with 52-cards deck), Barbu is worth -13 points (still 52-cards deck)...<br />
<br />
=== Option "Anti-Salade" ===<br />
<br />
Similar to the "Salade", the '''Anti-salade''' counts all the same points (tricks, hearts, queens, Barbu) with '''positive score'''.<br />
<br />
=== Option "Last two" ===<br />
<br />
In this hand, try to avoid taking the '''last two tricks''' (counts -4 points for the trick before last and -8 points for the last for a 52-cards deck and -3 points and -5 points for a 32-cards deck)<br />
<br />
=== Option "Trumps" ===<br />
<br />
Only when playing on geronimo mode, you can choose to play Trumps. The declareer choose the trump color and the goal is to take as much tricks as possible (tricks count +3 when playing 52-cards deck and +2 when playing 32-cards deck).<br />
<br />
Some rules apply when you play with trumps:<br />
<br />
* If a trump is led, each player must, if able, play a higher trump than those played before. Otherwise players follow suit, or discard if they are void in trumps.<br />
* If a plain suit is led, players follow suit if possible.<br />
* If they cannot and no trump has been played, they must play a trump card if possible or otherwise play a card of one of the other suits.<br />
* If a non-trump has been led and trumped, a player must, if possible, play a higher trump than those previously played. Otherwise the player may slough off any card.<br />
<br />
=== Option "Domino" (Klondike) ===<br />
<br />
You can play an extra hand: the '''Domino'''.<br />
<br />
During this hand, players have to play cards in a suite, starting from 2 (or 7 when playing 32-cards deck) to the Ace, for each of the 4 types.<br />
<br />
When a player plays an Ace, he/she can play again. If a player can't play, he/she passes. But you can't pass if you can play any card (even if you wanted to).<br />
<br />
Domino also counts points positively.<br />
<br />
If you play with a 52-cards deck, the first player with no card left wins 25 points, the second 15 points, the third 5 points. The other players score nothing.<br />
<br />
If you play with a 32-cards deck, the first player with no card left wins 20 points, the second 10 points. The other players score nothing.<br />
<br />
== Scoring ==<br />
<br />
Scoring depends on the variant and the type of deck you play with:<br />
<br />
{| class="wikitable" style="text-align: center;"<br />
! '''Scores'''<br />
! Deck type<br />
! No trick (per trick)<br />
! No hearts (per heart)<br />
! No queens (per queen)<br />
! No Barbu<br />
! Last two<br />
! Trumps<br />
! Domino<br />
|-<br />
!rowspan="2"|'''Basic scoring'''<br />
| 32-cards<br />
| -1<br />
| -1<br />
| -2<br />
| -8<br />
| -5 for the ultimate<br>-3 for penultimate<br />
| +2 per trick<br />
| +20 for the first<br>+10 for the second<br>0 for the others<br />
|-<br />
| 52-cards<br />
| -1<br />
| -1<br />
| -3<br />
| -12<br />
| -8 for the ultimate<br>-4 for penultimate<br />
| +3 per trick<br />
| +25 for the first<br>+15 for the second<br>+5 for the third<br>0 for the others<br />
|-<br />
!rowspan="2"|'''Wikipedia scoring'''<br />
| 32-cards<br />
|rowspan="2"| -2<br />
|rowspan="2"| -2<br />
-6 for Ace of Hearts<br />
|rowspan="2"| -6<br />
|rowspan="2"| -20<br />
|rowspan="2"| -20 for the ultimate<br>-10 for penultimate<br />
|rowspan="2"| +5 per trick<br />
|rowspan="2"| +45 for the first<br>+20 for the second<br>+5 for the third<br>-5 for the other players<br />
|-<br />
| 52-cards<br />
|-<br />
|}<br />
<br />
== 3 or 5 players ==<br />
<br />
The game is recommended to play at 4 players but you can play at 3 or 5 players. Some cards are removed from the game so that everyone has the same quantity of cards in hand. Here are the cards removed:<br />
<br />
{| class="wikitable"<br />
! '''Number of players'''<br />
! 32-cards deck<br />
! 52-cards deck<br />
|-<br />
|'''3 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds<br />
|-<br />
|'''4 players'''<br />
|nothing<br />
|nothing<br />
|-<br />
|'''5 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds and 2 clubs<br />
|}</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Gamehelpbarbu&diff=5020
Gamehelpbarbu
2020-07-19T20:19:47Z
<p>Cpasbanal: /* Scoring */</p>
<hr />
<div>== Goal == <br />
<br />
Your goal is to loose '''the least point''' you can.<br />
<br />
== Playing the game == <br />
<br />
The game is made of multiple hands (number of hands depends on the options you play):<br />
# avoid making '''tricks''' (every trick counts -1 point)<br />
# avoid taking '''hearts''' in tricks (every heart card counts -1 point)<br />
# avoid taking '''queens''' in tricks (each queeen counts -3 points for a 52-cards deck and -2 points when playing with a 32-cards deck)<br />
# avoid taking the '''King of Hearts''' (the ''Barbu'') in your tricks (Barbu counts -12 point for a 52-cards deck and -8 when playing with a 32-cards deck)<br />
<br />
== Options availables ==<br />
<br />
=== Option "geronimo" ===<br />
<br />
When playing with this option, each player at his/her turn will choose the hand he/she wants to play. Yet, during the game, all the players will have to play all the hands.<br />
<br />
=== Option "Double" ===<br />
<br />
After declarer picks a game but before the first trick is led, each of the other players may wager double against one or more of the other players. This operates like a side bet on the relative game score between the two players, who are said to have "business" with each other. The rules for doubling are:<br />
<br />
* Each player gets one opportunity to double<br />
* A player can choose to double only some players (declarer or non-declarer) or pass<br />
* At a positive game, non-dealers may only double the dealer (who may redouble)<br />
* Each non-declarer must double against declarer '''at least twice''' during the game for each declarer.<br />
* Any player receiving a wager of double may redouble; "maximum" implies redoubling all received wagers as well as doubling the remaining players<br />
* Declarer can not double, but may redouble as above. A "maximum" from the dealer therefore redoubles received wagers only<br />
<br />
After the hand is played out and scores are tallied, modifications are made for doubles as follows:<br />
<br />
* Between each pair of players who doubled, the difference in their scores is calculated. Player with the higher score for that round is awarded the amount of the difference and player with the lower score had the same amount subtracted from his or her score.<br />
* Redoubled wagers are figured the same way, with the difference in scores doubled before awards and penalties are tallied.<br />
<br />
=== Option "Salade" ===<br />
<br />
Play an extra hand, the '''Salade''' when everything counts, so avoid making tricks, taking hearts, taking queens or the King of Hearts. All the point counts so Queen of Hearts is worth -4 points (with 52-cards deck), Barbu is worth -13 points (still 52-cards deck)...<br />
<br />
=== Option "Anti-Salade" ===<br />
<br />
Similar to the "Salade", the '''Anti-salade''' counts all the same points (tricks, hearts, queens, Barbu) with '''positive score'''.<br />
<br />
=== Option "Last two" ===<br />
<br />
In this hand, try to avoid taking the '''last two tricks''' (counts -4 points for the trick before last and -8 points for the last for a 52-cards deck and -3 points and -5 points for a 32-cards deck)<br />
<br />
=== Option "Trumps" ===<br />
<br />
Only when playing on geronimo mode, you can choose to play Trumps. The declareer choose the trump color and the goal is to take as much tricks as possible (tricks count +3 when playing 52-cards deck and +2 when playing 32-cards deck).<br />
<br />
Some rules apply when you play with trumps:<br />
<br />
* If a trump is led, each player must, if able, play a higher trump than those played before. Otherwise players follow suit, or discard if they are void in trumps.<br />
* If a plain suit is led, players follow suit if possible.<br />
* If they cannot and no trump has been played, they must play a trump card if possible or otherwise play a card of one of the other suits.<br />
* If a non-trump has been led and trumped, a player must, if possible, play a higher trump than those previously played. Otherwise the player may slough off any card.<br />
<br />
=== Option "Domino" (Klondike) ===<br />
<br />
You can play an extra hand: the '''Domino'''.<br />
<br />
During this hand, players have to play cards in a suite, starting from 2 (or 7 when playing 32-cards deck) to the Ace, for each of the 4 types.<br />
<br />
When a player plays an Ace, he/she can play again. If a player can't play, he/she passes. But you can't pass if you can play any card (even if you wanted to).<br />
<br />
Domino also counts points positively.<br />
<br />
If you play with a 52-cards deck, the first player with no card left wins 25 points, the second 15 points, the third 5 points. The other players score nothing.<br />
<br />
If you play with a 32-cards deck, the first player with no card left wins 20 points, the second 10 points. The other players score nothing.<br />
<br />
== Scoring ==<br />
<br />
Scoring depends on the variant and the type of deck you play with:<br />
<br />
{| class="wikitable" style="text-align: center;"<br />
! '''Scores'''<br />
! Deck type<br />
! No trick (per trick)<br />
! No hearts (per heart)<br />
! No queens (per queen)<br />
! No Barbu<br />
! Last two<br />
! Trumps<br />
! Domino<br />
|-<br />
!rowspan="2"|'''Basic scoring'''<br />
| 32-cards<br />
| -1<br />
| -1<br />
| -2<br />
| -8<br />
| -5 for the ultimate<br>-3 for penultimate<br />
| +2 per trick<br />
| +20 for the first<br>+10 for the second<br>0 for the others<br />
|-<br />
| 52-cards<br />
| -1<br />
| -1<br />
| -3<br />
| -12<br />
| -8 for the ultimate<br>-4 for penultimate<br />
| +3 per trick<br />
| +25 for the first<br>+15 for the second<br>5 for the third<br>0 for the others<br />
|-<br />
!rowspan="2"|'''Wikipedia scoring'''<br />
| 32-cards<br />
|rowspan="2"| -2<br />
|rowspan="2"| -2<br />
-6 for Ace of Hearts<br />
|rowspan="2"| -6<br />
|rowspan="2"| -20<br />
|rowspan="2"| -20 for the ultimate<br>-10 for penultimate<br />
|rowspan="2"| +5 per trick<br />
|rowspan="2"| +45 for the first<br>+20 for the second<br>5 for the third<br>-5 for the other players<br />
|-<br />
| 52-cards<br />
|-<br />
|}<br />
<br />
== 3 or 5 players ==<br />
<br />
The game is recommended to play at 4 players but you can play at 3 or 5 players. Some cards are removed from the game so that everyone has the same quantity of cards in hand. Here are the cards removed:<br />
<br />
{| class="wikitable"<br />
! '''Number of players'''<br />
! 32-cards deck<br />
! 52-cards deck<br />
|-<br />
|'''3 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds<br />
|-<br />
|'''4 players'''<br />
|nothing<br />
|nothing<br />
|-<br />
|'''5 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds and 2 clubs<br />
|}</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Gamehelpbarbu&diff=5019
Gamehelpbarbu
2020-07-19T20:19:22Z
<p>Cpasbanal: </p>
<hr />
<div>== Goal == <br />
<br />
Your goal is to loose '''the least point''' you can.<br />
<br />
== Playing the game == <br />
<br />
The game is made of multiple hands (number of hands depends on the options you play):<br />
# avoid making '''tricks''' (every trick counts -1 point)<br />
# avoid taking '''hearts''' in tricks (every heart card counts -1 point)<br />
# avoid taking '''queens''' in tricks (each queeen counts -3 points for a 52-cards deck and -2 points when playing with a 32-cards deck)<br />
# avoid taking the '''King of Hearts''' (the ''Barbu'') in your tricks (Barbu counts -12 point for a 52-cards deck and -8 when playing with a 32-cards deck)<br />
<br />
== Options availables ==<br />
<br />
=== Option "geronimo" ===<br />
<br />
When playing with this option, each player at his/her turn will choose the hand he/she wants to play. Yet, during the game, all the players will have to play all the hands.<br />
<br />
=== Option "Double" ===<br />
<br />
After declarer picks a game but before the first trick is led, each of the other players may wager double against one or more of the other players. This operates like a side bet on the relative game score between the two players, who are said to have "business" with each other. The rules for doubling are:<br />
<br />
* Each player gets one opportunity to double<br />
* A player can choose to double only some players (declarer or non-declarer) or pass<br />
* At a positive game, non-dealers may only double the dealer (who may redouble)<br />
* Each non-declarer must double against declarer '''at least twice''' during the game for each declarer.<br />
* Any player receiving a wager of double may redouble; "maximum" implies redoubling all received wagers as well as doubling the remaining players<br />
* Declarer can not double, but may redouble as above. A "maximum" from the dealer therefore redoubles received wagers only<br />
<br />
After the hand is played out and scores are tallied, modifications are made for doubles as follows:<br />
<br />
* Between each pair of players who doubled, the difference in their scores is calculated. Player with the higher score for that round is awarded the amount of the difference and player with the lower score had the same amount subtracted from his or her score.<br />
* Redoubled wagers are figured the same way, with the difference in scores doubled before awards and penalties are tallied.<br />
<br />
=== Option "Salade" ===<br />
<br />
Play an extra hand, the '''Salade''' when everything counts, so avoid making tricks, taking hearts, taking queens or the King of Hearts. All the point counts so Queen of Hearts is worth -4 points (with 52-cards deck), Barbu is worth -13 points (still 52-cards deck)...<br />
<br />
=== Option "Anti-Salade" ===<br />
<br />
Similar to the "Salade", the '''Anti-salade''' counts all the same points (tricks, hearts, queens, Barbu) with '''positive score'''.<br />
<br />
=== Option "Last two" ===<br />
<br />
In this hand, try to avoid taking the '''last two tricks''' (counts -4 points for the trick before last and -8 points for the last for a 52-cards deck and -3 points and -5 points for a 32-cards deck)<br />
<br />
=== Option "Trumps" ===<br />
<br />
Only when playing on geronimo mode, you can choose to play Trumps. The declareer choose the trump color and the goal is to take as much tricks as possible (tricks count +3 when playing 52-cards deck and +2 when playing 32-cards deck).<br />
<br />
Some rules apply when you play with trumps:<br />
<br />
* If a trump is led, each player must, if able, play a higher trump than those played before. Otherwise players follow suit, or discard if they are void in trumps.<br />
* If a plain suit is led, players follow suit if possible.<br />
* If they cannot and no trump has been played, they must play a trump card if possible or otherwise play a card of one of the other suits.<br />
* If a non-trump has been led and trumped, a player must, if possible, play a higher trump than those previously played. Otherwise the player may slough off any card.<br />
<br />
=== Option "Domino" (Klondike) ===<br />
<br />
You can play an extra hand: the '''Domino'''.<br />
<br />
During this hand, players have to play cards in a suite, starting from 2 (or 7 when playing 32-cards deck) to the Ace, for each of the 4 types.<br />
<br />
When a player plays an Ace, he/she can play again. If a player can't play, he/she passes. But you can't pass if you can play any card (even if you wanted to).<br />
<br />
Domino also counts points positively.<br />
<br />
If you play with a 52-cards deck, the first player with no card left wins 25 points, the second 15 points, the third 5 points. The other players score nothing.<br />
<br />
If you play with a 32-cards deck, the first player with no card left wins 20 points, the second 10 points. The other players score nothing.<br />
<br />
== Scoring ==<br />
<br />
Scoring depends on the variant and the type of deck you play with:<br />
<br />
{| class="wikitable" style="text-align: center;"<br />
! '''Scores'''<br />
! Deck type<br />
! No trick (per trick)<br />
! No hearts (per heart)<br />
! No queens (per queen)<br />
! No Barbu<br />
! Last two<br />
! Trumps<br />
! Domino<br />
|-<br />
!rowspan="2"|'''Basic scoring'''<br />
| 32-cards<br />
| -1<br />
| -1<br />
| -2<br />
| -8<br />
| -5 for the ultimate<br>-3 for penuultimate<br />
| +2 per trick<br />
| +20 for the first<br>+10 for the second<br>0 for the others<br />
|-<br />
| 52-cards<br />
| -1<br />
| -1<br />
| -3<br />
| -12<br />
| -8 for the ultimate<br>-4 for penuultimate<br />
| +3 per trick<br />
| +25 for the first<br>+15 for the second<br>5 for the third<br>0 for the others<br />
|-<br />
!rowspan="2"|'''Wikipedia scoring'''<br />
| 32-cards<br />
|rowspan="2"| -2<br />
|rowspan="2"| -2<br />
-6 for Ace of Hearts<br />
|rowspan="2"| -6<br />
|rowspan="2"| -20<br />
|rowspan="2"| -20 for the ultimate<br>-10 for penultimate<br />
|rowspan="2"| +5 per trick<br />
|rowspan="2"| +45 for the first<br>+20 for the second<br>5 for the third<br>-5 for the other players<br />
|-<br />
| 52-cards<br />
|-<br />
|}<br />
<br />
== 3 or 5 players ==<br />
<br />
The game is recommended to play at 4 players but you can play at 3 or 5 players. Some cards are removed from the game so that everyone has the same quantity of cards in hand. Here are the cards removed:<br />
<br />
{| class="wikitable"<br />
! '''Number of players'''<br />
! 32-cards deck<br />
! 52-cards deck<br />
|-<br />
|'''3 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds<br />
|-<br />
|'''4 players'''<br />
|nothing<br />
|nothing<br />
|-<br />
|'''5 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds and 2 clubs<br />
|}</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Game_interface_logic:_yourgamename.js&diff=4984
Game interface logic: yourgamename.js
2020-07-13T20:13:01Z
<p>Cpasbanal: /* Animations */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game interface. Here you will define:<br />
<br />
* Which actions on the page will generate calls to the server.<br />
* What happens when you get a notification for a change from the server and how it will show in the browser. <br />
<br />
== File structure ==<br />
<br />
The details of how the file is structured are described below with comments on the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* '''constructor''': here you can define global variables for your whole interface.<br />
* '''setup''': this method is called when the page is refreshed, and sets up the game interface.<br />
* '''onEnteringState''': this method is called when entering a new game state. You can use it to customize the view for each game state.<br />
* '''onLeavingState''': this method is called when leaving a game state.<br />
* '''onUpdateActionButtons''': called when entering a new state, in order to add action buttons to the status bar.<br />
* ''(utility methods)'': this is where you can define your utility methods.<br />
* ''(player's actions)'': this is where you can write your handlers for player actions on the interface (example: click on an item).<br />
* '''setupNotifications''': this method associates notifications with notification handlers. For each game notification, you can trigger a javascript method to handle it and update the game interface.<br />
* ''(notification handlers)'': this is where you define the notifications handlers associated with notifications in '''setupNotifications''', above.<br />
<br />
== General tips ==<br />
<br />
; this.player_id<br />
: ID of the player on whose browser the code is running.<br />
<br />
; this.isSpectator<br />
: Flag set to true if the user at the table is a spectator (not a player).<br />
: Note: This is a variable, not a function.<br />
: Note: If you want to hide an element from spectators, you should use [[Game_interface_stylesheet:_yourgamename.css#spectatorMode|CSS 'spectatorMode' class]].<br />
<br />
; this.gamedatas<br />
: Contains the initial set of data to init the game, created at game start or by game refresh (F5).<br />
: You can update it as needed to keep an up-to-date reference of the game on the client side if you need it. (Most of the time this is unnecessary).<br />
<br />
; this.isCurrentPlayerActive()<br />
: Returns true if the player on whose browser the code is running is currently active (it's his turn to play).<br />
<br />
; this.getActivePlayerId()<br />
: Return the ID of the active player, or null if we are not in an "activeplayer" type state.<br />
<br />
; this.getActivePlayers()<br />
: Return an array with the IDs of players who are currently active (or an empty array if there are none).<br />
<br />
; this.bRealtime<br />
: Return true if the game is in realtime. Note that having a distinct behavior in realtime and turn-based should be exceptional.<br />
<br />
; typeof g_replayFrom != 'undefined'<br />
: Returns true if the game is in instant replay mode (replay during the game)<br />
<br />
; g_archive_mode<br />
: Returns true if the game is in archive mode (advanced replay after the game has ended)<br />
<br />
== Dojo framework ==<br />
<br />
BGA uses the [http://dojotoolkit.org/ Dojo Javascript framework].<br />
<br />
The Dojo framework allows us to do complex things more easily. The BGA framework uses Dojo extensively.<br />
<br />
To implement a game, you only need to use a few parts of the Dojo framework. All the Dojo methods you need are described on this page.<br />
<br />
== Javascript minimization (before July 2020) ==<br />
<br />
For performance reasons, when deploying a game the js code is minimized using ShrinkSafe (based on ECMASCRIPT version 3). Some advanced syntax may not be compatible with this process. In particular:<br />
* You should not use reserved keywords from the javascript language as variables.<br />
* You should not declare default argument values in function declarations. The following syntax is invalid for ShrinkSafe: '''function myFunc(requiredArg, optionalArg = 'defaultValue') {}'''<br />
* You should not use '''let''' or '''const''' to declare variables.<br />
<br />
'''Tip:''' a developer encountering some problems with this has successfully used [http://plugins.netbeans.org/plugin/58580/jshint JSHint] on NetBeans to evaluate code to make it compatible for ECMAScript 3. With the plugin installed, set the below options in '''.jshintrc''' file, and then open '''Action Items''' window (in NetBeans):<pre>{ "maxerr": 999, "esversion": 3 }</pre><br />
<br />
'''Tip:''' some online tools also allow to convert between different versions of javascript, such as https://www.typescriptlang.org/play or https://babeljs.io/ or https://extendsclass.com/javascript-fiddle.html<br />
<br />
== Javascript minimization (after July 2020) ==<br />
<br />
For performance reasons, when deploying a game the javascript code is minimized using '''terser''' (https://github.com/terser/terser). This minifier works with modern javascript syntax. From your project "Manage game" page, you can now test a minified version of jour javascript on the studio (and revert to the original).<br />
<br />
== Accessing and manipulating the DOM ==<br />
<br />
'''$('some_html_element_id')'''<br />
<br />
The $() function is used to get an HTML element using its "id" attribute.<br />
<br />
Example 1: modify the content of a "span" element:<br />
<br />
<pre><br />
In your HTML code:<br />
<span id="a_value_in_the_game_interface">1234</span><br />
<br />
In your Javascript code:<br />
$('a_value_in_the_game_interface').innerHTML = "9999";<br />
</pre><br />
<br />
Note: $() is the standard method to access some HTML element with the BGA Framework. You should not use the '''getElementById''' function.<br />
<br />
<br />
'''dojo.style'''<br />
<br />
With dojo.style you can modify the CSS property of any HTML element in your interface.<br />
<br />
Examples:<br />
<br />
<pre><br />
// Make an element disappear<br />
dojo.style( 'my_element', 'display', 'none' );<br />
<br />
// Give an element a 2px border<br />
dojo.style( 'my_element', 'borderWidth', '2px' );<br />
<br />
// Change the background position of an element<br />
// (very practical when you are using CSS sprites to transform an element to another)<br />
dojo.style( 'my_element', 'backgroundPosition', '-20px -50px' );<br />
</pre><br />
<br />
Note: you must always use dojo.style to modify the CSS properties of HTML elements.<br />
<br />
NoteÂČ: if you have to modify several CSS properties of an element, or if you have a complex CSS transformation to do, you should consider using dojo.addClass/dojo.removeClass (see below).<br />
<br />
'''dojo CSS classes manipulation'''<br />
<br />
In many situations, many small CSS property updates can be replaced by a CSS class change (i.e., you add a CSS class to your element instead of applying all modifications manually).<br />
<br />
Advantages are:<br />
* All your CSS stuff remains in your CSS file.<br />
* You can add/remove a list of CSS modifications with a simple function and without error.<br />
* You can test whether you applied the CSS to an element with the '''dojo.hasClass''' method.<br />
<br />
Example from ''Reversi'':<br />
<br />
<pre><br />
// We add "possibleMove" to an element<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
<br />
// In our CSS file, the class is defined as:<br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
<br />
// So we've applied 4 CSS property changes in one line of code.<br />
<br />
// ... and when we need to check if a square is a possible move on the client side:<br />
if( dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{ ... }<br />
<br />
// ... and if we want to remove all possible moves in one line of code (see "dojo.query" method):<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
</pre><br />
<br />
Conclusion: We encourage you to use '''dojo.addClass''', '''dojo.removeClass''' and '''dojo.hasClass''' to make your life easier :)<br />
<br />
'''dojo.query'''<br />
<br />
With dojo.query, you can query a bunch of HTML elements with a single function, with a "CSS selector" style.<br />
<br />
Example:<br />
<pre><br />
// All elements with class "possibleMove":<br />
var elements = dojo.query( '.possibleMove' );<br />
<br />
// Count number of tokens (i.e., elements of class "token") on the board (i.e., the element with id "board"):<br />
dojo.query( '#board .token' ).length;<br />
</pre><br />
<br />
But what is really cool with dojo.query is that you can combine it with almost all methods above.<br />
<br />
Examples:<br />
<pre><br />
// Trigger a method when the mouse enter in any element with class "meeple":<br />
dojo.query( '.meeple' ).connect( 'onmouseenter', this, 'myMethodToTrigger' );<br />
<br />
// Hide all meeples who are on the board<br />
dojo.query( '#board .meeple' ).style( 'display', 'none' );<br />
</pre><br />
<br />
'''dojo.place'''<br />
<br />
dojo.place is the best function to insert HTML code somewhere in your game interface without breaking something. It is much better to use than the '''innerHTML=''' method if you must insert HTML tags and not only values.<br />
<br />
<pre><br />
// Insert your HTML code as a child of a container element<br />
dojo.place( "<your html code>", "your_container_element_id" );<br />
<br />
// Replace the container element with your new html<br />
dojo.place( "<your html code>", "your_container_element_id", "replace" );<br />
<br />
</pre><br />
<br />
The third parameter of dojo.place can take various interesting values:<br />
<br />
"replace" : (see description above).<br />
<br />
"first" : Places the node as a child of the reference node. The node is placed as the first child.<br />
<br />
"last" (default) : Places the node as a child of the reference node. The node is placed as the last child.<br />
<br />
"before" : places the node right before the reference node.<br />
<br />
"after" : places the node right after the reference node.<br />
<br />
"only" : replaces all children of the reference node with the node.<br />
<br />
positif integer : This parameter can be a positif integer. In this case, the node will be placed as a child of the reference node with this number (counting from 0). If the number is more than number of children, the node will be appended to the reference node making it the last child. <br />
<br />
See also full doc on dojo.place : [https://dojotoolkit.org/reference-guide/1.7/dojo/place.html]<br />
<br />
Usually, when you want to insert some piece of HTML in your game interface, you should use "[[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]".<br />
<br />
'''addStyleToClass: function( cssClassName, cssProperty, propertyValue )'''<br />
<br />
Same as dojo.style(), but for all the nodes set with the specified cssClassName<br />
<br />
=== Animations ===<br />
<br />
'''Dojo Animations'''<br />
<br />
BGA animations is based on Dojo Animation ([http://dojotoolkit.org/documentation/tutorials/1.8/animation/ see tutorial here]).<br />
<br />
However, most of the time, you can just use methods below, which are built on top of Dojo Animation.<br />
<br />
Note: one interesting method from Dojo that could be useful from time to time is "Dojo.Animation". It allows you to make any CSS property "slide" from one value to another.<br />
<br />
Note 2: the slideTo methods are not compatible with CSS transform (scale, zoom, rotate...). If possible, avoid using CSS transform on nodes that are being slided. Eventually, the only possible solution to make these 2 compatible is to disable all CSS transform properties, use slideToObjectPos/placeOnObjectPos, and then apply them again.<br />
<br />
<br />
'''this.slideToObject( mobile_obj, target_obj, duration, delay )'''<br />
<br />
You can use slideToObject to "slide" an element to a target position.<br />
<br />
Sliding element on the game area is the recommended and the most used way to animate your game interface. Using slides allow players to figure out what is happening on the game, as if they were playing with the real boardgame.<br />
<br />
The parameters are:<br />
* mobile_obj: the ID of the object to move. This object must be "relative" or "absolute" positioned.<br />
* target_obj: the ID of the target object. This object must be "relative" or "absolute" positioned. Note that it is not mandatory that mobile_obj and target_obj have the same size. If their size are different, the system slides the center of mobile_obj to the center of target_obj.<br />
* duration: (optional) defines the duration in millisecond of the slide. The default is 500 milliseconds.<br />
* delay: (optional). If you defines a delay, the slide will start only after this delay. This is particularly useful when you want to slide several object from the same position to the same position: you can give a 0ms delay to the first object, a 100ms delay to the second one, a 200ms delay to the third one, ... this way they won't be superposed during the slide.<br />
<br />
BE CAREFUL: The method returns an dojo.fx animation, so you can combine it with other animation if you want to. It means that you have to call the "play()" method, otherwise the animation WON'T START.<br />
<br />
Example:<br />
<pre><br />
this.slideToObject( "some_token", "some_place_on_board" ).play();<br />
</pre><br />
<br />
<br />
'''this.slideToObjectPos( mobile_obj, target_obj, target_x, target_y, duration, delay )'''<br />
<br />
This method does exactly the same as "slideToObject", except than you can specify some (x,y) coordinates. This way, "mobile_obj" will slide to the specified x,y position relatively to "target_obj".<br />
<br />
Example: slide a token to some place on the board, 10 pixels to the bottom:<br />
<pre><br />
this.slideToObjectPos( "some_token", "some_place_on_board", 0, 10 ).play();<br />
</pre><br />
<br />
'''this.slideTemporaryObject( mobile_obj_html, mobile_obj_parent, from, to, duration, delay )'''<br />
<br />
This method is useful when you want to slide a temporary HTML object from one place to another. As this object does not exists before the animation and won't remain after, it could be complex to create this object (with dojo.place), to place it at its origin (with placeOnObject) to slide it (with slideToObject) and to make it disappear at the end.<br />
<br />
slideTemporaryObject does all of this for you:<br />
* mobile_obj_html is a piece of HTML code that represent the object to slide.<br />
* mobile_obj_parent is the ID of an HTML element of your interface that will be the parent of this temporary HTML object.<br />
* from is the ID of the origin of the slide.<br />
* to is the ID of the target of the slide.<br />
* duration/delay works exactly like in "slideToObject"<br />
<br />
Example:<br />
<pre><br />
this.slideTemporaryObject( '<div class="token_icon"></div>', 'tokens', 'my_origin_div', 'my_target_div' );<br />
</pre><br />
<br />
'''this.slideToObjectAndDestroy: function( node, to, time, delay )'''<br />
<br />
This method is a handy shortcut to slide an existing HTML object to some place then destroy it upon arrival. It can be used for example to move a victory token or a card from the board to the player panel to show that the player earns it, then destroy it when we don't need to keep it visible on the player panel.<br />
<br />
It works the same as this.slideToObject and takes the same arguments. <br />
<br />
Example:<br />
<pre><br />
this.slideToObjectAndDestroy( "some_token", "some_place_on_board", 1000, 0 );<br />
</pre><br />
<br />
'''this.fadeOutAndDestroy( node, duration, delay )'''<br />
<br />
This function fade out the target HTML node, then destroy it.<br />
<br />
Example:<br />
<pre><br />
this.fadeOutAndDestroy( "a_card_that_must_disappear" );<br />
</pre><br />
<br />
CAREFUL: the HTML node still exists until during few milliseconds, until the fadeOut has been completed.<br />
<br />
'''Rotating elements'''<br />
<br />
You can check here [http://jimfulton.info/demos/dojo-animated-rotate.html an example of use] of Dojo to make an element rotate.<br />
<br />
This example combines "Dojo.Animation" method and a CSS3 property that allow you to rotate the element.<br />
<br />
IMPORTANT: to asses browser compatibility, you must select the CSS property to use just like in the example (see sourcecode below):<br />
<br />
<pre><br />
var transform;<br />
dojo.forEach(<br />
['transform', 'WebkitTransform', 'msTransform',<br />
'MozTransform', 'OTransform'],<br />
function (name) {<br />
if (typeof dojo.body().style[name] != 'undefined') {<br />
transform = name;<br />
}<br />
});<br />
// ... and then use "transform" as the name of your CSS property for rotation<br />
</pre><br />
<br />
'''Animation Callbacks'''<br />
<br />
If you wish to run some code only after an animation has completed you can do this by linking a callback method.<br />
<br />
<pre><br />
var animation_id = this.slideToObject( mobile_obj, target_obj, duration, delay );<br />
dojo.connect(animation_id, 'onEnd', dojo.hitch(this, 'callback_function', parameters));<br />
animation_id.play();<br />
<br />
âŠ<br />
<br />
callback_function: function(params) {<br />
// this will be called after the animation ends<br />
},<br />
</pre><br />
<br />
If you wish to call a second animation after the first (rather than general code) then you can use a dojo animation chain (see tutorial referenced above).<br />
<br />
=== Moving elements ===<br />
<br />
'''this.placeOnObject( mobile_obj, target_obj )'''<br />
<br />
placeOnObject works exactly like "slideToObject", except that the effect is immediate.<br />
<br />
This is not really an animation, but placeOnObject is frequently used before starting an animation.<br />
<br />
Example:<br />
<pre><br />
// (We just created an object "my_new_token")<br />
<br />
// Place the new token on current player board<br />
this.placeOnObject( "my_new_token", "overall_player_board_"+this.player_id );<br />
<br />
// Then slide it to its position on the board<br />
this.slideToObject( "my_new_token", "a_place_on_board" ).play();<br />
</pre><br />
<br />
'''this.placeOnObjectPos( mobile_obj, target_obj, target_x, target_y )'''<br />
<br />
This method works exactly like placeOnObject, except than you can specify some (x,y) coordinates. This way, "mobile_obj" will be placed to the specified x,y position relatively to "target_obj".<br />
<br />
'''this.attachToNewParent( mobile_obj, target_obj )'''<br />
<br />
With this method, you change the HTML parent of "mobile_obj" element. "target_obj" is the new parent of this element. The beauty of <br />
attachToNewParent is that the mobile_obj element DOES NOT MOVE during this process.<br />
<br />
Note: what happens is that the method calculate a relative position of mobile_obj to make sure it does not move after the HTML parent changes.<br />
<br />
Why using this method?<br />
<br />
Changing the HTML parent of an element can be useful for the following reasons:<br />
* When the HTML parent moves, all its child are moving with them. If some game elements is no more linked with a parent HTML object, you may want to attach it to another place.<br />
* The z_order (vertical order of display) depends on the position in the DOM, so you may need to change the parent of some game elements when they are moving in your game area.<br />
<br />
CAREFUL: this function destroys original object and places a clone onto a new parent, this will break all references to this HTML element (ex: dojo.connect).<br />
<br />
== Players input ==<br />
<br />
'''dojo.connect'''<br />
<br />
Used to associate a player event with one of your notification method.<br />
<br />
Example: associate a click on an element ("my_element") with one of our method ("onClickOnMyElement"):<br />
<pre><br />
dojo.connect( $('my_element'), 'onclick', this, 'onClickOnMyElement' );<br />
</pre><br />
<br />
Note: this is the only possible correct way to associate a player input event to your code, and you must not use anything else.<br />
<br />
'''this.checkAction( "my_action_name" )'''<br />
<br />
Usage: checkAction: function( action, nomessage )<br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
Restricted arguments names (please don't use them):<br />
<br />
* "action"<br />
* "module"<br />
* "class"<br />
return true if action is authorized (ie: the action is listed as a "possibleaction" in current game state).<br />
<br />
return false and display an error message if not (display no message if nomessage parameter is true). The displayed error message could be either "This move is not allowed at this moment" or "An action is already in progress".<br />
<br />
Example:<br />
<pre><br />
function onClickOnGameElement( evt )<br />
{<br />
if( this.checkAction( "my_action" ) )<br />
{<br />
// Do the action<br />
}<br />
}<br />
</pre><br />
<br />
'''this.checkPossibleActions( "my_action_name" )'''<br />
<br />
Usage: checkPossibleActions: function( action, nomessage )<br />
<br />
* this is independent of the player being active, so can be used instead of this.checkAction(). This is particularly useful for multiplayer states when the player is not active in a 'player may like to change their mind' scenario. <br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
Restricted arguments names (please don't use them):<br />
* "action"<br />
* "module"<br />
* "class"<br />
<br />
'''this.ajaxcall( url, parameters, obj_callback, callback, callback_error )'''<br />
<br />
This method must be used to send a player input to the game server.<br />
<br />
* url: the url of the action to perform. For a game, it must be: "/<mygame>/<mygame>/myAction.html"<br />
* parameters: an array of parameter to send to the game server. Note that "lock:true" must always be specified in this list of parameter in order the interface can be locked during the server call.<br />
* obj_callback: must be set to "this".<br />
* callback: a function to trigger when the server returns and everything went fine.<br />
* callback_error: (optional and rarely used) a function to trigger when the server returns an error.<br />
<br />
Usage:<br />
<pre><br />
this.ajaxcall( '/mygame/mygame/myaction.html', { lock: true, <br />
arg1: myarg1, <br />
arg2: myarg2, <br />
...<br />
}, this, function( result ) {<br />
// Do some stuff after a successful call<br />
} );<br />
</pre><br />
<br />
'''this.confirmationDialog()'''<br />
<br />
Display a confirmation dialog with a yes/no choice.<br />
<br />
We advice you to NOT use this function unless the player action is really critical and could ruins the game, because it slows down the game and upset players.<br />
<br />
Usage: this.confirmationDialog( "Question to displayed", callback_function_if_click_on_yes );<br />
<br />
Example:<br />
<pre><br />
this.confirmationDialog( _('Are you sure to use this bonus (points penalty at the end of the game) ?'),<br />
dojo.hitch( this, function() {<br />
this.ajaxcall( '/seasons/seasons/useBonus.html',<br />
{ id:bonus_id, lock:true }, this, function( result ) {} );<br />
} ) ); <br />
</pre><br />
<br />
<br />
; addEventToClass: function( cssClassName, eventName, functionName )<br />
: Same as dojo.connect(), but for all the nodes set with the specified cssClassName<br />
<br />
'''this.addActionButton( id, label, method, (opt)destination, (opt)blinking, (opt)color )'''<br />
<br />
You can use this method to add an action button in the main action status bar.<br />
<br />
Arguments:<br />
* id: an element ID that should be unique in your HTML DOM document.<br />
* label: the text of the button. Should be translatable (use _() function).<br />
* method: the name of your method that must be triggered when the player clicks on this button.<br />
* destination (optional): deprecated, do not use this. Use '''null''' as value if you need to specify other arguments.<br />
* blinking (optional): if set to '''true''', the button is going blink to catch player's attention. Please don't abuse of blinking button.<br />
* color: could be '''blue''' (default), '''red''' or '''gray'''.<br />
<br />
You should only use this method in your "onUpdateActionButtons" method. Usually, you use it like this (from Hearts example):<br />
<br />
<pre><br />
onUpdateActionButtons: function( stateName, args ) {<br />
<br />
if (this.isCurrentPlayerActive()) { <br />
switch( stateName ) {<br />
case 'giveCards':<br />
this.addActionButton( 'giveCards_button', _('Give selected cards'), 'onGiveCards' ); <br />
break;<br />
}<br />
}<br />
}, <br />
</pre><br />
<br />
In the example above, we are adding a "Give selected cards" button in the case we are on game state "giveCards". When player clicks on this button, it triggers our "onGiveCards" method.<br />
<br />
Example using blinking red button:<br />
<pre><br />
this.addActionButton( 'commit_button', _('Confirm'), 'onConfirm', null, true, 'red'); <br />
</pre><br />
<br />
Note: at least in studio example above will make button huge, because it sets it display of blinking things to '''block''', <br />
if you don't like it you have to change css display value<br />
of the button to inline-block (the id of the button is the first argument, i.e 'commit_button' in example above)<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Notifications ==<br />
<br />
When something happens on the server side, your game interface Javascript logic received a notification.<br />
<br />
Here's how you can handle these notifications on the client side.<br />
<br />
=== Subscribe to notifications ===<br />
<br />
Your Javascript "setupNotifications" method is the place where you can subscribe to notifications from your PHP code.<br />
<br />
Here's how you associate one of your Javascript method to a notification "playDisc" (from Reversi example):<br />
<br />
<pre><br />
// In setupNotifications method:<br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
</pre><br />
<br />
Note: the "playDisc" corresponds to the name of the notification you define it in your PHP code, in your "notifyAllPlayers" or "notifyPlayer" method.<br />
<br />
Then, you have to define your "notif_playDisc" method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addDiscOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
In a notification handler like our "notif_playDisc" method, you can access to all notifications arguments with "notif.args".<br />
<br />
Example:<br />
<pre><br />
// If you did this on PHP side:<br />
self::notifyAllPlayers( "myNotification", '', array( "myArgument" => 3 ) );<br />
<br />
// On Javascript side, you can access the "myArgument" like this:<br />
notif_myNotification: function( notif )<br />
{<br />
alert( "myArgument = " + notif.args.myArgument );<br />
}<br />
</pre><br />
<br />
<br />
=== Synchronous notifications ===<br />
<br />
When several notifications are received by your game interface, these notifications are processed immediately, one after the other, in the same exact order they have been generated in your PHP game logic.<br />
<br />
However, sometimes, you need to give some time to the players to figure out what happened on the game before jumping to the next notification. Indeed, in many games, they are a lot of automatic actions, and the computer is going to resolve all these actions very fast if you don't tell it not to do so.<br />
<br />
As an example, for Reversi, when someone is playing a disc, we want to wait 500 milliseconds before doing anything else in order the opponent player can figure out what move has been played.<br />
<br />
Here's how we do this, right after our subscription:<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 ); // Wait 500 milliseconds after executing the playDisc handler<br />
</pre><br />
<br />
<br />
=== Pre-defined notification types ===<br />
<br />
'''tableWindow''' - This defines notification to display [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialogs]], see below.<br />
<br />
'''message''' - This defines notification that shows on players log and have no other effect<br />
<br />
// You can call this on php side without doing anything on client side<br />
self::notifyAllPlayers( 'message', 'hello', array( ) );<br />
<br />
== Tooltips ==<br />
<br />
'''this.addTooltip( nodeId, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to the DOM node.<br />
<br />
Specify 'helpString' to display some information about "what is this game element?".<br />
Specify 'actionString' to display some information about "what happens when I click on this element?".<br />
<br />
You must specify both helpString and actionString. Most of the time, you should use only one and specify a void string ("") for the other one.<br />
<br />
Usually, _() must be used for the text to be marked for translation.<br />
<br />
"Delay" is an optional parameter. Usually, it is primarily used to specify a zero delay for some game element when the tooltip gives really important information for the game - but remember: no essential information must be placed in tooltips as they won't be displayed in some browsers (see [[BGA_Studio_Guidelines|Guidelines]]).<br />
<br />
Example:<br />
<pre><br />
this.addTooltip( 'cardcount', _('Number of cards in hand'), '' );<br />
</pre><br />
<br />
'''this.addTooltipHtml( nodeId, html, delay )'''<br />
<br />
Add an HTML tooltip to the DOM node (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
'''this.addTooltipToClass( cssClass, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to all the DOM nodes set with this cssClass. <br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips.<br />
<br />
'''this.addTooltipHtmlToClass( cssClass, html, delay )'''<br />
<br />
Add an HTML tooltip to to all the DOM nodes set with this cssClass (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips<br />
<br />
'''this.removeTooltip( nodeId )'''<br />
<br />
Remove a tooltip from the DOM node.<br />
<br />
== Dialogs, warning messages, confirmation dialogs, ... ==<br />
<br />
=== Warning messages ===<br />
<br />
Sometimes, there is something important that is happening on the game and you have to make sure all players get the message. Most of the time, the evolution of the game situation or the game log is enough, but sometimes you need something more visible.<br />
<br />
Ex: someone fulfill one of the end of the game condition, so this is the last turn.<br />
<br />
'''this.showMessage( msg, type )'''<br />
<br />
showMessage shows a message in a big rectangular area on the top of the screen of current player.<br />
<br />
* "msg" is the string to display. It should be translated.<br />
* "type" can be set to "info" or "error". If set to "info", the message will be an informative message on a white background. If set to "error", the message will be an error message on a red background.<br />
<br />
Important: the normal way to inform players about the progression of the game is the game log. "showMessage" is intrusive and should not be used often.<br />
<br />
=== Confirmation dialog ===<br />
<br />
'''confirmationDialog( message, yesHandler, noHandler )'''<br />
<br />
<br />
When an important action with a lot of consequences is triggered by the player, you may want to propose a confirmation dialog.<br />
<br />
CAREFUL: the general guidelines of BGA is to AVOID the use of confirmation dialog. Confirmation dialogs slow down the game and bother players. The players knows that they have to pay attention about each move when they are playing online.<br />
<br />
The situation where you should use a confirmation dialog are the following:<br />
* It must not happen very often during a game.<br />
* It must be linked to an action that can really "kill a game" if the player do not pay attention.<br />
* It must be something that can be done by mistake (ex: a link on the action status bar).<br />
<br />
How to display a confirmation dialog:<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to bake the pie?'), dojo.hitch( this, function() {<br />
this.bakeThePie();<br />
} ) ); <br />
return; // nothing should be called or done after calling this, all action must be done in the handler <br />
</pre><br />
<br />
=== Multiple choice dialog ===<br />
You can use this dialog to give user a choice with small amount of options:<br />
<pre><br />
var keys = [1,5,10];<br />
this.multipleChoiceDialog(<br />
_('How many bugs to fix?"), keys, <br />
dojo.hitch(this, function(choice) {<br />
var bugchoice = keys[choice];<br />
console.log('dialog callback with '+bugchoice);<br />
this.ajaxcall( '/mygame/mygame/fixBugs.html', { bugs: bugchoice}, this, function( result ) {} ); }));<br />
</pre><br />
<br />
=== Dialogs ===<br />
<br />
As a general rule, you shouldn't use dialogs windows.<br />
<br />
BGA guidelines specify that all game elements should be displayed on the main screen. Players can eventually scroll down to see game elements they don't need to see anytime, and you may eventually create anchors to move between game area section. Of course dialogs windows are very practical, but the thing is: all players know how to scroll down, and not all players know how to show up your dialog window. In addition, when the dialog shows up, players can't access the other game components.<br />
<br />
Sometimes although, you need to display a dialog window. Here is how you do this:<br />
<br />
<br />
<br />
// Create the new dialog over the play zone. You should store the handler in a member variable to access it later<br />
this.myDlg = new ebg.popindialog();<br />
this.myDlg.create( 'myDialogUniqueId' );<br />
this.myDlg.setTitle( _("my dialog title to translate") );<br />
this.myDlg.setMaxWidth( 500 ); // Optional<br />
<br />
// Create the HTML of my dialog. <br />
// The best practice here is to use [[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]<br />
var html = this.format_block( 'jstpl_myDialogTemplate', { <br />
arg1: myArg1,<br />
arg2: myArg2,<br />
...<br />
} ); <br />
<br />
// Show the dialog<br />
this.myDlg.setContent( html ); // Must be set before calling show() so that the size of the content is defined before positioning the dialog<br />
this.myDlg.show();<br />
<br />
// Now that the dialog has been displayed, you can connect your method to some dialog elements<br />
// Example, if you have an "OK" button in the HTML of your dialog:<br />
dojo.connect( $('my_ok_button'), 'onclick', this, function(evt){<br />
evt.preventDefault();<br />
this.myDlg.destroy();<br />
} );<br />
<br />
If necessary, you can remove the default top right corner 'close' icon, or replace the function called when it is clicked:<br />
// Removes the default close icon<br />
this.myDlg.hideCloseIcon();<br />
<br />
// Replace the function call when it's clicked<br />
this.myDlg.replaceQuitCallback( function() { ... } );<br />
<br />
=== Scoring dialogs ===<br />
<br />
Sometimes at the end of a round you want to display a big table that details the points wins in each section of the game.<br />
<br />
Example: in Hearts game, we display at the end of each round the number of "heart" cards collected by each player, the player who collected the Queen of Spades, and the total number of points loose by each player.<br />
<br />
Scoring dialogs are managed entirely on '''PHP side''', but they are described here as their effects are visible only on client side.<br />
<br />
Displaying a scoring dialog is quite simple and is using a special notification type: "tableWindow":<br />
<pre><br />
// on PHP side:<br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table<br />
) ); <br />
</pre><br />
<br />
The "table" argument is a 2 dimensional PHP array that describe the table you want to display, line by line and column by column.<br />
<br />
Example: display an 3x3 array of strings<br />
<pre><br />
$table = array(<br />
array( "one", "two", "three" ), // This is my first line<br />
array( "four", "five", "six" ), // This is my second line<br />
array( "seven", "height", "nine" ) // This is my third line<br />
);<br />
</pre><br />
<br />
As you can see above, in each "cell" of your array you can display a simple string value. But you can also display a complex value with a template and associated arguments like this:<br />
<pre><br />
$table = array(<br />
array( "one", "two", array( "str" => clienttranslate("a string with an ${argument}"), "args" => array( 'argument' => 'argument_value' ) ) ),<br />
array( "four", "five", "six" ), <br />
array( "seven", "height", "nine" )<br />
);<br />
</pre><br />
<br />
This is especially useful when you want to display player names with colors. Example from "Hearts":<br />
<pre><br />
$firstRow = array( '' );<br />
foreach( $players as $player_id => $player )<br />
{<br />
$firstRow[] = array( 'str' => '${player_name}',<br />
'args' => array( 'player_name' => $player['player_name'] ),<br />
'type' => 'header'<br />
);<br />
}<br />
$table[] = $firstRow;<br />
</pre><br />
<br />
You can also use three extra attributes in the parameter array for the notification:<br />
<br />
<pre><br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table,<br />
"header" => array('str' => clienttranslate('Table header with parameter ${number}'),<br />
'args' => array( 'number' => 3 ),<br />
),<br />
"footer" => '<div>Some footer</div>',<br />
"closing" => clienttranslate( "Closing button label" )<br />
) ); <br />
</pre><br />
<br />
*'''header''': the content for this parameter will display before the table (also, the html will be parsed and player names will be colored according to the current game colors). <br />
*'''footer''': the content for this parameter will display after the table (no parsing for coloring the player names)<br />
*'''closing''': if this parameter is used, a button will be displayed with this label at the bottom of the popup and will allow players to close it (more easily than by clicking the top right 'cross' icon).<br />
<br />
=== Scoring animated display ===<br />
<br />
Sometimes (Terra Mystica final scoring for example), you may want to display a score value over an element to make the scoring easier to follow for the players.<br />
You can do it with:<br />
<br />
<pre><br />
this.displayScoring( anchor_id, color, score, duration );<br />
</pre><br />
<br />
'''anchor_id''': ID of the element to place the animated score onto (without the '#') <br />
<br />
'''color''': hexadecimal RGB representation of the color (should be the color of the scoring player), but without a leading '#'. For instance, 'ff0000' for red.<br />
<br />
'''score''': numeric score to display, prefixed by a '+'<br />
<br />
'''duration''': animation duration in milliseconds<br />
<br />
=== Speech bubble ===<br />
<br />
For better interactivity in some games (Love Letter for example), you may use comic book style speech bubbles to express the players voices.<br />
This is done with:<br />
<br />
<pre><br />
this.showBubble( anchor_id, text, delay, duration, custom_class )<br />
</pre><br />
<br />
delay in milliseconds is optional (default 0)<br />
<br />
duration in milliseconds is optional (default 3000)<br />
<br />
custom_class is optional, if you need to override the default bubble style<br />
<br />
'''Warning''': if your bubble could overlap other active elements of the interface (buttons in particular), as it stays in place even after disappearing, you should use a custom class to give it the style "pointer-events: none;" in order to intercept click events.<br />
<br />
== Update players score ==<br />
<br />
Increase a player score (with a positive or negative number):<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].incValue( score_delta );<br />
</pre><br />
<br />
Set a player score to a specific value:<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].setValue( new_score );<br />
</pre><br />
<br />
Set a player score to a specific value with animation :<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].toValue( new_score );<br />
</pre><br />
<br />
== Players panels ==<br />
<br />
=== Adding stuff to player's panel ===<br />
<br />
At first, create a new "JS template" string in your template (tpl) file:<br />
<br />
(from Gomoku example)<br />
<pre><br />
var jstpl_player_board = '\<div class="cp_board">\<br />
<div id="stoneicon_p${id}" class="gmk_stoneicon gmk_stoneicon_${color}"></div><span id="stonecount_p${id}">0</span>\<br />
</div>';<br />
</pre><br />
<br />
Then, you add this piece of code in your JS file to add this template to each player panel:<br />
<br />
<pre><br />
// Setting up player boards<br />
for( var player_id in gamedatas.players )<br />
{<br />
var player = gamedatas.players[player_id];<br />
<br />
// Setting up players boards if needed<br />
var player_board_div = $('player_board_'+player_id);<br />
dojo.place( this.format_block('jstpl_player_board', player ), player_board_div );<br />
}<br />
</pre><br />
<br />
(Note: the code above is of course from your "setup" function in your Javascript).<br />
<br />
Very often, you have to distinguish current player and others players. In this case, you just have to create another JS template (ex: jstpl_otherplayer_board) and use it when "player_id" is different than "this.player_id".<br />
<br />
=== Player's panel disabling/enabling ===<br />
<br />
'''this.disablePlayerPanel( player_id )'''<br />
<br />
Disable given player panel (the panel background become gray).<br />
<br />
Usually, this is used to signal that this played passes, or will be inactive during a while.<br />
<br />
Note that the only effect of this is visual. There are no consequences on the behaviour of the panel itself.<br />
<br />
'''this.enablePlayerPanel( player_id )'''<br />
<br />
Enable a player panel that has been disabled before.<br />
<br />
'''this.enableAllPlayerPanels()'''<br />
<br />
Enable all player panels that has been disabled before.<br />
<br />
== Image loading ==<br />
<br />
See also [[Game_art:_img_directory]].<br />
<br />
'''Be careful''': by default, ALL images of your img directory are loaded on a player's browser when he loads the game. For this reason, don't let in your img directory images that are not useful, otherwise it's going to slowdown the game load.<br />
<br />
'''dontPreloadImage( image_file_name )'''<br />
<br />
Using dontPreloadImage, you tell the interface to not preload a specific image in your img directory.<br />
<br />
Example of use:<br />
<pre><br />
this.dontPreloadImage( 'cards.png' );<br />
</pre><br />
<br />
This is particularly useful if for example you have 2 different themes for a game. To accelerate the loading of the game, you can specify to not preload images corresponding to the other theme.<br />
<br />
Another example of use: in "Gosu" game with Kamakor extension, you play with 5 sets of cards among 10 available. Cards images are organized by sets, and we only preload the images corresponding to the 5 current sets with '''ensureSpecificGameImageLoading( image_file_names_array )'''.<br />
<pre><br />
// By default, do not preload anything<br />
this.dontPreloadImage( 'cards.png' );<br />
this.dontPreloadImage( 'clan1.png' );<br />
this.dontPreloadImage( 'clan2.png' );<br />
this.dontPreloadImage( 'clan3.png' );<br />
this.dontPreloadImage( 'clan4.png' );<br />
this.dontPreloadImage( 'clan5.png' );<br />
this.dontPreloadImage( 'clan6.png' );<br />
this.dontPreloadImage( 'clan7.png' );<br />
this.dontPreloadImage( 'clan8.png' );<br />
this.dontPreloadImage( 'clan9.png' );<br />
this.dontPreloadImage( 'clan10.png' );<br />
var to_preload = [];<br />
for( i in this.gamedatas.clans )<br />
{<br />
var clan_id = this.gamedatas.clans[i];<br />
to_preload.push( 'clan'+clan_id+'.png' );<br />
}<br />
if( to_preload.length == 5 )<br />
{<br />
this.ensureSpecificGameImageLoading( to_preload );<br />
}<br />
</pre><br />
<br />
'''Note:''' You don't need to specify to not preload game box images (game_box.png, game_box75.png...) since they are not preloaded by default.<br />
<br />
== Other useful stuff ==<br />
<br />
<br />
'''dojo.hitch'''<br />
<br />
With dojo.hitch, you can create a callback function that will run with your game object context whatever happen.<br />
<br />
Typical example: display a BGA confirmation dialog with a callback function created with dojo.hitch:<br />
<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), dojo.hitch( this, function() {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, function( result ) {} );<br />
} ) ); <br />
</pre><br />
<br />
In the example above, using dojo.hitch, we ensure that the "this" object will be set when the callback is called.<br />
<br />
<br />
; updateCounters(counters)<br />
: Useful for updating game counters in the player panel (such as resources). <br />
: 'counters' arg is an associative array [counter_name_value => [ 'counter_name' => counter_name_value, 'counter_value' => counter_value_value], ... ]<br />
: All counters must be referenced in this.gamedatas.counters and will be updated.<br />
: DOM objects referenced by 'counter_name' will have their innerHTML updated with 'counter_value'.<br />
<br />
<br />
'''onScreenWidthChange()'''<br />
This function can be overridden in your game to manage some resizing on the client side when the browser window is resized. This function is also triggered at load time, so it can be used to adapt to the viewport size at the start of the game too.<br />
<br />
<br />
'''updatePageTitle()'''<br />
This function allows to update the current page title and turn description according to the game state. If the current game state description this.gamedatas.gamestate.descriptionmyturn is modified before calling the function, it allows to update the turn description without changing state.<br />
<br />
Example from Terra Mystica:<br />
<pre><br />
onClickFavorTile: function( evt )<br />
{<br />
[...]<br />
<br />
if ( ... ) {<br />
<br />
this.gamedatas.gamestate.descriptionmyturn = _('Special action: ') + _('Advance 1 space on a Cult track');<br />
this.updatePageTitle();<br />
this.removeActionButtons();<br />
<br />
this.addActionButton( 'action_confirm1', _("Fire"),<br />
function() { ... }<br />
);<br />
this.addActionButton( 'action_confirm2', _("Water"),<br />
function() { ... }<br />
);<br />
this.addActionButton( 'action_confirm3', _("Earth"),<br />
function() { ... }<br />
);<br />
this.addActionButton( 'action_confirm4', _("Air"),<br />
function() { ... }<br />
);<br />
<br />
this.addActionButton( 'action_cancel', _("Cancel"), function() { ... }, false, false, 'gray'<br />
);<br />
<br />
return;<br />
}<br />
<br />
[...]<br />
<br />
}<br />
</pre><br />
<br />
== BGA GUI components ==<br />
<br />
BGA framework provides some useful ready-to-use components for the game interface:<br />
<br />
[[Studio#BGA_Studio_game_components_reference]]<br />
<br />
Note that each time you are using an additional component, you must declare it at the top of your Javascript file in the list of modules used.<br />
<br />
Example if you are using "ebg.stock":<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <=== we are using ebg.stock module<br />
],<br />
</pre></div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Players_actions:_yourgamename.action.php&diff=4849
Players actions: yourgamename.action.php
2020-07-02T19:51:08Z
<p>Cpasbanal: Add the fact you cannot retrieve data from an ajax call (even if callback exists which is wierd somehow)</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Purpose of this file ==<br />
<br />
With this file, you define all the player entry points (i.e., possible game actions) for your game.<br />
<br />
This file is a sort of "bridge" between the AJAX calls you perform from the Javascript client side, and your main PHP code in "yourgame.game.php".<br />
<br />
The role of the methods defined in this file is to filter the arguments, format them a bit, and then call a corresponding PHP method from your main game logic ("yourgame.game.php" file).<br />
<br />
Methods in this file should be short: no game logic must be introduced here.<br />
<br />
== Example of typical action method ==<br />
<br />
(from Reversi example)<br />
<br />
<pre><br />
public function playDisc()<br />
{<br />
self::setAjaxMode(); <br />
$x = self::getArg( "x", AT_posint, true );<br />
$y = self::getArg( "y", AT_posint, true );<br />
$result = $this->game->playDisc( $x, $y );<br />
self::ajaxResponse( );<br />
}<br />
</pre><br />
<br />
== Methods to use in action methods ==<br />
<br />
'''function setAjaxMode()'''<br />
<br />
Must be used at the beginning of each action method.<br />
<br />
'''function ajaxResponse()'''<br />
<br />
Must be used at the end of each action method.<br />
<br />
'''function getArg( $argName, $argType, $mandatory=false, $default=NULL, $argTypeDetails=array(), $bCanFail=false )'''<br />
<br />
This method must be used to retrieve the arguments sent with your AJAX query.<br />
<br />
You must ''not'' use "_GET", "_POST" or equivalent PHP variables to do this, as it is unsafe.<br />
<br />
This method uses the following arguments:<br />
<br />
* argName: the name of the argument to retrieve.<br />
* argType: the type of the argument. You should use one of the following:<br />
'AT_int' for an integer<br />
'AT_posint' for a positive integer <br />
'AT_float' for a float<br />
'AT_bool' for 1/0/true/false<br />
'AT_enum' for an enumeration (argTypeDetails lists the possible values as an array)<br />
'AT_alphanum' for a string with 0-9a-zA-Z_ and space<br />
'AT_numberlist' for a list of numbers separated with "," or ";" (example: 1,4;2,3;-1,2).<br />
'AT_base64' for a base64-encoded string.<br />
* mandatory: specify "true" if the argument is mandatory.<br />
* default: if mandatory=false, you can specify here a default value in case the argument is not present.<br />
* argTypeDetails: see AT_enum above.<br />
* bCanFail: if true, specify that it may be possible that the argument won't be of the type specified by argType (and then do not log this as a fatal error in the system, and return a standard exception to the player).<br />
<br />
<br />
'''function isArg( $argName )'''<br />
<br />
This is a useful method when you only want to check if an argument is present or not present in your AJAX request (and don't care about the value).<br />
<br />
It returns "true" or "false" according to whether "argName" has been specified as an argument of the AJAX request or not.<br />
<br />
== Useful tip: retrieve a list of numbers ==<br />
<br />
If your Javascript sends a list of integers separated by ";" (example: "1;2;3;4") as an argument, you can transform them into a PHP array with the following:<br />
<br />
<pre><br />
public function playCards()<br />
{<br />
self::setAjaxMode(); <br />
<br />
$card_ids_raw = self::getArg( "card_ids", AT_numberlist, true );<br />
<br />
// Removing last ';' if exists<br />
if( substr( $card_ids_raw, -1 ) == ';' )<br />
$card_ids_raw = substr( $card_ids_raw, 0, -1 );<br />
if( $card_ids_raw == '' )<br />
$card_ids = array();<br />
else<br />
$card_ids = explode( ';', $card_ids_raw );<br />
<br />
$this->game->playCards( $card_ids );<br />
self::ajaxResponse( );<br />
}<br />
</pre><br />
<br />
== Retrieving data from ajax call ==<br />
<br />
Note that this is not possible to return any result from a player action: it should return nothing (action went fine) or an exception (action unsuccessful).<br />
<br />
The typical way to implement this is using games states with game state arguments. Eventually, use player notifications.</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Deck&diff=4830
Deck
2020-07-01T08:33:02Z
<p>Cpasbanal: /* Create a new Deck component */ Add precision if multiple decks</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
"Deck" is one of the most useful component on the PHP side. With "Deck", you can manage the cards in your game on the server side.<br />
<br />
Using "deck", you will be able to use the following features without writing a single SQL database request:<br />
* Place cards in a pile, shuffle cards, draw cards one by one or many at a time.<br />
* "Auto-reshuffle" the discard pile into the deck when the deck is empty.<br />
* Move cards between different locations: hands of players, the table, etc.<br />
<br />
<br />
== Using Deck: Hearts example ==<br />
<br />
The Deck component is extensively used in the sample ''Hearts'' card game. You will find in "hearts.game.php" that the object "$this->cards" is used many times.<br />
<br />
== Deck overview ==<br />
<br />
With Deck component, you manage all cards of your game.<br />
<br />
=== The 5 properties of each card ===<br />
<br />
Using the Deck component, each card will have 5 properties:<br />
* '''id''': This is the unique ID of each card.<br />
* '''type''' and '''type_arg''': These two values define the type of your card (i.e., what sort of card is this?).<br />
* '''location''' and '''location_arg''': These two values define where the card is at now.<br />
<br />
The id, type, and type_arg properties are constants throughout the game. location and location_arg change when your cards move from one place to another in the game area.<br />
<br />
'''id''' is the unique ID of each card. Two cards cannot have the same ID. IDs are generated automatically by the Deck component when you create cards during the Setup phase of your game.<br />
<br />
'''type''' and '''type_arg''' defines the type of your card.<br />
<br />
'''type''' is a short string, and '''type_arg''' is an integer.<br />
<br />
You can use these two values as you like to make sure you will be able to identify the different cards in the game. See usage of "type" and "type_arg" below.<br />
<br />
Examples of usage of "type" and "type_arg":<br />
* In ''Hearts'', "type" represents the color (suite) of the card (1 to 4) and "type_arg" is the value of the card (1, 2, ... 10, J, Q, K).<br />
* In ''Seasons'', "type" represents the type of the card (e.g., 1 is Amulet of Air, 2 is Amulet of Fire, etc...). type_arg is not used.<br />
* In ''Takenoko'', a Deck component is used for objective cards. "type" is the kind of objective (irrigation/panda/plot) and "type_arg" is the ID of the specific objective to realize (e.g., "green bamboo x4"). Note that a second Deck component is used in ''Takenoko'' to manage the "garden plot" pile.<br />
<br />
'''location''' and '''location_arg''' define where a card is at now. '''location''' is a short string, and '''location_arg''' is an integer.<br />
<br />
You can use 'location' and 'location_arg' as you like, to move your card within the game area.<br />
<br />
There are 3 special 'location' values that Deck manages automatically. You can choose to use these locations or not, depending on your needs:<br />
* 'deck': the 'deck' location is a standard draw deck. Cards are placed face down in a stack and are drawn in sequential order during the game. 'location_arg' is used to specify where the card is located within the stack (the card with the highest location_arg value is the next to be drawn).<br />
* 'hand': the 'hand' location represents cards in a player's hand. 'location_arg' is set to the ID of each player.<br />
* 'discard': the 'discard' location is used for discard piles. Card in 'discard' may be reshuffled into the deck if needed (see "autoreshuffle").<br />
<br />
<br />
Tips: using the Deck component, you will use generic properties ("location", "type_arg",...) for specific purposes in your game. Thus, during the design step before realizing your game, take a few minutes to write down the exact meaning of each of these generic properties in the context of your game.<br />
<br />
=== Create a new Deck component ===<br />
<br />
For each Deck component in your game, you need to create a dedicated table in the SQL database. This table has a standard format. In practice, if you just want to have a Deck component named "card", you can copy/paste the following into your "dbmodel.sql" file:<br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `card` (<br />
`card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,<br />
`card_type` varchar(16) NOT NULL,<br />
`card_type_arg` int(11) NOT NULL,<br />
`card_location` varchar(16) NOT NULL,<br />
`card_location_arg` int(11) NOT NULL,<br />
PRIMARY KEY (`card_id`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;<br />
</pre><br />
<br />
Note: the database schema of this table does not have to be exactly what is listed above. You can increase the size of the fields or add more fields. For additional fields<br />
you just have to do manual queries.<br />
<br />
Once you have done this (and restarted your game), you can declare the Deck component in your PHP code in your class constructor. For ''Hearts'' for example, I added to the "Hearts()" method:<br />
<br />
<pre><br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
</pre><br />
<br />
Note that we specify "card" here: the name of our previously created table. This means you can create several "Deck" components with multiple tables:<br />
<br />
<pre><br />
$this->firstKindCards = self::getNew( "module.common.deck" );<br />
$this->firstKindCards ->init( "first_kind_card" );<br />
$this->secondKindCards = self::getNew( "module.common.deck" );<br />
$this->secondKindCards ->init( "second_kind_card" );<br />
</pre><br />
<br />
Most of the time this is not useful; a Deck component should manage all objects of the same kind (i.e., all cards in the game).<br />
Note that you need to create a table for each "Deck", table name should be "first_kind_card" but the fields must remain "card_id", "card_type" and so on.<br />
<br />
Afterwards, we can initialize your "Deck" by creating all the cards of the game. Generally, this is done only once during the game, in the "setupNewGame" method.<br />
<br />
The "Deck" component provides a fast way to initialize all your cards at once: createCards. Here is how it is used for "Hearts":<br />
<pre><br />
// Create cards<br />
$cards = array();<br />
foreach( $this->colors as $color_id => $color ) // spade, heart, diamond, club<br />
{<br />
for( $value=2; $value<=14; $value++ ) // 2, 3, 4, ... K, A<br />
{<br />
$cards[] = array( 'type' => $color_id, 'type_arg' => $value, 'nbr' => 1);<br />
}<br />
}<br />
<br />
$this->cards->createCards( $cards, 'deck' );<br />
</pre><br />
<br />
As you can see, "createCards" takes a description of all cards of the game. For each type of card, you have to specify its "type", "type_arg" and the number of card to create. "createCards" create all cards and place them into the "deck" location (as specified in the second argument).<br />
<br />
Now, you are ready to use "Deck"!<br />
<br />
=== Simple examples using Deck ===<br />
<br />
(Most examples are from "Hearts" game)<br />
<br />
<pre><br />
// In "getAllDatas', we need to send to the current player all the cards he has in hand:<br />
$result['hand'] = $this->cards->getCardsInLocation( 'hand', $player_id );<br />
</pre><br />
<br />
<pre><br />
// At some time we want to check if all the cards (52) are in player's hands:<br />
if( $this->cards->countCardInLocation( 'hand' ) == 52 )<br />
// do something<br />
</pre><br />
<br />
<pre><br />
// When a player plays a card in front of him on the table:<br />
$this->cards->moveCard( $card_id, 'cardsontable', $player_id );<br />
<br />
// Note the use of the custom location 'cardsontable' here to keep track of cards on the table.<br />
</pre><br />
<br />
<br />
<pre><br />
// This is a new hand: let's gather all cards from everywhere in the deck:<br />
$this->cards->moveAllCardsInLocation( null, "deck" );<br />
<br />
// And then shuffle the deck<br />
$this->cards->shuffle( 'deck' );<br />
<br />
// And then deal 13 cards to each player<br />
// Deal 13 cards to each players<br />
// Create deck, shuffle it and give 13 initial cards<br />
$players = self::loadPlayersBasicInfos();<br />
foreach( $players as $player_id => $player )<br />
{<br />
$cards = $this->cards->pickCards( 13, 'deck', $player_id );<br />
<br />
// Notify player about his cards<br />
self::notifyPlayer( $player_id, 'newHand', '', array( <br />
'cards' => $cards<br />
) );<br />
} <br />
<br />
// Note the use of "notifyPlayer" instead of "notifyAllPlayers": new cards is a private information ;) <br />
</pre><br />
<br />
== Deck component reference ==<br />
<br />
=== Initializing Deck component ===<br />
<br />
'''init( $table_name )'''<br />
<br />
Initialize the Deck component.<br />
<br />
Argument:<br />
* table_name: name of the DB table used by this Deck component.<br />
<br />
Must be called before any other Deck method.<br />
<br />
Usually, init is called in your game constructor.<br />
<br />
Example with Hearts:<br />
<br />
<pre><br />
function Hearts( )<br />
{<br />
(...)<br />
<br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
}<br />
</pre> <br />
<br />
'''createCards( $cards, $location='deck', $location_arg=null )'''<br />
<br />
Create card items in your deck component. Usually, all card items are created once, during the setup phase of the game.<br />
<br />
"cards" describe all cards that need to be created. "cards" is an array with the following format:<br />
<br />
<pre><br />
// Create 1 card of type "1" with type_arg=99,<br />
// and 4 cards of type "2" with type_arg=12,<br />
// and 2 cards of type "3" with type_arg=33<br />
<br />
$cards = array(<br />
array( 'type' => 1, 'type_arg' => 99, 'nbr' => 1 ),<br />
array( 'type' => 2, 'type_arg' => 12, 'nbr' => 4 ),<br />
array( 'type' => 3, 'type_arg' => 33, 'nbr' => 2 )<br />
...<br />
);<br />
</pre><br />
<br />
Note: During the "createCards" process, Deck generates unique IDs for all card items.<br />
<br />
Note: createCards is optimized to create a lot of cards at once. Do not use it to create cards one by one.<br />
<br />
If "location" and "location_arg" arguments are not set, newly created cards are placed in the "deck" location. If "location" (and optionally location_arg) is specified, cards are created for this specific location.<br />
<br />
=== Card standard format ===<br />
<br />
When Deck component methods are returning one or several cards, the following format is used:<br />
<br />
<pre><br />
array(<br />
'id' => .., // the card ID<br />
'type' => .., // the card type<br />
'type_arg' => .., // the card type argument<br />
'location' => .., // the card location<br />
'location_arg' => .. // the card location argument<br />
);<br />
</pre><br />
<br />
=== Picking cards ===<br />
<br />
'''pickCard( $location, $player_id )'''<br />
<br />
Pick a card from a "pile" location (ex: "deck") and place it in the "hand" of specified player.<br />
<br />
Return the card picked or "null" if there are no more card in given location.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
'''pickCards( $nbr, $location, $player_id )'''<br />
<br />
Pick "$nbr" cards from a "pile" location (ex: "deck") and place them in the "hand" of specified player.<br />
<br />
Return an array with the cards picked (indexed by the card ID), or "null" if there are no more card in given location.<br />
<br />
Note that the number of cards picked can be less than "$nbr" in case there are not enough cards in the pile location.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below). In case there are not enough cards in the pile, all remaining cards are picked first, then the auto-reshuffle is triggered, then the other cards are picked.<br />
<br />
'''pickCardForLocation( $from_location, $to_location, $location_arg=0 )'''<br />
<br />
This method is similar to 'pickCard', except that you can pick a card for any sort of location and not only the "hand" location.<br />
<br />
* from_location is the "pile" style location from where you are picking a card.<br />
* to_location is the location where you will place the card picked.<br />
* if "location_arg" is specified, the card picked will be set with this "location_arg".<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
'''pickCardsForLocation( $nbr, $from_location, $to_location, $location_arg=0, $no_deck_reform=false )'''<br />
<br />
This method is similar to 'pickCards', except that you can pick cards for any sort of location and not only the "hand" location.<br />
<br />
* from_location is the "pile" style location from where you are picking some cards.<br />
* to_location is the location where you will place the cards picked.<br />
* if "location_arg" is specified, the cards picked will be set with this "location_arg".<br />
* if "no_deck_reform" is set to "true", the auto-reshuffle feature is disabled during this method call.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
=== Moving cards ===<br />
<br />
'''moveCard( $card_id, $location, $location_arg=0 )'''<br />
<br />
Move the specific card to given location.<br />
<br />
* card_id: ID of the card to move.<br />
* location: location where to move the card.<br />
* location_arg: if specified, location_arg where to move the card. If not specified "location_arg" will be set to 0.<br />
<br />
<br />
'''moveCards( $cards, $location, $location_arg )'''<br />
<br />
Move the specific cards to given location.<br />
<br />
* cards: an array of IDs of cards to move.<br />
* location: location where to move the cards.<br />
* location_arg: if specified, location_arg where to move the cards. If not specified "location_arg" will be set to 0.<br />
<br />
'''insertCard( $card_id, $location, $location_arg )'''<br />
<br />
Move a card to a specific "pile" location where card are ordered.<br />
<br />
If location_arg place is already taken, increment all cards after location_arg in order to insert new card at this precise location.<br />
<br />
(note: insertCardOnExtremePosition method below is more useful in most of the case)<br />
<br />
'''insertCardOnExtremePosition( $card_id, $location, $bOnTop )'''<br />
<br />
Move a card on top or at bottom of given "pile" type location.<br />
<br />
'''moveAllCardsInLocation( $from_location, $to_location, $from_location_arg=null, $to_location_arg=0 )'''<br />
<br />
Move all cards in specified "from" location to given location.<br />
<br />
* from_location: where to take the cards<br />
* to_location: where to put the cards<br />
* from_location (optional): if specified, only cards with given "location_arg" are moved.<br />
* to_location (optional): if specified, cards moved "location_arg" is set to given value. Otherwise location_arg is set to zero.<br />
<br />
Note: if you want to keep "location_arg" untouched, you should use "moveAllCardsInLocationKeepOrder" below.<br />
<br />
'''moveAllCardsInLocationKeepOrder( $from_location, $to_location )'''<br />
<br />
Move all cards in specified "from" location to given "to" location. This method does not modify the "location_arg" of cards.<br />
<br />
'''playCard( $card_id )'''<br />
<br />
Move specified card at the top of the "discard" location.<br />
<br />
Note: this is an alias for: insertCardOnExtremePosition( $card_id, "discard", true )<br />
<br />
=== Get cards informations ===<br />
<br />
'''getCard( $card_id )'''<br />
<br />
Get specific card information.<br />
<br />
Return null if this card is not found.<br />
<br />
'''getCards( $cards_array )'''<br />
<br />
Get specific cards information.<br />
<br />
cards_array is an array of cards ID.<br />
<br />
If some cards are not found or if some cards IDs are specified multiple times, the method throws an (unexpected) Exception.<br />
<br />
'''getCardsInLocation( $location, $location_arg = null, $order_by = null )'''<br />
<br />
Get all cards in specific location, as an array. Return an empty array if the location is empty.<br />
<br />
* location (string): the location where to get the cards.<br />
* location_arg (optional): if specified, return only cards with the specified "location_arg".<br />
* order_by (optional): if specified, returned cards are ordered by the given database field. Example: "card_id" or "card_type".<br />
<br />
'''countCardInLocation( $location, $location_arg=null )'''<br />
<br />
Return the number of cards in specified location.<br />
<br />
* location (string): the location where to count the cards.<br />
* location_arg (optional): if specified, count only cards with the specified "location_arg".<br />
<br />
'''countCardsInLocations()'''<br />
<br />
Return the number of cards in each location of the game.<br />
<br />
The method returns an associative array with the format "location" => "number of cards".<br />
<br />
Example:<br />
<pre><br />
array(<br />
'deck' => 12,<br />
'hand' => 21,<br />
'discard' => 54,<br />
'ontable' => 3<br />
);<br />
</pre><br />
<br />
'''countCardsByLocationArgs( $location )'''<br />
<br />
Return the number of cards in each "location_arg" for the given location.<br />
<br />
The method returns an associative array with the format "location_arg" => "number of cards".<br />
<br />
Example: count the number of cards in each player's hand:<br />
<pre><br />
countCardsByLocationArgs( 'hand' );<br />
<br />
// Result:<br />
array(<br />
122345 => 5, // player 122345 has 5 cards in hand<br />
123456 => 4 // and player 123456 has 4 cards in hand<br />
);<br />
</pre><br />
<br />
<br />
'''getPlayerHand( $player_id )'''<br />
<br />
Get all cards in given player hand.<br />
<br />
Note: This is an alias for:<br />
getCardsInLocation( "hand", $player_id )<br />
<br />
'''getCardOnTop( $location )'''<br />
<br />
Get the card on top of the given ("pile" style) location, or null if the location is empty.<br />
<br />
Note that the card pile won't be "auto-reshuffled" if there is no more card available.<br />
<br />
'''getCardsOnTop( $nbr, $location )'''<br />
<br />
Get the "$nbr" cards on top of the given ("pile" style) location.<br />
<br />
The method return an array with at most "$nbr" elements (or a void array if there is no card in this location).<br />
<br />
Note that the card pile won't be "auto-reshuffled" if there is not enough cards available.<br />
<br />
'''getExtremePosition( $bGetMax ,$location )'''<br />
<br />
(rarely used)<br />
<br />
Get the position of cards at the top of the given location / at the bottom of the given location.<br />
<br />
Of course this method works only on location in "pile" where you are using "location_arg" to specify the position of each card (example: "deck" location).<br />
<br />
If bGetMax=true, return the location of the top card of the pile.<br />
<br />
If bGetMax=false, return the location of the bottom card of the pile.<br />
<br />
'''getCardsOfType( $type, $type_arg=null )'''<br />
<br />
Get all cards of a specific type (rarely used).<br />
<br />
Return an array of cards, or an empty array if there is no cards of the specified type.<br />
<br />
* type: the type of cards<br />
* type_arg: if specified, return only cards with the specified "type_arg".<br />
<br />
'''getCardsOfTypeInLocation( $type, $type_arg=null, $location, $location_arg = null )<br />
<br />
Get all cards of a specific type in a specific location (rarely used).<br />
<br />
Return an array of cards, or an empty array if there is no cards of the specified type.<br />
<br />
* type: the type of cards<br />
* type_arg: if specified, return only cards with the specified "type_arg".<br />
* location (string): the location where to get the cards.<br />
* location_arg (optional): if specified, return only cards with the specified "location_arg".<br />
<br />
=== Shuffling ===<br />
<br />
'''shuffle( $location )'''<br />
<br />
Shuffle all cards in specific location.<br />
<br />
Shuffle only works on locations where cards are on a "pile" (ex: "deck").<br />
<br />
Please note that all "location_arg" will be reset to reflect the new order of the cards in the pile.<br />
<br />
=== Auto-reshuffle ===<br />
<br />
To enable auto-reshuffle you must do "$this->cards->autoreshuffle = true" during the setup of the component.<br />
<br />
Every time a card must be retrieved from the "deck" location, if it is empty the "discard" location will be automatically reshuffled into the "deck" location.<br />
<br />
If you need to notify players when the deck is shuffled, you can setup a callback method using this feature: $this->cards->autoreshuffle_trigger = array('obj' => $this, 'method' => 'deckAutoReshuffle');<br />
<br />
If you need to use other locations than "deck" and "discard" for auto-reshuffle feature, you can configure it this way: $this->cards->autoreshuffle_custom = array('deck' => 'discard'); (replace 'deck' and 'discard' with your custom locations).</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Deck&diff=4824
Deck
2020-06-30T12:52:21Z
<p>Cpasbanal: /* auto-reshuffle */ Capitalize header</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
"Deck" is one of the most useful component on the PHP side. With "Deck", you can manage the cards in your game on the server side.<br />
<br />
Using "deck", you will be able to use the following features without writing a single SQL database request:<br />
* Place cards in a pile, shuffle cards, draw cards one by one or many at a time.<br />
* "Auto-reshuffle" the discard pile into the deck when the deck is empty.<br />
* Move cards between different locations: hands of players, the table, etc.<br />
<br />
<br />
== Using Deck: Hearts example ==<br />
<br />
The Deck component is extensively used in the sample ''Hearts'' card game. You will find in "hearts.game.php" that the object "$this->cards" is used many times.<br />
<br />
== Deck overview ==<br />
<br />
With Deck component, you manage all cards of your game.<br />
<br />
=== The 5 properties of each card ===<br />
<br />
Using the Deck component, each card will have 5 properties:<br />
* '''id''': This is the unique ID of each card.<br />
* '''type''' and '''type_arg''': These two values define the type of your card (i.e., what sort of card is this?).<br />
* '''location''' and '''location_arg''': These two values define where the card is at now.<br />
<br />
The id, type, and type_arg properties are constants throughout the game. location and location_arg change when your cards move from one place to another in the game area.<br />
<br />
'''id''' is the unique ID of each card. Two cards cannot have the same ID. IDs are generated automatically by the Deck component when you create cards during the Setup phase of your game.<br />
<br />
'''type''' and '''type_arg''' defines the type of your card.<br />
<br />
'''type''' is a short string, and '''type_arg''' is an integer.<br />
<br />
You can use these two values as you like to make sure you will be able to identify the different cards in the game. See usage of "type" and "type_arg" below.<br />
<br />
Examples of usage of "type" and "type_arg":<br />
* In ''Hearts'', "type" represents the color (suite) of the card (1 to 4) and "type_arg" is the value of the card (1, 2, ... 10, J, Q, K).<br />
* In ''Seasons'', "type" represents the type of the card (e.g., 1 is Amulet of Air, 2 is Amulet of Fire, etc...). type_arg is not used.<br />
* In ''Takenoko'', a Deck component is used for objective cards. "type" is the kind of objective (irrigation/panda/plot) and "type_arg" is the ID of the specific objective to realize (e.g., "green bamboo x4"). Note that a second Deck component is used in ''Takenoko'' to manage the "garden plot" pile.<br />
<br />
'''location''' and '''location_arg''' define where a card is at now. '''location''' is a short string, and '''location_arg''' is an integer.<br />
<br />
You can use 'location' and 'location_arg' as you like, to move your card within the game area.<br />
<br />
There are 3 special 'location' values that Deck manages automatically. You can choose to use these locations or not, depending on your needs:<br />
* 'deck': the 'deck' location is a standard draw deck. Cards are placed face down in a stack and are drawn in sequential order during the game. 'location_arg' is used to specify where the card is located within the stack (the card with the highest location_arg value is the next to be drawn).<br />
* 'hand': the 'hand' location represents cards in a player's hand. 'location_arg' is set to the ID of each player.<br />
* 'discard': the 'discard' location is used for discard piles. Card in 'discard' may be reshuffled into the deck if needed (see "autoreshuffle").<br />
<br />
<br />
Tips: using the Deck component, you will use generic properties ("location", "type_arg",...) for specific purposes in your game. Thus, during the design step before realizing your game, take a few minutes to write down the exact meaning of each of these generic properties in the context of your game.<br />
<br />
=== Create a new Deck component ===<br />
<br />
For each Deck component in your game, you need to create a dedicated table in the SQL database. This table has a standard format. In practice, if you just want to have a Deck component named "card", you can copy/paste the following into your "dbmodel.sql" file:<br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `card` (<br />
`card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,<br />
`card_type` varchar(16) NOT NULL,<br />
`card_type_arg` int(11) NOT NULL,<br />
`card_location` varchar(16) NOT NULL,<br />
`card_location_arg` int(11) NOT NULL,<br />
PRIMARY KEY (`card_id`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;<br />
</pre><br />
<br />
Note: the database schema of this table does not have to be exactly what is listed above. You can increase the size of the fields or add more fields. For additional fields<br />
you just have to do manual queries.<br />
<br />
Once you have done this (and restarted your game), you can declare the Deck component in your PHP code in your class constructor. For ''Hearts'' for example, I added to the "Hearts()" method:<br />
<br />
<pre><br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
</pre><br />
<br />
Note that we specify "card" here: the name of our previously created table. This means you can create several "Deck" components with multiple tables:<br />
<br />
<pre><br />
$this->firstKindCards = self::getNew( "module.common.deck" );<br />
$this->firstKindCards ->init( "first_kind_card" );<br />
$this->secondKindCards = self::getNew( "module.common.deck" );<br />
$this->secondKindCards ->init( "second_kind_card" );<br />
</pre><br />
<br />
Most of the time this is not useful; a Deck component should manage all objects of the same kind (i.e., all cards in the game).<br />
<br />
Afterwards, we can initialize your "Deck" by creating all the cards of the game. Generally, this is done only once during the game, in the "setupNewGame" method.<br />
<br />
The "Deck" component provides a fast way to initialize all your cards at once: createCards. Here is how it is used for "Hearts":<br />
<pre><br />
// Create cards<br />
$cards = array();<br />
foreach( $this->colors as $color_id => $color ) // spade, heart, diamond, club<br />
{<br />
for( $value=2; $value<=14; $value++ ) // 2, 3, 4, ... K, A<br />
{<br />
$cards[] = array( 'type' => $color_id, 'type_arg' => $value, 'nbr' => 1);<br />
}<br />
}<br />
<br />
$this->cards->createCards( $cards, 'deck' );<br />
</pre><br />
<br />
As you can see, "createCards" takes a description of all cards of the game. For each type of card, you have to specify its "type", "type_arg" and the number of card to create. "createCards" create all cards and place them into the "deck" location (as specified in the second argument).<br />
<br />
Now, you are ready to use "Deck"!<br />
<br />
=== Simple examples using Deck ===<br />
<br />
(Most examples are from "Hearts" game)<br />
<br />
<pre><br />
// In "getAllDatas', we need to send to the current player all the cards he has in hand:<br />
$result['hand'] = $this->cards->getCardsInLocation( 'hand', $player_id );<br />
</pre><br />
<br />
<pre><br />
// At some time we want to check if all the cards (52) are in player's hands:<br />
if( $this->cards->countCardInLocation( 'hand' ) == 52 )<br />
// do something<br />
</pre><br />
<br />
<pre><br />
// When a player plays a card in front of him on the table:<br />
$this->cards->moveCard( $card_id, 'cardsontable', $player_id );<br />
<br />
// Note the use of the custom location 'cardsontable' here to keep track of cards on the table.<br />
</pre><br />
<br />
<br />
<pre><br />
// This is a new hand: let's gather all cards from everywhere in the deck:<br />
$this->cards->moveAllCardsInLocation( null, "deck" );<br />
<br />
// And then shuffle the deck<br />
$this->cards->shuffle( 'deck' );<br />
<br />
// And then deal 13 cards to each player<br />
// Deal 13 cards to each players<br />
// Create deck, shuffle it and give 13 initial cards<br />
$players = self::loadPlayersBasicInfos();<br />
foreach( $players as $player_id => $player )<br />
{<br />
$cards = $this->cards->pickCards( 13, 'deck', $player_id );<br />
<br />
// Notify player about his cards<br />
self::notifyPlayer( $player_id, 'newHand', '', array( <br />
'cards' => $cards<br />
) );<br />
} <br />
<br />
// Note the use of "notifyPlayer" instead of "notifyAllPlayers": new cards is a private information ;) <br />
</pre><br />
<br />
== Deck component reference ==<br />
<br />
=== Initializing Deck component ===<br />
<br />
'''init( $table_name )'''<br />
<br />
Initialize the Deck component.<br />
<br />
Argument:<br />
* table_name: name of the DB table used by this Deck component.<br />
<br />
Must be called before any other Deck method.<br />
<br />
Usually, init is called in your game constructor.<br />
<br />
Example with Hearts:<br />
<br />
<pre><br />
function Hearts( )<br />
{<br />
(...)<br />
<br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
}<br />
</pre> <br />
<br />
'''createCards( $cards, $location='deck', $location_arg=null )'''<br />
<br />
Create card items in your deck component. Usually, all card items are created once, during the setup phase of the game.<br />
<br />
"cards" describe all cards that need to be created. "cards" is an array with the following format:<br />
<br />
<pre><br />
// Create 1 card of type "1" with type_arg=99,<br />
// and 4 cards of type "2" with type_arg=12,<br />
// and 2 cards of type "3" with type_arg=33<br />
<br />
$cards = array(<br />
array( 'type' => 1, 'type_arg' => 99, 'nbr' => 1 ),<br />
array( 'type' => 2, 'type_arg' => 12, 'nbr' => 4 ),<br />
array( 'type' => 3, 'type_arg' => 33, 'nbr' => 2 )<br />
...<br />
);<br />
</pre><br />
<br />
Note: During the "createCards" process, Deck generates unique IDs for all card items.<br />
<br />
Note: createCards is optimized to create a lot of cards at once. Do not use it to create cards one by one.<br />
<br />
If "location" and "location_arg" arguments are not set, newly created cards are placed in the "deck" location. If "location" (and optionally location_arg) is specified, cards are created for this specific location.<br />
<br />
=== Card standard format ===<br />
<br />
When Deck component methods are returning one or several cards, the following format is used:<br />
<br />
<pre><br />
array(<br />
'id' => .., // the card ID<br />
'type' => .., // the card type<br />
'type_arg' => .., // the card type argument<br />
'location' => .., // the card location<br />
'location_arg' => .. // the card location argument<br />
);<br />
</pre><br />
<br />
=== Picking cards ===<br />
<br />
'''pickCard( $location, $player_id )'''<br />
<br />
Pick a card from a "pile" location (ex: "deck") and place it in the "hand" of specified player.<br />
<br />
Return the card picked or "null" if there are no more card in given location.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
'''pickCards( $nbr, $location, $player_id )'''<br />
<br />
Pick "$nbr" cards from a "pile" location (ex: "deck") and place them in the "hand" of specified player.<br />
<br />
Return an array with the cards picked (indexed by the card ID), or "null" if there are no more card in given location.<br />
<br />
Note that the number of cards picked can be less than "$nbr" in case there are not enough cards in the pile location.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below). In case there are not enough cards in the pile, all remaining cards are picked first, then the auto-reshuffle is triggered, then the other cards are picked.<br />
<br />
'''pickCardForLocation( $from_location, $to_location, $location_arg=0 )'''<br />
<br />
This method is similar to 'pickCard', except that you can pick a card for any sort of location and not only the "hand" location.<br />
<br />
* from_location is the "pile" style location from where you are picking a card.<br />
* to_location is the location where you will place the card picked.<br />
* if "location_arg" is specified, the card picked will be set with this "location_arg".<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
'''pickCardsForLocation( $nbr, $from_location, $to_location, $location_arg=0, $no_deck_reform=false )'''<br />
<br />
This method is similar to 'pickCards', except that you can pick cards for any sort of location and not only the "hand" location.<br />
<br />
* from_location is the "pile" style location from where you are picking some cards.<br />
* to_location is the location where you will place the cards picked.<br />
* if "location_arg" is specified, the cards picked will be set with this "location_arg".<br />
* if "no_deck_reform" is set to "true", the auto-reshuffle feature is disabled during this method call.<br />
<br />
This method supports auto-reshuffle (see "auto-reshuffle" below).<br />
<br />
=== Moving cards ===<br />
<br />
'''moveCard( $card_id, $location, $location_arg=0 )'''<br />
<br />
Move the specific card to given location.<br />
<br />
* card_id: ID of the card to move.<br />
* location: location where to move the card.<br />
* location_arg: if specified, location_arg where to move the card. If not specified "location_arg" will be set to 0.<br />
<br />
<br />
'''moveCards( $cards, $location, $location_arg )'''<br />
<br />
Move the specific cards to given location.<br />
<br />
* cards: an array of IDs of cards to move.<br />
* location: location where to move the cards.<br />
* location_arg: if specified, location_arg where to move the cards. If not specified "location_arg" will be set to 0.<br />
<br />
'''insertCard( $card_id, $location, $location_arg )'''<br />
<br />
Move a card to a specific "pile" location where card are ordered.<br />
<br />
If location_arg place is already taken, increment all cards after location_arg in order to insert new card at this precise location.<br />
<br />
(note: insertCardOnExtremePosition method below is more useful in most of the case)<br />
<br />
'''insertCardOnExtremePosition( $card_id, $location, $bOnTop )'''<br />
<br />
Move a card on top or at bottom of given "pile" type location.<br />
<br />
'''moveAllCardsInLocation( $from_location, $to_location, $from_location_arg=null, $to_location_arg=0 )'''<br />
<br />
Move all cards in specified "from" location to given location.<br />
<br />
* from_location: where to take the cards<br />
* to_location: where to put the cards<br />
* from_location (optional): if specified, only cards with given "location_arg" are moved.<br />
* to_location (optional): if specified, cards moved "location_arg" is set to given value. Otherwise location_arg is set to zero.<br />
<br />
Note: if you want to keep "location_arg" untouched, you should use "moveAllCardsInLocationKeepOrder" below.<br />
<br />
'''moveAllCardsInLocationKeepOrder( $from_location, $to_location )'''<br />
<br />
Move all cards in specified "from" location to given "to" location. This method does not modify the "location_arg" of cards.<br />
<br />
'''playCard( $card_id )'''<br />
<br />
Move specified card at the top of the "discard" location.<br />
<br />
Note: this is an alias for: insertCardOnExtremePosition( $card_id, "discard", true )<br />
<br />
=== Get cards informations ===<br />
<br />
'''getCard( $card_id )'''<br />
<br />
Get specific card information.<br />
<br />
Return null if this card is not found.<br />
<br />
'''getCards( $cards_array )'''<br />
<br />
Get specific cards information.<br />
<br />
cards_array is an array of cards ID.<br />
<br />
If some cards are not found or if some cards IDs are specified multiple times, the method throws an (unexpected) Exception.<br />
<br />
'''getCardsInLocation( $location, $location_arg = null, $order_by = null )'''<br />
<br />
Get all cards in specific location, as an array. Return an empty array if the location is empty.<br />
<br />
* location (string): the location where to get the cards.<br />
* location_arg (optional): if specified, return only cards with the specified "location_arg".<br />
* order_by (optional): if specified, returned cards are ordered by the given database field. Example: "card_id" or "card_type".<br />
<br />
'''countCardInLocation( $location, $location_arg=null )'''<br />
<br />
Return the number of cards in specified location.<br />
<br />
* location (string): the location where to count the cards.<br />
* location_arg (optional): if specified, count only cards with the specified "location_arg".<br />
<br />
'''countCardsInLocations()'''<br />
<br />
Return the number of cards in each location of the game.<br />
<br />
The method returns an associative array with the format "location" => "number of cards".<br />
<br />
Example:<br />
<pre><br />
array(<br />
'deck' => 12,<br />
'hand' => 21,<br />
'discard' => 54,<br />
'ontable' => 3<br />
);<br />
</pre><br />
<br />
'''countCardsByLocationArgs( $location )'''<br />
<br />
Return the number of cards in each "location_arg" for the given location.<br />
<br />
The method returns an associative array with the format "location_arg" => "number of cards".<br />
<br />
Example: count the number of cards in each player's hand:<br />
<pre><br />
countCardsByLocationArgs( 'hand' );<br />
<br />
// Result:<br />
array(<br />
122345 => 5, // player 122345 has 5 cards in hand<br />
123456 => 4 // and player 123456 has 4 cards in hand<br />
);<br />
</pre><br />
<br />
<br />
'''getPlayerHand( $player_id )'''<br />
<br />
Get all cards in given player hand.<br />
<br />
Note: This is an alias for:<br />
getCardsInLocation( "hand", $player_id )<br />
<br />
'''getCardOnTop( $location )'''<br />
<br />
Get the card on top of the given ("pile" style) location, or null if the location is empty.<br />
<br />
Note that the card pile won't be "auto-reshuffled" if there is no more card available.<br />
<br />
'''getCardsOnTop( $nbr, $location )'''<br />
<br />
Get the "$nbr" cards on top of the given ("pile" style) location.<br />
<br />
The method return an array with at most "$nbr" elements (or a void array if there is no card in this location).<br />
<br />
Note that the card pile won't be "auto-reshuffled" if there is not enough cards available.<br />
<br />
'''getExtremePosition( $bGetMax ,$location )'''<br />
<br />
(rarely used)<br />
<br />
Get the position of cards at the top of the given location / at the bottom of the given location.<br />
<br />
Of course this method works only on location in "pile" where you are using "location_arg" to specify the position of each card (example: "deck" location).<br />
<br />
If bGetMax=true, return the location of the top card of the pile.<br />
<br />
If bGetMax=false, return the location of the bottom card of the pile.<br />
<br />
'''getCardsOfType( $type, $type_arg=null )'''<br />
<br />
Get all cards of a specific type (rarely used).<br />
<br />
Return an array of cards, or an empty array if there is no cards of the specified type.<br />
<br />
* type: the type of cards<br />
* type_arg: if specified, return only cards with the specified "type_arg".<br />
<br />
'''getCardsOfTypeInLocation( $type, $type_arg=null, $location, $location_arg = null )<br />
<br />
Get all cards of a specific type in a specific location (rarely used).<br />
<br />
Return an array of cards, or an empty array if there is no cards of the specified type.<br />
<br />
* type: the type of cards<br />
* type_arg: if specified, return only cards with the specified "type_arg".<br />
* location (string): the location where to get the cards.<br />
* location_arg (optional): if specified, return only cards with the specified "location_arg".<br />
<br />
=== Shuffling ===<br />
<br />
'''shuffle( $location )'''<br />
<br />
Shuffle all cards in specific location.<br />
<br />
Shuffle only works on locations where cards are on a "pile" (ex: "deck").<br />
<br />
Please note that all "location_arg" will be reset to reflect the new order of the cards in the pile.<br />
<br />
=== Auto-reshuffle ===<br />
<br />
To enable auto-reshuffle you must do "$this->cards->autoreshuffle = true" during the setup of the component.<br />
<br />
Every time a card must be retrieved from the "deck" location, if it is empty the "discard" location will be automatically reshuffled into the "deck" location.<br />
<br />
If you need to notify players when the deck is shuffled, you can setup a callback method using this feature: $this->cards->autoreshuffle_trigger = array('obj' => $this, 'method' => 'deckAutoReshuffle');<br />
<br />
If you need to use other locations than "deck" and "discard" for auto-reshuffle feature, you can configure it this way: $this->cards->autoreshuffle_custom = array('deck' => 'discard'); (replace 'deck' and 'discard' with your custom locations).</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Gamehelpbarbu&diff=4783
Gamehelpbarbu
2020-06-24T15:58:41Z
<p>Cpasbanal: /* Option "Trumps" */</p>
<hr />
<div>== Goal == <br />
<br />
Your goal is to loose '''the least point''' you can.<br />
<br />
== Playing the game == <br />
<br />
The game is made of multiple hands (number of hands depends on the options you play):<br />
# avoid making '''tricks''' (every trick counts -1 point)<br />
# avoid taking '''hearts''' in tricks (every heart card counts -1 point)<br />
# avoid taking '''queens''' in tricks (each queeen counts -3 points for a 52-cards deck and -2 points when playing with a 32-cards deck)<br />
# avoid taking the '''King of Hearts''' (the ''Barbu'') in your tricks (Barbu counts -12 point for a 52-cards deck and -8 when playing with a 32-cards deck)<br />
<br />
== Options availables ==<br />
<br />
=== Option "geronimo" ===<br />
<br />
When playing with this option, each player at his/her turn will choose the hand he/she wants to play. Yet, during the game, all the players will have to play all the hands.<br />
<br />
=== Option "Double" ===<br />
<br />
After declarer picks a game but before the first trick is led, each of the other players may wager double against one or more of the other players. This operates like a side bet on the relative game score between the two players, who are said to have "business" with each other. The rules for doubling are:<br />
<br />
* Each player gets one opportunity to double<br />
* A player can choose to double only some players (declarer or non-declarer) or pass<br />
* At a positive game, non-dealers may only double the dealer (who may redouble)<br />
* Each non-declarer must double against declarer '''at least twice''' during the game for each declarer.<br />
* Any player receiving a wager of double may redouble; "maximum" implies redoubling all received wagers as well as doubling the remaining players<br />
* Declarer can not double, but may redouble as above. A "maximum" from the dealer therefore redoubles received wagers only<br />
<br />
After the hand is played out and scores are tallied, modifications are made for doubles as follows:<br />
<br />
* Between each pair of players who doubled, the difference in their scores is calculated. Player with the higher score for that round is awarded the amount of the difference and player with the lower score had the same amount subtracted from his or her score.<br />
* Redoubled wagers are figured the same way, with the difference in scores doubled before awards and penalties are tallied.<br />
<br />
=== Option "Salade" ===<br />
<br />
Play an extra hand, the '''Salade''' when everything counts, so avoid making tricks, taking hearts, taking queens or the King of Hearts. All the point counts so Queen of Hearts is worth -4 points (with 52-cards deck), Barbu is worth -13 points (still 52-cards deck)...<br />
<br />
=== Option "Anti-Salade" ===<br />
<br />
Similar to the "Salade", the '''Anti-salade''' counts all the same points (tricks, hearts, queens, Barbu) with '''positive score'''.<br />
<br />
=== Option "Last two" ===<br />
<br />
In this hand, try to avoid taking the '''last two tricks''' (counts -4 points for the trick before last and -8 points for the last for a 52-cards deck and -3 points and -5 points for a 32-cards deck)<br />
<br />
=== Option "Trumps" ===<br />
<br />
Only when playing on geronimo mode, you can choose to play Trumps. The declareer choose the trump color and the goal is to take as much tricks as possible (tricks count +3 when playing 52-cards deck and +2 when playing 32-cards deck).<br />
<br />
Some rules apply when you play with trumps:<br />
<br />
* If a trump is led, each player must, if able, play a higher trump than those played before. Otherwise players follow suit, or discard if they are void in trumps.<br />
* If a plain suit is led, players follow suit if possible.<br />
* If they cannot and no trump has been played, they must play a trump card if possible or otherwise play a card of one of the other suits.<br />
* If a non-trump has been led and trumped, a player must, if possible, play a higher trump than those previously played. Otherwise the player may slough off any card.<br />
<br />
=== Option "Domino" (Klondike) ===<br />
<br />
You can play an extra hand: the '''Domino'''.<br />
<br />
During this hand, players have to play cards in a suite, starting from 2 (or 7 when playing 32-cards deck) to the Ace, for each of the 4 types.<br />
<br />
When a player plays an Ace, he/she can play again. If a player can't play, he/she passes. But you can't pass if you can play any card (even if you wanted to).<br />
<br />
Domino also counts points positively.<br />
<br />
If you play with a 52-cards deck, the first player with no card left wins 25 points, the second 15 points, the third 5 points. The other players score nothing.<br />
<br />
If you play with a 32-cards deck, the first player with no card left wins 20 points, the second 10 points. The other players score nothing.<br />
<br />
== 3 or 5 players ==<br />
<br />
The game is recommended to play at 4 players but you can play at 3 or 5 players. Some cards are removed from the game so that everyone has the same quantity of cards in hand. Here are the cards removed:<br />
<br />
{| class="wikitable"<br />
! '''Number of players'''<br />
! 32-cards deck<br />
! 52-cards deck<br />
|-<br />
|'''3 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds<br />
|-<br />
|'''4 players'''<br />
|nothing<br />
|nothing<br />
|-<br />
|'''5 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds and 2 clubs<br />
|}</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Gamehelpbarbu&diff=4780
Gamehelpbarbu
2020-06-24T15:51:16Z
<p>Cpasbanal: /* Playing the game */</p>
<hr />
<div>== Goal == <br />
<br />
Your goal is to loose '''the least point''' you can.<br />
<br />
== Playing the game == <br />
<br />
The game is made of multiple hands (number of hands depends on the options you play):<br />
# avoid making '''tricks''' (every trick counts -1 point)<br />
# avoid taking '''hearts''' in tricks (every heart card counts -1 point)<br />
# avoid taking '''queens''' in tricks (each queeen counts -3 points for a 52-cards deck and -2 points when playing with a 32-cards deck)<br />
# avoid taking the '''King of Hearts''' (the ''Barbu'') in your tricks (Barbu counts -12 point for a 52-cards deck and -8 when playing with a 32-cards deck)<br />
<br />
== Options availables ==<br />
<br />
=== Option "geronimo" ===<br />
<br />
When playing with this option, each player at his/her turn will choose the hand he/she wants to play. Yet, during the game, all the players will have to play all the hands.<br />
<br />
=== Option "Double" ===<br />
<br />
After declarer picks a game but before the first trick is led, each of the other players may wager double against one or more of the other players. This operates like a side bet on the relative game score between the two players, who are said to have "business" with each other. The rules for doubling are:<br />
<br />
* Each player gets one opportunity to double<br />
* A player can choose to double only some players (declarer or non-declarer) or pass<br />
* At a positive game, non-dealers may only double the dealer (who may redouble)<br />
* Each non-declarer must double against declarer '''at least twice''' during the game for each declarer.<br />
* Any player receiving a wager of double may redouble; "maximum" implies redoubling all received wagers as well as doubling the remaining players<br />
* Declarer can not double, but may redouble as above. A "maximum" from the dealer therefore redoubles received wagers only<br />
<br />
After the hand is played out and scores are tallied, modifications are made for doubles as follows:<br />
<br />
* Between each pair of players who doubled, the difference in their scores is calculated. Player with the higher score for that round is awarded the amount of the difference and player with the lower score had the same amount subtracted from his or her score.<br />
* Redoubled wagers are figured the same way, with the difference in scores doubled before awards and penalties are tallied.<br />
<br />
=== Option "Salade" ===<br />
<br />
Play an extra hand, the '''Salade''' when everything counts, so avoid making tricks, taking hearts, taking queens or the King of Hearts. All the point counts so Queen of Hearts is worth -4 points (with 52-cards deck), Barbu is worth -13 points (still 52-cards deck)...<br />
<br />
=== Option "Anti-Salade" ===<br />
<br />
Similar to the "Salade", the '''Anti-salade''' counts all the same points (tricks, hearts, queens, Barbu) with '''positive score'''.<br />
<br />
=== Option "Last two" ===<br />
<br />
In this hand, try to avoid taking the '''last two tricks''' (counts -4 points for the trick before last and -8 points for the last for a 52-cards deck and -3 points and -5 points for a 32-cards deck)<br />
<br />
=== Option "Trumps" ===<br />
<br />
Only when playing on geronimo mode, you can choose to play Trumps. The taker choose the trump color and the goal is to take as much tricks as possible (tricks count +3 when playing 52-cards deck and +2 when playing 32-cards deck).<br />
<br />
=== Option "Domino" (Klondike) ===<br />
<br />
You can play an extra hand: the '''Domino'''.<br />
<br />
During this hand, players have to play cards in a suite, starting from 2 (or 7 when playing 32-cards deck) to the Ace, for each of the 4 types.<br />
<br />
When a player plays an Ace, he/she can play again. If a player can't play, he/she passes. But you can't pass if you can play any card (even if you wanted to).<br />
<br />
Domino also counts points positively.<br />
<br />
If you play with a 52-cards deck, the first player with no card left wins 25 points, the second 15 points, the third 5 points. The other players score nothing.<br />
<br />
If you play with a 32-cards deck, the first player with no card left wins 20 points, the second 10 points. The other players score nothing.<br />
<br />
== 3 or 5 players ==<br />
<br />
The game is recommended to play at 4 players but you can play at 3 or 5 players. Some cards are removed from the game so that everyone has the same quantity of cards in hand. Here are the cards removed:<br />
<br />
{| class="wikitable"<br />
! '''Number of players'''<br />
! 32-cards deck<br />
! 52-cards deck<br />
|-<br />
|'''3 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds<br />
|-<br />
|'''4 players'''<br />
|nothing<br />
|nothing<br />
|-<br />
|'''5 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds and 2 clubs<br />
|}</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Gamehelpbarbu&diff=4779
Gamehelpbarbu
2020-06-24T15:51:05Z
<p>Cpasbanal: Add doubling and reformat options</p>
<hr />
<div>== Goal == <br />
<br />
Your goal is to loose '''the least point''' you can.<br />
<br />
== Playing the game == <br />
<br />
The game is made of multiple hands (number of hands depends on the options you play):<br />
# avoid making '''tricks''' (every trick counts -1 point)<br />
# avoid taking '''hearts''' in tricks (every heart card counts -1 point)<br />
# avoid taking '''queens''' in tricks (each queeen counts -3 points for a 52-cards deck and -2 points when playing with a 32-cards deck)<br />
# avoid taking the '''King of Hearts''' (the ''Barbu'') in your tricks (Barbu counts -12 point for a 52-cards deck and -8 when playing with a 32-cards deck)<br />
<br />
<br />
== Options availables ==<br />
<br />
=== Option "geronimo" ===<br />
<br />
When playing with this option, each player at his/her turn will choose the hand he/she wants to play. Yet, during the game, all the players will have to play all the hands.<br />
<br />
=== Option "Double" ===<br />
<br />
After declarer picks a game but before the first trick is led, each of the other players may wager double against one or more of the other players. This operates like a side bet on the relative game score between the two players, who are said to have "business" with each other. The rules for doubling are:<br />
<br />
* Each player gets one opportunity to double<br />
* A player can choose to double only some players (declarer or non-declarer) or pass<br />
* At a positive game, non-dealers may only double the dealer (who may redouble)<br />
* Each non-declarer must double against declarer '''at least twice''' during the game for each declarer.<br />
* Any player receiving a wager of double may redouble; "maximum" implies redoubling all received wagers as well as doubling the remaining players<br />
* Declarer can not double, but may redouble as above. A "maximum" from the dealer therefore redoubles received wagers only<br />
<br />
After the hand is played out and scores are tallied, modifications are made for doubles as follows:<br />
<br />
* Between each pair of players who doubled, the difference in their scores is calculated. Player with the higher score for that round is awarded the amount of the difference and player with the lower score had the same amount subtracted from his or her score.<br />
* Redoubled wagers are figured the same way, with the difference in scores doubled before awards and penalties are tallied.<br />
<br />
=== Option "Salade" ===<br />
<br />
Play an extra hand, the '''Salade''' when everything counts, so avoid making tricks, taking hearts, taking queens or the King of Hearts. All the point counts so Queen of Hearts is worth -4 points (with 52-cards deck), Barbu is worth -13 points (still 52-cards deck)...<br />
<br />
=== Option "Anti-Salade" ===<br />
<br />
Similar to the "Salade", the '''Anti-salade''' counts all the same points (tricks, hearts, queens, Barbu) with '''positive score'''.<br />
<br />
=== Option "Last two" ===<br />
<br />
In this hand, try to avoid taking the '''last two tricks''' (counts -4 points for the trick before last and -8 points for the last for a 52-cards deck and -3 points and -5 points for a 32-cards deck)<br />
<br />
=== Option "Trumps" ===<br />
<br />
Only when playing on geronimo mode, you can choose to play Trumps. The taker choose the trump color and the goal is to take as much tricks as possible (tricks count +3 when playing 52-cards deck and +2 when playing 32-cards deck).<br />
<br />
=== Option "Domino" (Klondike) ===<br />
<br />
You can play an extra hand: the '''Domino'''.<br />
<br />
During this hand, players have to play cards in a suite, starting from 2 (or 7 when playing 32-cards deck) to the Ace, for each of the 4 types.<br />
<br />
When a player plays an Ace, he/she can play again. If a player can't play, he/she passes. But you can't pass if you can play any card (even if you wanted to).<br />
<br />
Domino also counts points positively.<br />
<br />
If you play with a 52-cards deck, the first player with no card left wins 25 points, the second 15 points, the third 5 points. The other players score nothing.<br />
<br />
If you play with a 32-cards deck, the first player with no card left wins 20 points, the second 10 points. The other players score nothing.<br />
<br />
== 3 or 5 players ==<br />
<br />
The game is recommended to play at 4 players but you can play at 3 or 5 players. Some cards are removed from the game so that everyone has the same quantity of cards in hand. Here are the cards removed:<br />
<br />
{| class="wikitable"<br />
! '''Number of players'''<br />
! 32-cards deck<br />
! 52-cards deck<br />
|-<br />
|'''3 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds<br />
|-<br />
|'''4 players'''<br />
|nothing<br />
|nothing<br />
|-<br />
|'''5 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds and 2 clubs<br />
|}</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Game_interface_logic:_yourgamename.js&diff=4742
Game interface logic: yourgamename.js
2020-06-16T20:18:59Z
<p>Cpasbanal: /* Players input */ typo hears -> hearts</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
This is the main file for your game interface. Here you will define:<br />
<br />
* Which actions on the page will generate calls to the server.<br />
* What happens when you get a notification for a change from the server and how it will show in the browser. <br />
<br />
== File structure ==<br />
<br />
The details of how the file is structured are described below with comments on the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* '''constructor''': here you can define global variables for your whole interface.<br />
* '''setup''': this method is called when the page is refreshed, and sets up the game interface.<br />
* '''onEnteringState''': this method is called when entering a new game state. You can use it to customize the view for each game state.<br />
* '''onLeavingState''': this method is called when leaving a game state.<br />
* '''onUpdateActionButtons''': called when entering a new state, in order to add action buttons to the status bar.<br />
* ''(utility methods)'': this is where you can define your utility methods.<br />
* ''(player's actions)'': this is where you can write your handlers for player actions on the interface (example: click on an item).<br />
* '''setupNotifications''': this method associates notifications with notification handlers. For each game notification, you can trigger a javascript method to handle it and update the game interface.<br />
* ''(notification handlers)'': this is where you define the notifications handlers associated with notifications in '''setupNotifications''', above.<br />
<br />
== General tips ==<br />
<br />
; this.player_id<br />
: ID of the player on whose browser the code is running.<br />
<br />
; this.isSpectator<br />
: Flag set to true if the user at the table is a spectator (not a player).<br />
: Note: This is a variable, not a function.<br />
: Note: If you want to hide an element from spectators, you should use [[Game_interface_stylesheet:_yourgamename.css#spectatorMode|CSS 'spectatorMode' class]].<br />
<br />
; this.gamedatas<br />
: Contains the initial set of data to init the game, created at game start or by game refresh (F5).<br />
: You can update it as needed to keep an up-to-date reference of the game on the client side if you need it. (Most of the time this is unnecessary).<br />
<br />
; this.isCurrentPlayerActive()<br />
: Returns true if the player on whose browser the code is running is currently active (it's his turn to play).<br />
<br />
; this.getActivePlayerId()<br />
: Return the ID of the active player, or null if we are not in an "activeplayer" type state.<br />
<br />
; this.getActivePlayers()<br />
: Return an array with the IDs of players who are currently active (or an empty array if there are none).<br />
<br />
; this.bRealtime<br />
: Return true if the game is in realtime. Note that having a distinct behavior in realtime and turn-based should be exceptional.<br />
<br />
== Dojo framework ==<br />
<br />
BGA uses the [http://dojotoolkit.org/ Dojo Javascript framework].<br />
<br />
The Dojo framework allows us to do complex things more easily. The BGA framework uses Dojo extensively.<br />
<br />
To implement a game, you only need to use a few parts of the Dojo framework. All the Dojo methods you need are described on this page.<br />
<br />
== ShrinkSafe minimization ==<br />
<br />
For performance reasons, when deploying a game the js code is minimized using ShrinkSafe (based on ECMASCRIPT version 3). Some advanced syntax may not be compatible with this process. In particular:<br />
* You should not use reserved keywords from the javascript language as variables.<br />
* You should not declare default argument values in function declarations. The following syntax is invalid for ShrinkSafe: '''function myFunc(requiredArg, optionalArg = 'defaultValue') {}'''<br />
<br />
'''Tip:''' a developer encountering some problems with this has successfully used [http://plugins.netbeans.org/plugin/58580/jshint JSHint] on NetBeans to evaluate code to make it compatible for ECMAScript 3. With the plugin installed, set the below options in '''.jshintrc''' file, and then open '''Action Items''' window (in NetBeans):<pre>{ "maxerr": 999, "esversion": 3 }</pre><br />
<br />
'''Tip:''' some online tools also allow to convert between different versions of javascript, such as https://www.typescriptlang.org/play<br />
<br />
== Accessing and manipulating the DOM ==<br />
<br />
'''$('some_html_element_id')'''<br />
<br />
The $() function is used to get an HTML element using its "id" attribute.<br />
<br />
Example 1: modify the content of a "span" element:<br />
<br />
<pre><br />
In your HTML code:<br />
<span id="a_value_in_the_game_interface">1234</span><br />
<br />
In your Javascript code:<br />
$('a_value_in_the_game_interface').innerHTML = "9999";<br />
</pre><br />
<br />
Note: $() is the standard method to access some HTML element with the BGA Framework. You should not use the '''getElementById''' function.<br />
<br />
<br />
'''dojo.style'''<br />
<br />
With dojo.style you can modify the CSS property of any HTML element in your interface.<br />
<br />
Examples:<br />
<br />
<pre><br />
// Make an element disappear<br />
dojo.style( 'my_element', 'display', 'none' );<br />
<br />
// Give an element a 2px border<br />
dojo.style( 'my_element', 'borderWidth', '2px' );<br />
<br />
// Change the background position of an element<br />
// (very practical when you are using CSS sprites to transform an element to another)<br />
dojo.style( 'my_element', 'backgroundPosition', '-20px -50px' );<br />
</pre><br />
<br />
Note: you must always use dojo.style to modify the CSS properties of HTML elements.<br />
<br />
NoteÂČ: if you have to modify several CSS properties of an element, or if you have a complex CSS transformation to do, you should consider using dojo.addClass/dojo.removeClass (see below).<br />
<br />
'''dojo CSS classes manipulation'''<br />
<br />
In many situations, many small CSS property updates can be replaced by a CSS class change (i.e., you add a CSS class to your element instead of applying all modifications manually).<br />
<br />
Advantages are:<br />
* All your CSS stuff remains in your CSS file.<br />
* You can add/remove a list of CSS modifications with a simple function and without error.<br />
* You can test whether you applied the CSS to an element with the '''dojo.hasClass''' method.<br />
<br />
Example from ''Reversi'':<br />
<br />
<pre><br />
// We add "possibleMove" to an element<br />
dojo.addClass( 'square_'+x+'_'+y, 'possibleMove' );<br />
<br />
// In our CSS file, the class is defined as:<br />
.possibleMove {<br />
background-color: white;<br />
opacity: 0.2;<br />
filter:alpha(opacity=20); /* For IE8 and earlier */ <br />
cursor: pointer; <br />
}<br />
<br />
// So we've applied 4 CSS property changes in one line of code.<br />
<br />
// ... and when we need to check if a square is a possible move on the client side:<br />
if( dojo.hasClass( 'square_'+x+'_'+y, 'possibleMove' ) )<br />
{ ... }<br />
<br />
// ... and if we want to remove all possible moves in one line of code (see "dojo.query" method):<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' );<br />
</pre><br />
<br />
Conclusion: We encourage you to use '''dojo.addClass''', '''dojo.removeClass''' and '''dojo.hasClass''' to make your life easier :)<br />
<br />
'''dojo.query'''<br />
<br />
With dojo.query, you can query a bunch of HTML elements with a single function, with a "CSS selector" style.<br />
<br />
Example:<br />
<pre><br />
// All elements with class "possibleMove":<br />
var elements = dojo.query( '.possibleMove' );<br />
<br />
// Count number of tokens (i.e., elements of class "token") on the board (i.e., the element with id "board"):<br />
dojo.query( '#board .token' ).length;<br />
</pre><br />
<br />
But what is really cool with dojo.query is that you can combine it with almost all methods above.<br />
<br />
Examples:<br />
<pre><br />
// Trigger a method when the mouse enter in any element with class "meeple":<br />
dojo.query( '.meeple' ).connect( 'onmouseenter', this, 'myMethodToTrigger' );<br />
<br />
// Hide all meeples who are on the board<br />
dojo.query( '#board .meeple' ).style( 'display', 'none' );<br />
</pre><br />
<br />
'''dojo.place'''<br />
<br />
dojo.place is the best function to insert HTML code somewhere in your game interface without breaking something. It is much better to use than the '''innerHTML=''' method if you must insert HTML tags and not only values.<br />
<br />
<pre><br />
// Insert your HTML code as a child of a container element<br />
dojo.place( "<your html code>", "your_container_element_id" );<br />
<br />
// Replace the container element with your new html<br />
dojo.place( "<your html code>", "your_container_element_id", "replace" );<br />
<br />
</pre><br />
<br />
The third parameter of dojo.place can take various interesting value.<br />
<br />
values possibles :<br />
<br />
"replace" : (see description above).<br />
<br />
"first" : Places the node as a child of the reference node. The node is placed as the first child.<br />
<br />
"last" : Places the node as a child of the reference node. The node is placed as the last child.<br />
<br />
"before" : places the node right before the reference node.<br />
<br />
"last (value by default) " : places the node right after the reference node.<br />
<br />
"only" : replaces all children of the reference node with the node.<br />
<br />
positif integer : This parameter can be a positif integer. In this case, the node will be placed as a child of the reference node with this number (counting from 0). If the number is more than number of children, the node will be appended to the reference node making it the last child. <br />
<br />
See also full doc on dojo.place : [https://dojotoolkit.org/reference-guide/1.7/dojo/place.html]<br />
<br />
Usually, when you want to insert some piece of HTML in your game interface, you should use "[[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]".<br />
<br />
'''addStyleToClass: function( cssClassName, cssProperty, propertyValue )'''<br />
<br />
Same as dojo.style(), but for all the nodes set with the specified cssClassName<br />
<br />
=== Animations ===<br />
<br />
'''Dojo Animations'''<br />
<br />
BGA animations is based on Dojo Animation ([http://dojotoolkit.org/documentation/tutorials/1.8/animation/ see tutorial here]).<br />
<br />
However, most of the time, you can just use methods below, which are built on top of Dojo Animation.<br />
<br />
Note: one interesting method from Dojo that could be useful from time to time is "Dojo.Animation". It allows you to make any CSS property "slide" from one value to another.<br />
<br />
'''this.slideToObject( mobile_obj, target_obj, duration, delay )'''<br />
<br />
You can use slideToObject to "slide" an element to a target position.<br />
<br />
Sliding element on the game area is the recommended and the most used way to animate your game interface. Using slides allow players to figure out what is happening on the game, as if they were playing with the real boardgame.<br />
<br />
The parameters are:<br />
* mobile_obj: the ID of the object to move. This object must be "relative" or "absolute" positioned.<br />
* target_obj: the ID of the target object. This object must be "relative" or "absolute" positioned. Note that it is not mandatory that mobile_obj and target_obj have the same size. If their size are different, the system slides the center of mobile_obj to the center of target_obj.<br />
* duration: (optional) defines the duration in millisecond of the slide. The default is 500 milliseconds.<br />
* delay: (optional). If you defines a delay, the slide will start only after this delay. This is particularly useful when you want to slide several object from the same position to the same position: you can give a 0ms delay to the first object, a 100ms delay to the second one, a 200ms delay to the third one, ... this way they won't be superposed during the slide.<br />
<br />
BE CAREFUL: The method returns an dojo.fx animation, so you can combine it with other animation if you want to. It means that you have to call the "play()" method, otherwise the animation WON'T START.<br />
<br />
Example:<br />
<pre><br />
this.slideToObject( "some_token", "some_place_on_board" ).play();<br />
</pre><br />
<br />
<br />
'''this.slideToObjectPos( mobile_obj, target_obj, target_x, target_y, duration, delay )'''<br />
<br />
This method does exactly the same as "slideToObject", except than you can specify some (x,y) coordinates. This way, "mobile_obj" will slide to the specified x,y position relatively to "target_obj".<br />
<br />
Example: slide a token to some place on the board, 10 pixels to the bottom:<br />
<pre><br />
this.slideToObjectPos( "some_token", "some_place_on_board", 0, 10 ).play();<br />
</pre><br />
<br />
'''this.slideTemporaryObject( mobile_obj_html, mobile_obj_parent, from, to, duration, delay )'''<br />
<br />
This method is useful when you want to slide a temporary HTML object from one place to another. As this object does not exists before the animation and won't remain after, it could be complex to create this object (with dojo.place), to place it at its origin (with placeOnObject) to slide it (with slideToObject) and to make it disappear at the end.<br />
<br />
slideTemporaryObject does all of this for you:<br />
* mobile_obj_html is a piece of HTML code that represent the object to slide.<br />
* mobile_obj_parent is the ID of an HTML element of your interface that will be the parent of this temporary HTML object.<br />
* from is the ID of the origin of the slide.<br />
* to is the ID of the target of the slide.<br />
* duration/delay works exactly like in "slideToObject"<br />
<br />
Example:<br />
<pre><br />
this.slideTemporaryObject( '<div class="token_icon"></div>', 'tokens', 'my_origin_div', 'my_target_div' );<br />
</pre><br />
<br />
'''this.slideToObjectAndDestroy: function( node, to, time, delay )'''<br />
<br />
This method is a handy shortcut to slide an existing HTML object to some place then destroy it upon arrival. It can be used for example to move a victory token or a card from the board to the player panel to show that the player earns it, then destroy it when we don't need to keep it visible on the player panel.<br />
<br />
It works the same as this.slideToObject and takes the same arguments. <br />
<br />
Example:<br />
<pre><br />
this.slideToObjectAndDestroy( "some_token", "some_place_on_board", 1000, 0 );<br />
</pre><br />
<br />
'''this.fadeOutAndDestroy( node, duration, delay )'''<br />
<br />
This function fade out the target HTML node, then destroy it.<br />
<br />
Example:<br />
<pre><br />
this.fadeOutAndDestroy( "a_card_that_must_disappear" );<br />
</pre><br />
<br />
CAREFUL: the HTML node still exists until during few milliseconds, until the fadeOut has been completed.<br />
<br />
'''Rotating elements'''<br />
<br />
You can check here [http://jimfulton.info/demos/dojo-animated-rotate.html an example of use] of Dojo to make an element rotate.<br />
<br />
This example combines "Dojo.Animation" method and a CSS3 property that allow you to rotate the element.<br />
<br />
IMPORTANT: to asses browser compatibility, you must select the CSS property to use just like in the example (see sourcecode below):<br />
<br />
<pre><br />
var transform;<br />
dojo.forEach(<br />
['transform', 'WebkitTransform', 'msTransform',<br />
'MozTransform', 'OTransform'],<br />
function (name) {<br />
if (typeof dojo.body().style[name] != 'undefined') {<br />
transform = name;<br />
}<br />
});<br />
// ... and then use "transform" as the name of your CSS property for rotation<br />
</pre><br />
<br />
'''Animation Callbacks'''<br />
<br />
If you wish to run some code only after an animation has completed you can do this by linking a callback method.<br />
<br />
<pre><br />
var animation_id = this.slideToObject( mobile_obj, target_obj, duration, delay );<br />
dojo.connect(animation_id, 'onEnd', dojo.hitch(this, 'callback_function', parameters));<br />
animation_id.play();<br />
<br />
âŠ<br />
<br />
callback_function: function(params) {<br />
// this will be called after the animation ends<br />
},<br />
</pre><br />
<br />
If you wish to call a second animation after the first (rather than general code) then you can use a dojo animation chain (see tutorial referenced above).<br />
<br />
=== Moving elements ===<br />
<br />
'''this.placeOnObject( mobile_obj, target_obj )'''<br />
<br />
placeOnObject works exactly like "slideToObject", except that the effect is immediate.<br />
<br />
This is not really an animation, but placeOnObject is frequently used before starting an animation.<br />
<br />
Example:<br />
<pre><br />
// (We just created an object "my_new_token")<br />
<br />
// Place the new token on current player board<br />
this.placeOnObject( "my_new_token", "overall_player_board_"+this.player_id );<br />
<br />
// Then slide it to its position on the board<br />
this.slideToObject( "my_new_token", "a_place_on_board" ).play();<br />
</pre><br />
<br />
'''this.placeOnObjectPos( mobile_obj, target_obj, target_x, target_y )'''<br />
<br />
This method works exactly like placeOnObject, except than you can specify some (x,y) coordinates. This way, "mobile_obj" will be placed to the specified x,y position relatively to "target_obj".<br />
<br />
'''this.attachToNewParent( mobile_obj, target_obj )'''<br />
<br />
With this method, you change the HTML parent of "mobile_obj" element. "target_obj" is the new parent of this element. The beauty of <br />
attachToNewParent is that the mobile_obj element DOES NOT MOVE during this process.<br />
<br />
Note: what happens is that the method calculate a relative position of mobile_obj to make sure it does not move after the HTML parent changes.<br />
<br />
Why using this method?<br />
<br />
Changing the HTML parent of an element can be useful for the following reasons:<br />
* When the HTML parent moves, all its child are moving with them. If some game elements is no more linked with a parent HTML object, you may want to attach it to another place.<br />
* The z_order (vertical order of display) depends on the position in the DOM, so you may need to change the parent of some game elements when they are moving in your game area.<br />
<br />
CAREFUL: this function destroys original object and places a clone onto a new parent, this will break all references to this HTML element (ex: dojo.connect).<br />
<br />
== Players input ==<br />
<br />
'''dojo.connect'''<br />
<br />
Used to associate a player event with one of your notification method.<br />
<br />
Example: associate a click on an element ("my_element") with one of our method ("onClickOnMyElement"):<br />
<pre><br />
dojo.connect( $('my_element'), 'onclick', this, 'onClickOnMyElement' );<br />
</pre><br />
<br />
Note: this is the only possible correct way to associate a player input event to your code, and you must not use anything else.<br />
<br />
'''this.checkAction( "my_action_name" )'''<br />
<br />
Usage: checkAction: function( action, nomessage )<br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
Restricted arguments names (please don't use them):<br />
<br />
* "action"<br />
* "module"<br />
* "class"<br />
return true if action is authorized (ie: the action is listed as a "possibleaction" in current game state).<br />
<br />
return false and display an error message if not (display no message if nomessage parameter is true). The displayed error message could be either "This move is not allowed at this moment" or "An action is already in progress".<br />
<br />
Example:<br />
<pre><br />
function onClickOnGameElement( evt )<br />
{<br />
if( this.checkAction( "my_action" ) )<br />
{<br />
// Do the action<br />
}<br />
}<br />
</pre><br />
<br />
'''this.checkPossibleActions( "my_action_name" )'''<br />
<br />
Usage: checkPossibleActions: function( action, nomessage )<br />
<br />
* this is independent of the player being active, so can be used instead of this.checkAction(). This is particularly useful for multiplayer states when the player is not active in a 'player may like to change their mind' scenario. <br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
* interface locking (a player can't do any action if an action is already in progress)<br />
<br />
Restricted arguments names (please don't use them):<br />
* "action"<br />
* "module"<br />
* "class"<br />
<br />
'''this.ajaxcall( url, parameters, obj_callback, callback, callback_error )'''<br />
<br />
This method must be used to send a player input to the game server.<br />
<br />
* url: the url of the action to perform. For a game, it must be: "/<mygame>/<mygame>/myAction.html"<br />
* parameters: an array of parameter to send to the game server. Note that "lock:true" must always be specified in this list of parameter in order the interface can be locked during the server call.<br />
* obj_callback: must be set to "this".<br />
* callback: a function to trigger when the server returns and everything went fine.<br />
* callback_error: (optional and rarely used) a function to trigger when the server returns an error.<br />
<br />
Usage:<br />
<pre><br />
this.ajaxcall( '/mygame/mygame/myaction.html', { lock: true, <br />
arg1: myarg1, <br />
arg2: myarg2, <br />
...<br />
}, this, function( result ) {<br />
// Do some stuff after a successful call<br />
} );<br />
</pre><br />
<br />
'''this.confirmationDialog()'''<br />
<br />
Display a confirmation dialog with a yes/no choice.<br />
<br />
We advice you to NOT use this function unless the player action is really critical and could ruins the game, because it slows down the game and upset players.<br />
<br />
Usage: this.confirmationDialog( "Question to displayed", callback_function_if_click_on_yes );<br />
<br />
Example:<br />
<pre><br />
this.confirmationDialog( _('Are you sure to use this bonus (points penalty at the end of the game) ?'),<br />
dojo.hitch( this, function() {<br />
this.ajaxcall( '/seasons/seasons/useBonus.html',<br />
{ id:bonus_id, lock:true }, this, function( result ) {} );<br />
} ) ); <br />
</pre><br />
<br />
<br />
; addEventToClass: function( cssClassName, eventName, functionName )<br />
: Same as dojo.connect(), but for all the nodes set with the specified cssClassName<br />
<br />
'''this.addActionButton( id, label, method, (opt)destination, (opt)blinking, (opt)color )'''<br />
<br />
You can use this method to add an action button in the main action status bar.<br />
<br />
Arguments:<br />
* id: an element ID that should be unique in your HTML DOM document.<br />
* label: the text of the button. Should be translatable (use _() function).<br />
* method: the name of your method that must be triggered when the player clicks on this button.<br />
* destination (optional): deprecated, do not use this. Use '''null''' as value if you need to specify other arguments.<br />
* blinking (optional): if set to '''true''', the button is going blink to catch player's attention. Please don't abuse of blinking button.<br />
* color: could be '''blue''' (default), '''red''' or '''gray'''.<br />
<br />
You should only use this method in your "onUpdateActionButtons" method. Usually, you use it like this (from Hearts example):<br />
<br />
<pre><br />
onUpdateActionButtons: function( stateName, args ) {<br />
<br />
if (this.isCurrentPlayerActive()) { <br />
switch( stateName ) {<br />
case 'giveCards':<br />
this.addActionButton( 'giveCards_button', _('Give selected cards'), 'onGiveCards' ); <br />
break;<br />
}<br />
}<br />
}, <br />
</pre><br />
<br />
In the example above, we are adding a "Give selected cards" button in the case we are on game state "giveCards". When player clicks on this button, it triggers our "onGiveCards" method.<br />
<br />
Example using blinking red button:<br />
<pre><br />
this.addActionButton( 'commit_button', _('Confirm'), 'onConfirm', null, true, 'red'); <br />
</pre><br />
<br />
Note: at least in studio example above will make button huge, because it sets it display of blinking things to '''block''', <br />
if you don't like it you have to change css display value<br />
of the button to inline-block (the id of the button is the first argument, i.e 'commit_button' in example above)<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Notifications ==<br />
<br />
When something happens on the server side, your game interface Javascript logic received a notification.<br />
<br />
Here's how you can handle these notifications on the client side.<br />
<br />
=== Subscribe to notifications ===<br />
<br />
Your Javascript "setupNotifications" method is the place where you can subscribe to notifications from your PHP code.<br />
<br />
Here's how you associate one of your Javascript method to a notification "playDisc" (from Reversi example):<br />
<br />
<pre><br />
// In setupNotifications method:<br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
</pre><br />
<br />
Note: the "playDisc" corresponds to the name of the notification you define it in your PHP code, in your "notifyAllPlayers" or "notifyPlayer" method.<br />
<br />
Then, you have to define your "notif_playDisc" method:<br />
<br />
<pre><br />
notif_playDisc: function( notif )<br />
{<br />
// Remove current possible moves (makes the board more clear)<br />
dojo.query( '.possibleMove' ).removeClass( 'possibleMove' ); <br />
<br />
this.addDiscOnBoard( notif.args.x, notif.args.y, notif.args.player_id );<br />
},<br />
</pre><br />
<br />
In a notification handler like our "notif_playDisc" method, you can access to all notifications arguments with "notif.args".<br />
<br />
Example:<br />
<pre><br />
// If you did this on PHP side:<br />
self::notifyAllPlayers( "myNotification", '', array( "myArgument" => 3 ) );<br />
<br />
// On Javascript side, you can access the "myArgument" like this:<br />
notif_myNotification: function( notif )<br />
{<br />
alert( "myArgument = " + notif.args.myArgument );<br />
}<br />
</pre><br />
<br />
<br />
=== Synchronous notifications ===<br />
<br />
When several notifications are received by your game interface, these notifications are processed immediately, one after the other, in the same exact order they have been generated in your PHP game logic.<br />
<br />
However, sometimes, you need to give some time to the players to figure out what happened on the game before jumping to the next notification. Indeed, in many games, they are a lot of automatic actions, and the computer is going to resolve all these actions very fast if you don't tell it not to do so.<br />
<br />
As an example, for Reversi, when someone is playing a disc, we want to wait 500 milliseconds before doing anything else in order the opponent player can figure out what move has been played.<br />
<br />
Here's how we do this, right after our subscription:<br />
<pre><br />
dojo.subscribe( 'playDisc', this, "notif_playDisc" );<br />
this.notifqueue.setSynchronous( 'playDisc', 500 ); // Wait 500 milliseconds after executing the playDisc handler<br />
</pre><br />
<br />
<br />
=== Pre-defined notification types ===<br />
<br />
'''tableWindow''' - This defines notification to display [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialogs]], see below.<br />
<br />
'''message''' - This defines notification that shows on players log and have no other effect<br />
<br />
// You can call this on php side without doing anything on client side<br />
self::notifyAllPlayers( 'message', 'hello', array( ) );<br />
<br />
== Tooltips ==<br />
<br />
'''this.addTooltip( nodeId, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to the DOM node.<br />
<br />
Specify 'helpString' to display some information about "what is this game element?".<br />
Specify 'actionString' to display some information about "what happens when I click on this element?".<br />
<br />
You must specify both helpString and actionString. Most of the time, you should use only one and specify a void string ("") for the other one.<br />
<br />
Usually, _() must be used for the text to be marked for translation.<br />
<br />
"Delay" is an optional parameter. Usually, it is primarily used to specify a zero delay for some game element when the tooltip gives really important information for the game - but remember: no essential information must be placed in tooltips as they won't be displayed in some browsers (see [[BGA_Studio_Guidelines|Guidelines]]).<br />
<br />
Example:<br />
<pre><br />
this.addTooltip( 'cardcount', _('Number of cards in hand'), '' );<br />
</pre><br />
<br />
'''this.addTooltipHtml( nodeId, html, delay )'''<br />
<br />
Add an HTML tooltip to the DOM node (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
'''this.addTooltipToClass( cssClass, _( helpString ), _( actionString ), delay )'''<br />
<br />
Add a simple text tooltip to all the DOM nodes set with this cssClass. <br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips.<br />
<br />
'''this.addTooltipHtmlToClass( cssClass, html, delay )'''<br />
<br />
Add an HTML tooltip to to all the DOM nodes set with this cssClass (for more elaborate content such as presenting a bigger version of a card).<br />
<br />
IMPORTANT: all concerned nodes must have IDs to get tooltips<br />
<br />
'''this.removeTooltip( nodeId )'''<br />
<br />
Remove a tooltip from the DOM node.<br />
<br />
== Dialogs, warning messages, confirmation dialogs, ... ==<br />
<br />
=== Warning messages ===<br />
<br />
Sometimes, there is something important that is happening on the game and you have to make sure all players get the message. Most of the time, the evolution of the game situation or the game log is enough, but sometimes you need something more visible.<br />
<br />
Ex: someone fulfill one of the end of the game condition, so this is the last turn.<br />
<br />
'''this.showMessage( msg, type )'''<br />
<br />
showMessage shows a message in a big rectangular area on the top of the screen of current player.<br />
<br />
* "msg" is the string to display. It should be translated.<br />
* "type" can be set to "info" or "error". If set to "info", the message will be an informative message on a white background. If set to "error", the message will be an error message on a red background.<br />
<br />
Important: the normal way to inform players about the progression of the game is the game log. "showMessage" is intrusive and should not be used often.<br />
<br />
=== Confirmation dialog ===<br />
<br />
'''confirmationDialog( message, yesHandler, noHandler )'''<br />
<br />
<br />
When an important action with a lot of consequences is triggered by the player, you may want to propose a confirmation dialog.<br />
<br />
CAREFUL: the general guidelines of BGA is to AVOID the use of confirmation dialog. Confirmation dialogs slow down the game and bother players. The players knows that they have to pay attention about each move when they are playing online.<br />
<br />
The situation where you should use a confirmation dialog are the following:<br />
* It must not happen very often during a game.<br />
* It must be linked to an action that can really "kill a game" if the player do not pay attention.<br />
* It must be something that can be done by mistake (ex: a link on the action status bar).<br />
<br />
How to display a confirmation dialog:<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to bake the pie?'), dojo.hitch( this, function() {<br />
this.bakeThePie();<br />
} ) ); <br />
return; // nothing should be called or done after calling this, all action must be done in the handler <br />
</pre><br />
<br />
=== Multiple choice dialog ===<br />
You can use this dialog to give user a choice with small amount of options:<br />
<pre><br />
var keys = [1,5,10];<br />
this.multipleChoiceDialog(<br />
_('How many bugs to fix?"), keys, <br />
dojo.hitch(this, function(choice) {<br />
var bugchoice = keys[choice];<br />
console.log('dialog callback with '+bugchoice);<br />
this.ajaxcall( '/mygame/mygame/fixBugs.html', { bugs: bugchoice}, this, function( result ) {} ); }));<br />
</pre><br />
<br />
=== Dialogs ===<br />
<br />
As a general rule, you shouldn't use dialogs windows.<br />
<br />
BGA guidelines specify that all game elements should be displayed on the main screen. Players can eventually scroll down to see game elements they don't need to see anytime, and you may eventually create anchors to move between game area section. Of course dialogs windows are very practical, but the thing is: all players know how to scroll down, and not all players know how to show up your dialog window. In addition, when the dialog shows up, players can't access the other game components.<br />
<br />
Sometimes although, you need to display a dialog window. Here is how you do this:<br />
<br />
<br />
<br />
// Create the new dialog over the play zone. You should store the handler in a member variable to access it later<br />
this.myDlg = new ebg.popindialog();<br />
this.myDlg.create( 'myDialogUniqueId' );<br />
this.myDlg.setTitle( _("my dialog title to translate") );<br />
this.myDlg.setMaxWidth( 500 ); // Optional<br />
<br />
// Create the HTML of my dialog. <br />
// The best practice here is to use [[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl#Javascript_templates|Javascript templates]]<br />
var html = this.format_block( 'jstpl_myDialogTemplate', { <br />
arg1: myArg1,<br />
arg2: myArg2,<br />
...<br />
} ); <br />
<br />
// Show the dialog<br />
this.myDlg.setContent( html ); // Must be set before calling show() so that the size of the content is defined before positioning the dialog<br />
this.myDlg.show();<br />
<br />
// Now that the dialog has been displayed, you can connect your method to some dialog elements<br />
// Example, if you have an "OK" button in the HTML of your dialog:<br />
dojo.connect( $('my_ok_button'), 'onclick', this, function(evt){<br />
evt.preventDefault();<br />
this.myDlg.destroy();<br />
} );<br />
<br />
If necessary, you can remove the default top right corner 'close' icon, or replace the function called when it is clicked:<br />
// Removes the default close icon<br />
this.myDlg.hideCloseIcon();<br />
<br />
// Replace the function call when it's clicked<br />
this.myDlg.replaceQuitCallback( function() { ... } );<br />
<br />
=== Scoring dialogs ===<br />
<br />
Sometimes at the end of a round you want to display a big table that details the points wins in each section of the game.<br />
<br />
Example: in Hearts game, we display at the end of each round the number of "heart" cards collected by each player, the player who collected the Queen of Spades, and the total number of points loose by each player.<br />
<br />
Scoring dialogs are managed entirely on '''PHP side''', but they are described here as their effects are visible only on client side.<br />
<br />
Displaying a scoring dialog is quite simple and is using a special notification type: "tableWindow":<br />
<pre><br />
// on PHP side:<br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table<br />
) ); <br />
</pre><br />
<br />
The "table" argument is a 2 dimensional PHP array that describe the table you want to display, line by line and column by column.<br />
<br />
Example: display an 3x3 array of strings<br />
<pre><br />
$table = array(<br />
array( "one", "two", "three" ), // This is my first line<br />
array( "four", "five", "six" ), // This is my second line<br />
array( "seven", "height", "nine" ) // This is my third line<br />
);<br />
</pre><br />
<br />
As you can see above, in each "cell" of your array you can display a simple string value. But you can also display a complex value with a template and associated arguments like this:<br />
<pre><br />
$table = array(<br />
array( "one", "two", array( "str" => clienttranslate("a string with an ${argument}"), "args" => array( 'argument' => 'argument_value' ) ) ),<br />
array( "four", "five", "six" ), <br />
array( "seven", "height", "nine" )<br />
);<br />
</pre><br />
<br />
This is especially useful when you want to display player names with colors. Example from "Hearts":<br />
<pre><br />
$firstRow = array( '' );<br />
foreach( $players as $player_id => $player )<br />
{<br />
$firstRow[] = array( 'str' => '${player_name}',<br />
'args' => array( 'player_name' => $player['player_name'] ),<br />
'type' => 'header'<br />
);<br />
}<br />
$table[] = $firstRow;<br />
</pre><br />
<br />
You can also use three extra attributes in the parameter array for the notification:<br />
<br />
<pre><br />
$this->notifyAllPlayers( "tableWindow", '', array(<br />
"id" => 'finalScoring',<br />
"title" => clienttranslate("Title of the scoring dialog"),<br />
"table" => $table,<br />
"header" => array('str' => clienttranslate('Table header with parameter ${number}'),<br />
'args' => array( 'number' => 3 ),<br />
),<br />
"footer" => '<div>Some footer</div>',<br />
"closing" => clienttranslate( "Closing button label" )<br />
) ); <br />
</pre><br />
<br />
*'''header''': the content for this parameter will display before the table (also, the html will be parsed and player names will be colored according to the current game colors). <br />
*'''footer''': the content for this parameter will display after the table (no parsing for coloring the player names)<br />
*'''closing''': if this parameter is used, a button will be displayed with this label at the bottom of the popup and will allow players to close it (more easily than by clicking the top right 'cross' icon).<br />
<br />
=== Scoring animated display ===<br />
<br />
Sometimes (Terra Mystica final scoring for example), you may want to display a score value over an element to make the scoring easier to follow for the players.<br />
You can do it with:<br />
<br />
<pre><br />
this.displayScoring( anchor_id, color, score, duration );<br />
</pre><br />
<br />
'''anchor_id''': ID of the element to place the animated score onto (without the '#') <br />
<br />
'''color''': hexadecimal RGB representation of the color (should be the color of the scoring player), but without a leading '#'. For instance, 'ff0000' for red.<br />
<br />
'''score''': numeric score to display, prefixed by a '+'<br />
<br />
'''duration''': animation duration in milliseconds<br />
<br />
=== Speech bubble ===<br />
<br />
For better interactivity in some games (Love Letter for example), you may use comic book style speech bubbles to express the players voices.<br />
This is done with:<br />
<br />
<pre><br />
this.showBubble( anchor_id, text, delay, duration, custom_class )<br />
</pre><br />
<br />
delay in milliseconds is optional (default 0)<br />
<br />
duration in milliseconds is optional (default 3000)<br />
<br />
custom_class is optional, if you need to override the default bubble style<br />
<br />
'''Warning''': if your bubble could overlap other active elements of the interface (buttons in particular), as it stays in place even after disappearing, you should use a custom class to give it the style "pointer-events: none;" in order to intercept click events.<br />
<br />
== Update players score ==<br />
<br />
Increase a player score (with a positive or negative number):<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].incValue( score_delta );<br />
</pre><br />
<br />
Set a player score to a specific value:<br />
<br />
<pre><br />
this.scoreCtrl[ player_id ].setValue( new_score );<br />
</pre><br />
<br />
== Players panels ==<br />
<br />
=== Adding stuff to player's panel ===<br />
<br />
At first, create a new "JS template" string in your template (tpl) file:<br />
<br />
(from Gomoku example)<br />
<pre><br />
var jstpl_player_board = '\<div class="cp_board">\<br />
<div id="stoneicon_p${id}" class="gmk_stoneicon gmk_stoneicon_${color}"></div><span id="stonecount_p${id}">0</span>\<br />
</div>';<br />
</pre><br />
<br />
Then, you add this piece of code in your JS file to add this template to each player panel:<br />
<br />
<pre><br />
// Setting up player boards<br />
for( var player_id in gamedatas.players )<br />
{<br />
var player = gamedatas.players[player_id];<br />
<br />
// Setting up players boards if needed<br />
var player_board_div = $('player_board_'+player_id);<br />
dojo.place( this.format_block('jstpl_player_board', player ), player_board_div );<br />
}<br />
</pre><br />
<br />
(Note: the code above is of course from your "setup" function in your Javascript).<br />
<br />
Very often, you have to distinguish current player and others players. In this case, you just have to create another JS template (ex: jstpl_otherplayer_board) and use it when "player_id" is different than "this.player_id".<br />
<br />
=== Player's panel disabling/enabling ===<br />
<br />
'''this.disablePlayerPanel( player_id )'''<br />
<br />
Disable given player panel (the panel background become gray).<br />
<br />
Usually, this is used to signal that this played passes, or will be inactive during a while.<br />
<br />
Note that the only effect of this is visual. There are no consequences on the behaviour of the panel itself.<br />
<br />
'''this.enablePlayerPanel( player_id )'''<br />
<br />
Enable a player panel that has been disabled before.<br />
<br />
'''this.enableAllPlayerPanels()'''<br />
<br />
Enable all player panels that has been disabled before.<br />
<br />
== Image loading ==<br />
<br />
See also [[Game_art:_img_directory]].<br />
<br />
'''Be careful''': by default, ALL images of your img directory are loaded on a player's browser when he loads the game. For this reason, don't let in your img directory images that are not useful, otherwise it's going to slowdown the game load.<br />
<br />
'''dontPreloadImage( image_file_name )'''<br />
<br />
Using dontPreloadImage, you tell the interface to not preload a specific image in your img directory.<br />
<br />
Example of use:<br />
<pre><br />
this.dontPreloadImage( 'cards.png' );<br />
</pre><br />
<br />
This is particularly useful if for example you have 2 different themes for a game. To accelerate the loading of the game, you can specify to not preload images corresponding to the other theme.<br />
<br />
Another example of use: in "Gosu" game with Kamakor extension, you play with 5 sets of cards among 10 available. Cards images are organized by sets, and we only preload the images corresponding to the 5 current sets with '''ensureSpecificGameImageLoading( image_file_names_array )'''.<br />
<pre><br />
// By default, do not preload anything<br />
this.dontPreloadImage( 'cards.png' );<br />
this.dontPreloadImage( 'clan1.png' );<br />
this.dontPreloadImage( 'clan2.png' );<br />
this.dontPreloadImage( 'clan3.png' );<br />
this.dontPreloadImage( 'clan4.png' );<br />
this.dontPreloadImage( 'clan5.png' );<br />
this.dontPreloadImage( 'clan6.png' );<br />
this.dontPreloadImage( 'clan7.png' );<br />
this.dontPreloadImage( 'clan8.png' );<br />
this.dontPreloadImage( 'clan9.png' );<br />
this.dontPreloadImage( 'clan10.png' );<br />
var to_preload = [];<br />
for( i in this.gamedatas.clans )<br />
{<br />
var clan_id = this.gamedatas.clans[i];<br />
to_preload.push( 'clan'+clan_id+'.png' );<br />
}<br />
if( to_preload.length == 5 )<br />
{<br />
this.ensureSpecificGameImageLoading( to_preload );<br />
}<br />
</pre><br />
<br />
'''Note:''' You don't need to specify to not preload game box images (game_box.png, game_box75.png...) since they are not preloaded by default.<br />
<br />
== Other useful stuff ==<br />
<br />
<br />
'''dojo.hitch'''<br />
<br />
With dojo.hitch, you can create a callback function that will run with your game object context whatever happen.<br />
<br />
Typical example: display a BGA confirmation dialog with a callback function created with dojo.hitch:<br />
<br />
<pre><br />
this.confirmationDialog( _('Are you sure you want to make this?'), dojo.hitch( this, function() {<br />
this.ajaxcall( '/mygame/mygame/makeThis.html', { lock:true }, this, function( result ) {} );<br />
} ) ); <br />
</pre><br />
<br />
In the example above, using dojo.hitch, we ensure that the "this" object will be set when the callback is called.<br />
<br />
<br />
; updateCounters(counters)<br />
: Useful for updating game counters in the player panel (such as resources). <br />
: 'counters' arg is an associative array [counter_name_value => [ 'counter_name' => counter_name_value, 'counter_value' => counter_value_value], ... ]<br />
: All counters must be referenced in this.gamedatas.counters and will be updated.<br />
: DOM objects referenced by 'counter_name' will have their innerHTML updated with 'counter_value'.<br />
<br />
<br />
'''updatePageTitle()'''<br />
This function allows to update the current page title and turn description according to the game state. If the current game state description this.gamedatas.gamestate.descriptionmyturn is modified before calling the function, it allows to update the turn description without changing state.<br />
<br />
Example from Terra Mystica:<br />
<pre><br />
onClickFavorTile: function( evt )<br />
{<br />
[...]<br />
<br />
if ( ... ) {<br />
<br />
this.gamedatas.gamestate.descriptionmyturn = _('Special action: ') + _('Advance 1 space on a Cult track');<br />
this.updatePageTitle();<br />
this.removeActionButtons();<br />
<br />
this.addActionButton( 'action_confirm1', _("Fire"),<br />
function() { ... }<br />
);<br />
this.addActionButton( 'action_confirm2', _("Water"),<br />
function() { ... }<br />
);<br />
this.addActionButton( 'action_confirm3', _("Earth"),<br />
function() { ... }<br />
);<br />
this.addActionButton( 'action_confirm4', _("Air"),<br />
function() { ... }<br />
);<br />
<br />
this.addActionButton( 'action_cancel', _("Cancel"), function() { ... }, false, false, 'gray'<br />
);<br />
<br />
return;<br />
}<br />
<br />
[...]<br />
<br />
}<br />
</pre><br />
<br />
== BGA GUI components ==<br />
<br />
BGA framework provides some useful ready-to-use components for the game interface:<br />
<br />
[[Studio#BGA_Studio_game_components_reference]]<br />
<br />
Note that each time you are using an additional component, you must declare it at the top of your Javascript file in the list of modules used.<br />
<br />
Example if you are using "ebg.stock":<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <=== we are using ebg.stock module<br />
],<br />
</pre></div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Post-release_phase&diff=4738
Post-release phase
2020-06-16T08:51:59Z
<p>Cpasbanal: /* Changes that breaks the games in progress */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
Your game is now on BGA: congrats!<br />
<br />
But what happened when there are some bugs to fix or when you want to optimize something?<br />
<br />
Don't be afraid: you're still allowed to modify your game. You just have to pay attention to the points below.<br />
<br />
== Bugs reporting ==<br />
<br />
Bugs are reported in the [http://forum.boardgamearena.com/viewforum.php?f=4 BGA bugs forum].<br />
<br />
During days after your game has been published and from time to time, please have a look at it to check if everything is fine.<br />
<br />
== How to submit changes? ==<br />
<br />
There is 3 (short) steps to make your changes visible on BGA (from your Control Panel):<br />
<br />
* Commit your changes.<br />
* Build a new version (don't forget to do a successful commit BEFORE your build).<br />
* Deploy your new version in production.<br />
<br />
<br />
== What can be modified after release? ==<br />
<br />
Everything can be modified. BUT, some items requires a special attention, and you must inform us in some cases:<br />
<br />
===Changes that breaks the games in progress===<br />
<br />
Some changes will break the games in progress at the moment the release/the hotfix will be performed. Each time you make a change, you should ask you the question "it is safe to make this change in a game in progress", and if the answer is "no" you have to inform us.<br />
<br />
Example of changes that break the games in progress:<br />
* Changes in the database schema of the game (dbmodel.sql).<br />
* New global variable or game option accessed during the game (if it's only used during setup, it should be safe).<br />
* Delete or change ID of existing game option (especially for Arena tables and turn by turn tables)<br />
* Change ID of existing game states (adding new game states is fine).<br />
<br />
Of course, as a rule of thumb, you should avoid to introduce changes that break a game in progress. Sometimes however, you do not have any other choice. In this case:<br />
* Try to group all your updates in one version, thus we won't have to block your game several times.<br />
* Tell us explicitly that you introduce some update that can break games in progress so we can block the game during a short time.<br />
<br />
Note: in the near future, we will introduce the possibility for you to block/unblock a game directly from your Control Panel in order to perform all the process by yourself.<br />
<br />
=== Updating Statistics ===<br />
<br />
You should be careful when updating a statistics:<br />
<br />
* If you want to add a new statistic, please be aware that for ongoing games this statistic won't always be correct since it has not been counted from the start of that game.<br />
* If you want to update a statistic, please update it and do not remove/create another one. Otherwise, the statistic won't keep the same ID and players will lose all the historical statistics data.<br />
* If your game is published on BGA, please don't remove any statistics (historical data will be lost).<br />
<br />
=== Updating the database schema ===<br />
<br />
If you want to update the database schema of the game (dbmodel.sql) and your game is already open to everyone on BGA (from BETA onwards), you should inform us before releasing the new version (see "[[#Changes that breaks the games in progress|changes that breaks the games in progress]]").<br />
<br />
Any modification in dbmodel.sql should also appear in your nameofyourgame.game.php, in a function<br />
<br />
function upgradeTableDb( $from_version ){<br />
<br />
if( $from_version <= YYMMDDHHMM ){ // where your CURRENT version in production has number YYMMDD-HHMM<br />
<br />
// You DB schema update request.<br />
// Note: all tables names should be prefixed by "DBPREFIX_" to be compatible with the applyDbUpgradeToAllDB method you should use below<br />
$sql = "CREATE TABLE DBPREFIX_xxxxxxx ....";<br />
<br />
// The method below is applying your DB schema update request to all tables, including the BGA framework utility tables like "zz_replayXXXX" or "zz_savepointXXXX".<br />
// You should really use this request, in conjunction with "DBPREFIX_" in your $sql, so ALL tables are updated. All utility tables MUST have the same schema than the main table, otherwise the game may be blocked.<br />
self::applyDbUpgradeToAllDB( $sql );<br />
<br />
}}<br />
<br />
Note1: of course you need to change your dbmodel.sql accordingly, so that new games get your updated scheme.<br />
<br />
Note2: this is always risky to modify the DB scheme, so:<br />
_ if your game is already open to everyone on BGA (from BETA onwards), it may be worth contacting us before, so we can stop the realtime games during the update (this way, only turn based games are concerned by the DB live upgrade).<br />
_ if you can avoid it... try to avoid it :)<br />
<br />
=== Updating string to be translated ===<br />
<br />
When you update a string that has been marked to be translatable, please keep in mind that all current translations done by the BGA community will be lost.<br />
<br />
Consequently, when you are about to modify a string to be translated (after release), please ask you the following questions:<br />
* Is it just an English misspelling? In this case, it is better to fix the English translation of the string than the original string to be translated.<br />
* Has the meaning of the string changed? If yes, you HAVE to change the original string in order to invalidate all translations that has been done already.<br />
* Is there a similar string already used elsewhere in my game? In this case, you'd better use it again to enjoy immediately all translations already available.<br />
<br />
===Tell the community about your changes :)===<br />
<br />
The player's community is always happy to know that someone is taking care of their preferred game :)<br />
<br />
If your are the developer of game XXXX, you are also the administrator of player's group "XXXX's players", and you can publish some news in this group newsfeed.<br />
<br />
When you fix a bug or add something, do not hesitate to tell the players about this in this group: you'll get the "thank you" you deserved :)<br />
<br />
See for example Werewolves group:<br />
https://boardgamearena.com/group?id=2465913<br />
<br />
<br />
===Major changes===<br />
<br />
If you do some major changes to your game like:<br />
* Introducing a new expansion.<br />
* Major code rewriting/refactoring.<br />
<br />
... please tell us. In this case, we can:<br />
* Make your game back from "gold" to "public beta", to incite player to report bugs.<br />
* And eventually, publish a news about it :)</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Gamehelpbarbu&diff=4669
Gamehelpbarbu
2020-06-08T20:02:29Z
<p>Cpasbanal: </p>
<hr />
<div>== Goal == <br />
<br />
Your goal is to loose '''the least point''' you can.<br />
<br />
== Playing the game == <br />
<br />
The game is made of multiple hands (number of hands depends on the options you play):<br />
# avoid making '''tricks''' (every trick counts -1 point)<br />
# avoid taking '''hearts''' in tricks (every heart card counts -1 point)<br />
# avoid taking '''queens''' in tricks (each queeen counts -3 points for a 52-cards deck and -2 points when playing with a 32-cards deck)<br />
# avoir taking the '''King of Hearts''' (the ''Barbu'') in your tricks (Barbu counts -12 point for a 52-cards deck and -8 when playing with a 32-cards deck)<br />
# avoir taking the '''last two tricks''' (counts -4 points for the trick before last and -8 points for the last for a 52-cards deck and -3 points and -5 points for a 32-cards deck)<br />
<br />
An extra hand (optional), the '''Salade''' when everything counts, so avoid making tricks, taking hearts, taking queens or the King of Hearts. All the point counts so Queen of Hearts is worth -4 points (with 52-cards deck), Barbu is worth -13 points (still 52-cards deck)...<br />
<br />
Another extra hand (optional), the '''Anti-salade''' counts all the same points (tricks, hearts, queens, Barbu) with '''positive score'''.<br />
<br />
== Option "Trumps" ==<br />
<br />
Only when playing on geronimo mode, you can choose to play Trumps. The taker choose the trump color and the goal is to take as much tricks as possible (tricks count +3 when playing 52-cards deck and +2 when playing 32-cards deck).<br />
<br />
== Option "Domino" (Klondike) ==<br />
<br />
You can play an extra hand: the '''Domino'''.<br />
<br />
During this hand, players have to play cards in a suite, starting from 2 (or 7 when playing 32-cards deck) to the Ace, for each of the 4 types.<br />
<br />
When a player plays an Ace, he/she can play again. If a player can't play, he/she passes. But you can't pass if you can play any card (even if you wanted to).<br />
<br />
Domino also counts points positively.<br />
<br />
If you play with a 52-cards deck, the first player with no card left wins 25 points, the second 15 points, the third 5 points. The other players score nothing.<br />
<br />
If you play with a 32-cards deck, the first player with no card left wins 20 points, the second 10 points. The other players score nothing.<br />
<br />
== 3 or 5 players ==<br />
<br />
The game is recommended to play at 4 players but you can play at 3 or 5 players. Some cards are removed from the game so that everyone has the same quantity of cards in hand. Here are the cards removed:<br />
<br />
{| class="wikitable"<br />
! '''Number of players'''<br />
! 32-cards deck<br />
! 52-cards deck<br />
|-<br />
|'''3 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds<br />
|-<br />
|'''4 players'''<br />
|nothing<br />
|nothing<br />
|-<br />
|'''5 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds and 2 clubs<br />
|}</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Tutorial_hearts&diff=4623
Tutorial hearts
2020-06-05T09:42:13Z
<p>Cpasbanal: /* Update game infos and box graphics */ Add info for public domain publisher</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Introduction ==<br />
<br />
Using this tutorial, you can build a complete working game on the BGA environment: Hearts.<br />
<br />
Before you read this tutorial, you must:<br />
* Read the overall presentations of the BGA Framework ([[Studio|see here]]).<br />
* Know the rules for Hearts<br />
* Some-what know the languages used on BGA: PHP, SQL, HTML, CSS, Javascript<br />
* Set up your development environment [http://en.doc.boardgamearena.com/First_steps_with_BGA_Studio First Steps with BGA Studio]<br />
* As part of setup you have to have access to your ftp home folder in studio, which would have 'hearts' game source code. We will be using some resources of this game in this tutorial, so copy it over to local disk if you have not done so.<br />
<br />
If you are stuck or have question about this tutorial, post on [https://forum.boardgamearena.com/viewforum.php?f=12 BGA Developers forum]<br />
<br />
== Create your first game ==<br />
<br />
If you have not already, you have to create a project in BGA Studio. For this tutorial you can create a project heartsYOURNAME where<br />
YOURNAME is your developer login name. You can also re-use the project you have created for the "First Steps" tutorial above.<br />
With the initial skeleton of code provided, you can already start a game from the BGA Studio. <br />
<br />
1. Find and start the game in turn-based mode with 4 players. Make sure it works. <br />
<br />
2. Modify the text in heartsYOURNAME_heartsYOURNAME.tpl, reload the page in the browser and make sure your ftp sync works as expected.<br />
Note: if you have not setup auto-sync do it now, manually copying files is a no-starter.<br />
<br />
3. Express stop from settings menu (the gear icon).<br />
<br />
<i>Note: please do '''not''' use the hearts project code as a base. This tutorial assumes you started with a TEMPLATE project with no prior modifications. Using the hearts project as a base will be very confusing and you won't be able to follow all the steps.<br />
</i><br />
<br />
== Hook version control system ==<br />
<br />
For a real game, or even for this tutorial, we recommend committing the code to version control right from the start. You are going to find yourself in a situation where the game doesn't even start anymore and no way of debugging it, unless you have a way to revert. That is where version control becomes very handy. If you are not familiar with version control (e.g. [https://git-scm.com/docs/gittutorial git]) then at least back up your files after each major change. Start now.<br />
<br />
Code for this tutorial available is on github: https://github.com/elaskavaia/bga-heartsla<br />
<br />
Different revisions represent different steps along the process, starting from original template to a complete game.<br />
<br />
== Update game infos and box graphics ==<br />
<br />
Even it does not nothing yet, always start by making sure the game looks decent in the game selector, meaning it has nice box graphics and its information is correct. For that we need to edit [[Game_meta-information: gameinfos.inc.php|gameinfos.inc.php]].<br />
<br />
For a real game, you would go to [http://boardgamegeek.com BoardGameGeek], find the game, and use the information from BGG to fill in the gameinfos.<br />
<br />
So let's do that. Find "hearts" on BoardGameGeek. (Hint: Original release 1850 :))<br />
<br />
You can fill in the year of publishing and bgg id, put ''Public Domain'' under publisher (for a real game, leave an empty string so it won't be displayed), and a publisher id of 171 for public domain. And as designer and author you can just put your own name just for fun. Set number of players to 4.<br />
<br />
// Players configuration that can be played (ex: 2 to 4 players)<br />
'players' => array( 4 ), <br />
<br />
<br />
The next step is to replace '''game_box.png''' with nicer images. For this tutorial, just copy all the files from the img/ folder of the hearts/ template into the img/ directory of your project. Replace publisher.png with a nicer image: for example https://github.com/elaskavaia/bga-sharedcode/blob/master/img/publisher.png.<br />
<br />
Details about images can be found here: [[Game art: img directory]].<br />
<br />
Now an important step: you have to LOAD these files into the Studio website through the control panel. So go to Control Panel -> Manage Games -> heartsYOURNAME<br />
and press Reload for 'Reload game informations' and 'Reload game box image'<br />
<br />
Now try to start the game again. If you somehow introduced a syntax error in the gameinfos file it may not work (the game won't start).<br />
Always use the "Express Start" button to start the game. You should see a standard state prompt from the template. You should see 4 players on the right: testdude0 .. testdude3.<br />
To switch between them press the red arrow button near their names, it will open another tab. This way you don't need to login and logout from multiple accounts!<br />
<br />
<br />
<i>Note: if you had run the game before with less than 4 players there is a bug that will prevent you from running it with 4 only (if you did not run it before or run it with 4 players as instructed stop reading this note), to workaround revert back to original players array (i.e. 1,2,3,4), reload game options, then create a table with 4 players, exit that game table, then change gameoptions to 4 only as above, reload game options, create table again.</i><br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/4b3a73eeb5acae961ade18473af119e8ce8d1a8f]<br />
<br />
== Layout and Graphics ==<br />
<br />
In this section we will do graphics of the game, and main layout of the game.<br />
<br />
First copy a sprite with cards image from hearts img/cards.jpg into img/ folder of your project. Project hearts is mounted to your home directory on bga server.<br />
<br />
Edit .tpl to add some divs to represent player table and hand area<br />
<br />
<pre><br />
<div id="myhand_wrap" class="whiteblock"><br />
<h3>My Hand</h3><br />
<div id="myhand"><br />
</div><br />
</div><br />
</pre><br />
<br />
If you refresh you should see now white area with My Hand title.<br />
<br />
<br />
[[File:Heartsla-tpl2.png]]<br />
<br />
Now lets add a card into the hand, just so you can feel it. Edit .tpl and a playertablecard div inside a hand div<br />
<pre><br />
...<br />
<div id="myhand"><br />
<div class="playertablecard"></div><br />
</div><br />
...<br />
</pre><br />
<br />
Edit .css file<br />
<pre><br />
.playertablecard {<br />
display: inline-block;<br />
position: relative;<br />
margin-top: 5px;<br />
width: 72px;<br />
height: 96px;<br />
background-image: url('img/cards.jpg'); /* temp hack to see it */<br />
}<br />
</pre><br />
<br />
When you edit CSS remember that you have to FORCE-reload page, i.e. Ctrl-F5, otherwise its cached.<br />
<i>Same when you change existing graphics files</i>.<br />
<br />
You should see this:<br />
<br />
[[File:Heartsla-tpl3.png]]<br />
<br />
Awesome! Now lets do the rest of layout.<br />
<br />
There are few ways of how html could have been generated, you could have start with nothing and generate<br />
all by java script. Or you could have started with complete game markup in html and make java script just hide and move pieces around. BGA framework provides also a third way which is mix of both plus template engine to generate HTML using php. So lets do that.<br />
<br />
Change .tpl file to have this inside<br />
<pre><br />
<div id="playertables"><br />
<br />
<!-- BEGIN player --><br />
<div class="playertable whiteblock playertable_{DIR}"><br />
<div class="playertablename" style="color:#{PLAYER_COLOR}"><br />
{PLAYER_NAME}<br />
</div><br />
<div class="playertablecard" id="playertablecard_{PLAYER_ID}"><br />
</div><br />
</div><br />
<!-- END player --><br />
<br />
</div><br />
<br />
<div id="myhand_wrap" class="whiteblock"><br />
<h3>{MY_HAND}</h3><br />
<div id="myhand"><br />
</div><br />
</div><br />
</pre><br />
<br />
What we did is we added "block" player, it is marked up using html comments. {VAR} notation is used<br />
to inject variables and <br />
<pre><br />
<!-- BEGIN xxx --> <br />
inside <br />
<!-- END xxx --> <br />
</pre><br />
effectively allows us to do template loops.<br />
<br />
In .view.php insert this code after 'Place your code below' comment<br />
<br />
<br />
<pre><br />
$template = self::getGameName() . "_" . self::getGameName();<br />
<br />
$directions = array( 'S', 'W', 'N', 'E' );<br />
<br />
// this will inflate our player block with actual players data<br />
$this->page->begin_block($template, "player");<br />
foreach ( $players as $player_id => $info ) {<br />
$dir = array_shift($directions);<br />
$this->page->insert_block("player", array ("PLAYER_ID" => $player_id,<br />
"PLAYER_NAME" => $players [$player_id] ['player_name'],<br />
"PLAYER_COLOR" => $players [$player_id] ['player_color'],<br />
"DIR" => $dir ));<br />
}<br />
// this will make our My Hand text translatable<br />
$this->tpl['MY_HAND'] = self::_("My hand");<br />
</pre><br />
<br />
What it does is for each player we have it will replicate the html between <!-- BEGIN player --> and <!-- END player --> tags, substituting the variable denoted by {XXX}<br />
with the values you provide. The DIR variable in this case we pulling from directions array (where array_shift will take first element and remove it from the array).<br />
<br />
Reload. If everything went well you should see this:<br />
<br />
[[File:Heartsla-tpl4.png]]<br />
<br />
These are "tableau" areas for 4 players plus My hand visible only to one player.<br />
They are not exactly how we wanted them to be because we did not edit .css yet.<br />
<br />
Now edit .css, add these lines after import before our previous definition<br />
<br />
<pre><br />
/** Table layout **/<br />
<br />
#playertables {<br />
position: relative;<br />
width: 710px;<br />
height: 340px;<br />
}<br />
<br />
.playertablename {<br />
font-weight: bold;<br />
}<br />
<br />
.playertable {<br />
position: absolute;<br />
text-align: center;<br />
width: 180px;<br />
height: 130px;<br />
}<br />
<br />
.playertable_N {<br />
left: 50%;<br />
top: 0px;<br />
margin-left: -90px; /* half of 180 */<br />
}<br />
.playertable_S {<br />
left: 50%;<br />
bottom: 0px;<br />
margin-left: -90px; /* half of 180 */<br />
}<br />
.playertable_W {<br />
left: 0px;<br />
top: 50%;<br />
margin-top: -55px; /* half of 130 */<br />
}<br />
.playertable_E {<br />
right: 0px;<br />
top: 50%;<br />
margin-top: -55px; /* half of 130 */<br />
}<br />
</pre><br />
<br />
<br />
Now you force Reload and you should see this:<br />
[[File:Heartsla-tpl5.png]]<br />
<br />
This is almost all we need for graphics and layout, there are few tweaks left there but lets do some more heavy lifting now.<br />
<br />
<i>Note: if you did not see changes you may have not force reloaded, force means you use Ctrl+F5 or Cltr+Shift-R, if you don't "force" browser will use cached version of .css and images! Which is not what you just changed</i><br />
<br />
<br />
<i>Another Note: In general if you have auto-sync you don't need to reload if you change game.php file, you need normal reload if you change js, and force reload for css and images. If you changed state machine or database you likely need to restart the game.</i><br />
<br />
== Game Interface JS Stock ==<br />
<br />
The BGA framework provides a few out of the box classes to deal with cards. The client side<br />
contains a class called [[Stock]] and it can be used for any dynamic html "pieces" management that uses<br />
common sprite images. On the server side we will use the [[Deck]] class which we discuss later.<br />
<br />
If you open cards.jpg in an image viewer you will see that it is a "sprite" image - a 13x4 grid of images stitched together,<br />
which is a very efficient way to transport images. So we will use the Stock class to mark up these images and create<br />
"card" divs for us.<br />
<br />
First, we need to add '''ebg/stock''' as a dependency in the hearts.js file:<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <==== HERE<br />
],<br />
</pre><br />
<br />
Then add this to the Javascript contructor, this will define size of our cards<br />
<pre><br />
console.log('hearts constructor');<br />
this.cardwidth = 72;<br />
this.cardheight = 96;<br />
</pre><br />
<br />
The stock is initialized in the Javascript "setup" method like this:<br />
<pre><br />
// TODO: Set up your game interface here, according to "gamedatas"<br />
<br />
// Player hand<br />
this.playerHand = new ebg.stock(); // new stock object for hand<br />
this.playerHand.create( this, $('myhand'), this.cardwidth, this.cardheight );<br />
</pre><br />
<br />
As parameters of the "create" method, we provided the width/height of an item (a card), and the container div "myhand" - which is an id of "div" element from our .tpl file representing a player hand.<br />
<br />
<br />
Then, we must tell the stock what items it is going to display during its life: the 52 cards of a standard card game from a CSS sprite image named "cards.jpg" with all the cards arranged in 4 rows and 13 columns.<br />
<br />
Here's how we tell stock what item types to display:<br />
<pre><br />
this.playerHand.image_items_per_row = 13; // 13 images per row<br />
<br />
<br />
// Create cards types:<br />
for (var color = 1; color <= 4; color++) {<br />
for (var value = 2; value <= 14; value++) {<br />
// Build card type id<br />
var card_type_id = this.getCardUniqueId(color, value);<br />
this.playerHand.addItemType(card_type_id, card_type_id, g_gamethemeurl + 'img/cards.jpg', card_type_id);<br />
}<br />
}<br />
</pre><br />
<br />
And add this function to the utilities section<br />
<pre><br />
// Get card unique identifier based on its color and value<br />
getCardUniqueId : function(color, value) {<br />
return (color - 1) * 13 + (value - 2);<br />
},<br />
</pre><br />
<br />
Explanations:<br />
* At first, we tell the stock component that our CSS sprite contains 13 items per row. This way, it can find the correct image for each card type id.<br />
* Then for the 4x13 cards, we call the '''addItemType''' method that creates the type. The arguments are the type id, the weight of the card (for sorting purpose), the URL of our CSS sprite, and the position of our card image in the CSS sprite. It happens to be the same number in our case.<br />
<br />
Note: we need to generate a unique ID for each type of card based on its color and value. For that we create a function '''getCardUniqueId'''. The type is the unique identifier of the TYPE of the card, e.g., the queen of spades encoded as an integer. If our deck had 2 standard card decks we would have had 2 queens of spades; they would share the same type and the same image but would have different ids. NOTE: It's unfortunate that they named this '''getCardUniqueId'''; it should have been '''getCardUniqueType''', because it really isn't an id, but a TYPE of card. The type of the item should either be a reversible function of its properties (i.e., kind of suite * 13 + value) or just an enumerator described in material.inc.php. In this specific case it's a synthetic type id, which also the same as the number of the card in the sprite image (i.e., if you enumerate each image in sprite going left to right, then top to bottom).<br />
<br />
Now let's add the 5 of Hearts to the player's hand just for fun (this code will go in setup method after types initialization):<br />
<br />
<pre><br />
// 2 = hearts, 5 is 5, and 42 is the card id, which normally would come from db<br />
this.playerHand.addToStockWithId( this.getCardUniqueId( 2, 5 ), 42 );<br />
</pre><br />
<br />
This will add the card with id 42 and type 16 ( (2-1)*13+(5-2)=16 ). <br />
<br />
Note that number 16 would not be something you can see in database, Deck database will have separate field for type and type_arg where type is suite and type_arg is number, so its not the same thing, but you can use same formula to convert. Number 42 on the other hand would be id field in database. But we get to database in the later section.<br />
<br />
If you reload now you should see the 5 of hearts in "your hand".<br />
<br />
Stock control can handle clicking on items and forms the selection. You can immediately react to selection<br />
or you can query it later; for example when user presses some other button.<br />
<br />
Let's hook it up. Add this in the setup method in .js file, after this.playerHand is initialised:<br />
<br />
dojo.connect( this.playerHand, 'onChangeSelection', this, 'onPlayerHandSelectionChanged' );<br />
<br />
<br />
Then find the Player's action comment section and add a handler after the comment:<br />
<br />
<pre><br />
onPlayerHandSelectionChanged : function() {<br />
var items = this.playerHand.getSelectedItems();<br />
<br />
if (items.length > 0) {<br />
if (this.checkAction('playCard', true)) {<br />
// Can play a card<br />
<br />
var card_id = items[0].id;<br />
console.log("on playCard "+card_id);<br />
<br />
this.playerHand.unselectAll();<br />
} else if (this.checkAction('giveCards')) {<br />
// Can give cards => let the player select some cards<br />
} else {<br />
this.playerHand.unselectAll();<br />
}<br />
}<br />
},<br />
</pre><br />
<br />
The function name of the handler is 4th parameter of the dojo.connect function. Make sure you spell it correctly or there will be unpredictable effects.<br />
<br />
Now if you reload, open the Javascript Console (F12), and then click on the card in My Hand, you should see:<br />
on playCard 42<br />
printed on the console<br />
<br />
''Note : You need to be the active player to have rights to play a card and so log your message in the console''<br />
<br />
== Game Database and Game Initialisation ==<br />
<br />
Next step, you want to design a game database and setup a new game (on the server side).<br />
For that we need to a) modify the database schema to add our cards data b) add some global variables into<br />
the existing globals table.<br />
<br />
To modify the schema, first exit your existing game(s). Open '''dbmodel.sql''' file and uncomment the card table creation.<br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `card` (<br />
`card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,<br />
`card_type` varchar(16) NOT NULL,<br />
`card_type_arg` int(11) NOT NULL,<br />
`card_location` varchar(16) NOT NULL,<br />
`card_location_arg` int(11) NOT NULL,<br />
PRIMARY KEY (`card_id`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;<br />
</pre><br />
<br />
This is the "card" table which will be managed by the Deck php class.<br />
<br />
In addition we want a little piece of information in the players table:<br />
<br />
-- add info about first player<br />
ALTER TABLE `player` ADD `player_first` BOOLEAN NOT NULL DEFAULT '0';<br />
<br />
Not sure why they put this into the player table, as we could use a global db variable to hold first player as easily.<br />
But I am just following the existing code more-or-less.<br />
<br />
Next we finally get into .game.php class, where the main logic and db interaction would be. Find php constructor which should be <br />
function __construct( )<br />
This is first function in a file. Add this code to constructor.<br />
<pre><br />
parent::__construct();<br />
self::initGameStateLabels( array( <br />
"currentHandType" => 10, <br />
"trickColor" => 11, <br />
"alreadyPlayedHearts" => 12,<br />
) );<br />
<br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
</pre><br />
<br />
Here we are initializing three "Game State Variables" which are variables stored in the database. They are integers.<br />
It must start with no values lower then 10 since values lower than 10 are reserved. These values are stored by numeric ids<br />
in the database, but in the php we associate them with string labels for convenience of access. The variables are "trickColor": numbers from 1 to 4 that map to card suit (not sure why it's called color; maybe it's a translation from French); "alreadyPlayedHearts": a boolean flag (0 or 1) indicating hether somebody used hearts on the trick; "currentHandType": stores the value to indicate who to give cards to during exchange.<br />
<br />
The next 2 lines are creating $this->cards object and associating it with "card" table in the the database.<br />
<br />
<i>If we called db table 'foo' instead of 'card' the last statement would have been $this->cards->init( "foo" )</i><br />
<br />
<br />
At this point I would start a new game and make sure it starts, then exit. <br />
<br />
<i><br />
If you made a mistake<br />
in the .sql or php constructor the game won't start, and good luck debugging it. (That is why it's important to check<br />
once in a while to make sure it still starts while you remember what you have changed.)<br />
</i><br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/e3a049257b592ff6167688d4d344f8a83d349b08]<br />
<br />
Now we can go to game initialization '''setupNewGame''' in game.php. This method is called once when the table is created.<br />
<br />
In your template project you should have code that deals with player table, just leave it as is. Start inserting the<br />
other code after "Start the game initialization" comment.<br />
<pre><br />
// Init global values with their initial values<br />
<br />
// Note: hand types: 0 = give 3 cards to player on the left<br />
// 1 = give 3 cards to player on the right<br />
// 2 = give 3 cards to player opposite<br />
// 3 = keep cards<br />
self::setGameStateInitialValue( 'currentHandType', 0 );<br />
<br />
// Set current trick color to zero (= no trick color)<br />
self::setGameStateInitialValue( 'trickColor', 0 );<br />
<br />
// Mark if we already played hearts during this hand<br />
self::setGameStateInitialValue( 'alreadyPlayedHearts', 0 );<br />
</pre><br />
<br />
Here we initialize all the globals to 0.<br />
<br />
Next is to create our cards in the database. We have one deck of cards so it's pretty simple.<br />
<pre><br />
// Create cards<br />
$cards = array ();<br />
foreach ( $this->colors as $color_id => $color ) {<br />
// spade, heart, diamond, club<br />
for ($value = 2; $value <= 14; $value ++) {<br />
// 2, 3, 4, ... K, A<br />
$cards [] = array ('type' => $color_id,'type_arg' => $value,'nbr' => 1 );<br />
}<br />
}<br />
<br />
$this->cards->createCards( $cards, 'deck' );<br />
</pre><br />
<br />
This code that will create one of each card. But don't run it yet, because we missing $this->colors.<br />
So we have state of the game in the database, but there is some static game information which never changes.<br />
This information should be stored in material.inc.php and this way it can be accessed from all .php files.<br />
We will edit this file now by adding these lines<br />
<br />
<pre><br />
$this->colors = array(<br />
1 => array( 'name' => clienttranslate('spade'),<br />
'nametr' => self::_('spade') ),<br />
2 => array( 'name' => clienttranslate('heart'),<br />
'nametr' => self::_('heart') ),<br />
3 => array( 'name' => clienttranslate('club'),<br />
'nametr' => self::_('club') ),<br />
4 => array( 'name' => clienttranslate('diamond'),<br />
'nametr' => self::_('diamond') )<br />
);<br />
<br />
$this->values_label = array(<br />
2 =>'2',<br />
3 => '3',<br />
4 => '4',<br />
5 => '5',<br />
6 => '6',<br />
7 => '7',<br />
8 => '8',<br />
9 => '9',<br />
10 => '10',<br />
11 => clienttranslate('J'),<br />
12 => clienttranslate('Q'),<br />
13 => clienttranslate('K'),<br />
14 => clienttranslate('A')<br />
);<br />
</pre><br />
<br />
Where $this->colors will define Suit labels and $this->values_label will define value labels.<br />
If you noticed, we have two of each label for suits. This is because sometimes we need translated values on the php<br />
side and sometimes we don't. In this case '''nametr''' will return a translated value in php, which is only useful when you throw exceptions to show the right strings. If you pass a value to the client via notification you should always use untranslated strings, and the client will translate it. 'clienttranslate' marks the value for translation but does not actually change it for php. For more about this wonderful translation stuff see [[Translations]].<br />
<br />
== Full game model synchronisation ==<br />
<br />
Now at any point in the game we need to make sure that database information can be reflected back in the UI, so we must fix the '''getAllDatas''' function<br />
to return all possible data we need to reconstruct the game. This is in the game.php file. The template for getAllDatas() already takes care of player info. Let's just<br />
add hand and tableau data before we return a result.<br />
<br />
<pre><br />
// Cards in player hand<br />
$result['hand'] = $this->cards->getCardsInLocation( 'hand', $current_player_id );<br />
<br />
// Cards played on the table<br />
$result['cardsontable'] = $this->cards->getCardsInLocation( 'cardsontable' );<br />
</pre><br />
<br />
Now on the client side we should display this data, so in your .js file in the setup function (which is the receiver of getAllDatas) replace our hack of putting 5 of Hearts directly into the hand with:<br />
<br />
<pre><br />
// Cards in player's hand<br />
for ( var i in this.gamedatas.hand) {<br />
var card = this.gamedatas.hand[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
this.playerHand.addToStockWithId(this.getCardUniqueId(color, value), card.id);<br />
}<br />
<br />
// Cards played on table<br />
for (i in this.gamedatas.cardsontable) {<br />
var card = this.gamedatas.cardsontable[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
var player_id = card.location_arg;<br />
this.playCardOnTable(player_id, color, value, card.id);<br />
}<br />
</pre><br />
<br />
This should show hand and tableau cards now, except we are missing the '''playCardOnTable''' function. So find the '''getCardUniqueId''' function which<br />
should be in the utilities section and add this after it:<br />
<br />
<pre><br />
playCardOnTable : function(player_id, color, value, card_id) {<br />
// player_id => direction<br />
dojo.place(this.format_block('jstpl_cardontable', {<br />
x : this.cardwidth * (value - 2),<br />
y : this.cardheight * (color - 1),<br />
player_id : player_id<br />
}), 'playertablecard_' + player_id);<br />
<br />
if (player_id != this.player_id) {<br />
// Some opponent played a card<br />
// Move card from player panel<br />
this.placeOnObject('cardontable_' + player_id, 'overall_player_board_' + player_id);<br />
} else {<br />
// You played a card. If it exists in your hand, move card from there and remove<br />
// corresponding item<br />
<br />
if ($('myhand_item_' + card_id)) {<br />
this.placeOnObject('cardontable_' + player_id, 'myhand_item_' + card_id);<br />
this.playerHand.removeFromStockById(card_id);<br />
}<br />
}<br />
<br />
// In any case: move it to its final destination<br />
this.slideToObject('cardontable_' + player_id, 'playertablecard_' + player_id).play();<br />
},<br />
</pre><br />
<br />
For this to work we also need to add a card template in the .tpl file<br />
<pre><br />
// Javascript HTML templates<br />
<br />
var jstpl_cardontable = '<div class="cardontable" id="cardontable_${player_id}" style="background-position:-${x}px -${y}px">\<br />
</div>';<br />
</pre><br />
<br />
<br />
What this does is basically create another card object, because if it is not our card it's not in our hand (Stock) so<br />
we have to create it out of thin air. The technique to do that is to implement a Javascript template object defined in the .tpl file with some<br />
parameters, which will basically create a "div" string (yes you could have used string concatenation but it would not be fancy).<br />
Now dojo.place places it (the div) on top of a placeholder. Now we have an object with an id of 'cardontable_' + player_id. Depending<br />
on who is playing it we either place it on the player miniboard or in hand (and remove it from hand stock). Then we animate the card move.<br />
<br />
We also should fix our .css file now to add style for cardontable and REMOVE background for playertablecard which really is a placeholder div and not a card. (Don't miss the remove step; it will be all screwy if you do!)<br />
<br />
<pre><br />
.playertablecard {<br />
display: inline-block;<br />
position: relative;<br />
margin-top: 5px;<br />
width: 72px;<br />
height: 96px;<br />
/* we remove background-image here */<br />
}<br />
<br />
/*** cards on table ***/<br />
<br />
.cardontable {<br />
position: absolute;<br />
width: 72px;<br />
height: 96px;<br />
background-image: url('img/cards.jpg'); <br />
}<br />
</pre><br />
<br />
Now to test that it actually works let's deal cards to players during game initialization:<br />
<br />
Add this after createCards in setupNewGame function in the game.php file<br />
<pre><br />
// Shuffle deck<br />
$this->cards->shuffle('deck');<br />
// Deal 13 cards to each players<br />
$players = self::loadPlayersBasicInfos();<br />
foreach ( $players as $player_id => $player ) {<br />
$cards = $this->cards->pickCards(13, 'deck', $player_id);<br />
} <br />
</pre><br />
<br />
Now when you start the game you should see 13 cards in your hand!<br />
<br />
We just need to hook-up clicking on card and test if our playCardOnTable works.<br />
<br />
Find onPlayerHandSelectionChanged function in the JS file, we should have logging there like console.log("on playCard "+card_id);<br />
So after that insert this:<br />
<pre><br />
console.log("on playCard "+card_id);<br />
// type is (color - 1) * 13 + (value - 2)<br />
var type = items[0].type;<br />
var color = Math.floor(type / 13) + 1;<br />
var value = type % 13 + 2;<br />
<br />
this.playCardOnTable(this.player_id,color,value,card_id);<br />
</pre><br />
Note: this code is for testing we will replace it with server interaction after we test it.<br />
<br />
Now if you force reload (because we changed .css before) you should be able to click on card from you have and see it moving,<br />
you can click on few cards this way. When you done enjoying the animation, press F5 to get your hand back.<br />
<br />
[[File:Heartsla-sync.png]]<br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/01d4e2f595fd14c2adcc97a957d21bb2766f78a8]<br />
<br />
== State Machine ==<br />
<br />
Now we need to create a game state machine. So the states are:<br />
<br />
* Cards are dealt to all players (lets call it "newHand")<br />
* Player is selected who will start a new trick ("newTrick")<br />
* Player start or respond to played card ("playerTurn")<br />
* Game control is passed to next player or trick is ended ("nextPlayer")<br />
* End of hand processing (scoring and check for end of game) ("nextHand")<br />
<br />
In addition players can exchange cards so we need two more states for that but we will skip it for now.<br />
<br />
<br />
The state handling spread across 4 files, so you have to make sure all pieces are connected together.<br />
The state machine states.php defines all the states, and function handlers on php side in a form of string,<br />
and if any of these functions are not implemented it would be very hard to debug because it will break in random places.<br />
<br />
So .states.php<br />
<br />
<pre><br />
$machinestates = array(<br />
<br />
// The initial state. Please do not modify.<br />
1 => array(<br />
"name" => "gameSetup",<br />
"description" => clienttranslate("Game setup"),<br />
"type" => "manager",<br />
"action" => "stGameSetup",<br />
"transitions" => array( "" => 20 )<br />
),<br />
<br />
<br />
/// New hand<br />
20 => array(<br />
"name" => "newHand",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNewHand",<br />
"updateGameProgression" => true, <br />
"transitions" => array( "" => 30 )<br />
), <br />
<br />
<br />
<br />
// Trick<br />
<br />
30 => array(<br />
"name" => "newTrick",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNewTrick",<br />
"transitions" => array( "" => 31 )<br />
), <br />
31 => array(<br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must play a card'),<br />
"descriptionmyturn" => clienttranslate('${you} must play a card'),<br />
"type" => "activeplayer",<br />
"possibleactions" => array( "playCard" ),<br />
"transitions" => array( "playCard" => 32 )<br />
), <br />
32 => array(<br />
"name" => "nextPlayer",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNextPlayer",<br />
"transitions" => array( "nextPlayer" => 31, "nextTrick" => 30, "endHand" => 40 )<br />
), <br />
<br />
<br />
// End of the hand (scoring, etc...)<br />
40 => array(<br />
"name" => "endHand",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stEndHand",<br />
"transitions" => array( "nextHand" => 20, "endGame" => 99 )<br />
), <br />
<br />
// Final state.<br />
// Please do not modify.<br />
99 => array(<br />
"name" => "gameEnd",<br />
"description" => clienttranslate("End of game"),<br />
"type" => "manager",<br />
"action" => "stGameEnd",<br />
"args" => "argGameEnd"<br />
)<br />
<br />
);<br />
</pre><br />
<br />
The full details about what these fields are you can find in [[Your_game_state_machine:_states.inc.php]].<br />
<br />
But basically we have Player states, in which human player has to perform an "action" by pressing some button in UI or selecting some game item, which will trigger js handler, which will do ajax call to server into API define <br />
by .action.php file. All functions in this file are API between client and server and has very simple<br />
and repetitive structure. In this case there is only two action player can do - play a card or pass cards to other player. So these 2 functions go into .action.php file, we will only define one now since we have not implemented card passing states yet:<br />
<br />
<pre><br />
public function playCard() {<br />
self::setAjaxMode();<br />
$card_id = self::getArg("id", AT_posint, true);<br />
$this->game->playCard($card_id);<br />
self::ajaxResponse();<br />
}<br />
</pre><br />
<br />
<br />
Now to make it run we have define all handler functions that we referenced in states, which are - one function for state arguments argGiveCards, 4 functions for robot states (where game performs some action)<br />
and 1 function for player actions handling.<br />
Find 'Game state arguments' section and paste this in:<br />
<pre><br />
function argGiveCards() {<br />
return array ();<br />
}<br />
</pre><br />
<br />
This normally pass some parameters to states, but we don't need anything yet. It good to have placeholder there anyway, so we can fix it later.<br />
Important: even when its a stub this function must return array not scalar.<br />
<br />
Lets do stubs for other functions, find game state actions section in .game.php file and insert these<br />
<pre><br />
function stNewHand() {<br />
// Take back all cards (from any location => null) to deck<br />
$this->cards->moveAllCardsInLocation(null, "deck");<br />
$this->cards->shuffle('deck');<br />
// Deal 13 cards to each players<br />
// Create deck, shuffle it and give 13 initial cards<br />
$players = self::loadPlayersBasicInfos();<br />
foreach ( $players as $player_id => $player ) {<br />
$cards = $this->cards->pickCards(13, 'deck', $player_id);<br />
// Notify player about his cards<br />
self::notifyPlayer($player_id, 'newHand', '', array ('cards' => $cards ));<br />
}<br />
self::setGameStateValue('alreadyPlayedHearts', 0);<br />
$this->gamestate->nextState("");<br />
}<br />
<br />
function stNewTrick() {<br />
// New trick: active the player who wins the last trick, or the player who own the club-2 card<br />
// Reset trick color to 0 (= no color)<br />
self::setGameStateInitialValue('trickColor', 0);<br />
$this->gamestate->nextState();<br />
}<br />
<br />
function stNextPlayer() {<br />
// Active next player OR end the trick and go to the next trick OR end the hand<br />
if ($this->cards->countCardInLocation('cardsontable') == 4) {<br />
// This is the end of the trick<br />
// Move all cards to "cardswon" of the given player<br />
$best_value_player_id = self::activeNextPlayer(); // TODO figure out winner of trick<br />
$this->cards->moveAllCardsInLocation('cardsontable', 'cardswon', null, $best_value_player_id);<br />
<br />
if ($this->cards->countCardInLocation('hand') == 0) {<br />
// End of the hand<br />
$this->gamestate->nextState("endHand");<br />
} else {<br />
// End of the trick<br />
$this->gamestate->nextState("nextTrick");<br />
}<br />
} else {<br />
// Standard case (not the end of the trick)<br />
// => just active the next player<br />
$player_id = self::activeNextPlayer();<br />
self::giveExtraTime($player_id);<br />
$this->gamestate->nextState('nextPlayer');<br />
}<br />
}<br />
<br />
function stEndHand() {<br />
$this->gamestate->nextState("nextHand");<br />
}<br />
<br />
</pre><br />
Important: All state actions game or player must end with state transition (or thrown exception). Also make sure its ONLY one state transition,<br />
if you accidentally fall though after state transition and do another one it will be a real mess and head scratching for long time.<br />
<br />
Now find 'player actions' section and paste this code there<br />
<pre><br />
function playCard($card_id) {<br />
self::checkAction("playCard");<br />
$player_id = self::getActivePlayerId();<br />
throw new BgaUserException(self::_("Not implemented: ") . "$player_id plays $card_id");<br />
}<br />
</pre><br />
We won't implement it yet but throw an exception which we will see if interaction is working properly<br />
<br />
Now the game should start but it would not be any different then before because we have to implement actual interactions.<br />
Its good to check if it still working though (and if it was running before you have to exit because we changed state machine and normally it will break stuff)<br />
<br />
== Client - Server interactions ==<br />
<br />
Now to implement things for real we have hook UI actions to ajax calls, and process notifications sent by the server.<br />
So previously we hooked playCardOnTable right into js handler which caused client animation, in real game its a two<br />
step operation. When user clicks on game element js client sends an ajax call to server, server processes it and updates database, server sends<br />
notification in response, client hooks animations to server notification.<br />
<br />
So in .js code replace onPlayerHandSelectionChanged with<br />
<pre><br />
onPlayerHandSelectionChanged : function() {<br />
var items = this.playerHand.getSelectedItems();<br />
<br />
if (items.length > 0) {<br />
var action = 'playCard';<br />
if (this.checkAction(action, true)) {<br />
// Can play a card<br />
var card_id = items[0].id; <br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", {<br />
id : card_id,<br />
lock : true<br />
}, this, function(result) {<br />
}, function(is_error) {<br />
});<br />
<br />
this.playerHand.unselectAll();<br />
} else if (this.checkAction('giveCards')) {<br />
// Can give cards => let the player select some cards<br />
} else {<br />
this.playerHand.unselectAll();<br />
}<br />
}<br />
},<br />
</pre><br />
<br />
<br />
Now when you click on card you should get a server response: Not implemented...<br />
<br />
Lets implement it, in .game.php<br />
<pre><br />
function playCard($card_id) {<br />
self::checkAction("playCard");<br />
$player_id = self::getActivePlayerId();<br />
$this->cards->moveCard($card_id, 'cardsontable', $player_id);<br />
// XXX check rules here<br />
$currentCard = $this->cards->getCard($card_id);<br />
// And notify<br />
self::notifyAllPlayers('playCard', clienttranslate('${player_name} plays ${value_displayed} ${color_displayed}'), array (<br />
'i18n' => array ('color_displayed','value_displayed' ),'card_id' => $card_id,'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),'value' => $currentCard ['type_arg'],<br />
'value_displayed' => $this->values_label [$currentCard ['type_arg']],'color' => $currentCard ['type'],<br />
'color_displayed' => $this->colors [$currentCard ['type']] ['name'] ));<br />
// Next player<br />
$this->gamestate->nextState('playCard');<br />
}<br />
</pre><br />
<br />
We get the card from client, we move it to the tableau (moveCard is hooked to database directly, its part of deck class),<br />
we notify all players and we change state. What we missing here is bunch of checks (rule enforcements), we will add it later.<br />
<br />
Interesting part about this notify is that we use i18n array for string that needs to be translated by client, so<br />
they sent as English text in notification, then client has to know which parameters needs translating.<br />
<br />
On the client side .js we have to implement a notification handler to do the animation<br />
<br />
<pre><br />
setupNotifications : function() {<br />
console.log('notifications subscriptions setup');<br />
<br />
dojo.subscribe('newHand', this, "notif_newHand");<br />
dojo.subscribe('playCard', this, "notif_playCard");<br />
<br />
},<br />
<br />
notif_newHand : function(notif) {<br />
// We received a new full hand of 13 cards.<br />
this.playerHand.removeAll();<br />
<br />
for ( var i in notif.args.cards) {<br />
var card = notif.args.cards[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
this.playerHand.addToStockWithId(this.getCardUniqueId(color, value), card.id);<br />
}<br />
},<br />
<br />
notif_playCard : function(notif) {<br />
// Play a card on the table<br />
this.playCardOnTable(notif.args.player_id, notif.args.color, notif.args.value, notif.args.card_id);<br />
},<br />
</pre><br />
<br />
Now it actually works through the server when you click on card - the move is recorded. If you testing it now you will notice<br />
after trick is done all cards remains on the table, but if you press F5 they would disappear, this is because<br />
we updated database to pick-up the cards but did not send notification about it, so we need to send notification about it<br />
and have a handler for it<br />
<br />
So in .game.php file add notification in stNextPlayer function after moveAllCardsInLocation call:<br />
<br />
<pre><br />
// Notify<br />
// Note: we use 2 notifications here in order we can pause the display during the first notification<br />
// before we move all cards to the winner (during the second)<br />
$players = self::loadPlayersBasicInfos();<br />
self::notifyAllPlayers( 'trickWin', clienttranslate('${player_name} wins the trick'), array(<br />
'player_id' => $best_value_player_id,<br />
'player_name' => $players[ $best_value_player_id ]['player_name']<br />
) ); <br />
self::notifyAllPlayers( 'giveAllCardsToPlayer','', array(<br />
'player_id' => $best_value_player_id<br />
) );<br />
</pre><br />
<br />
And in .js file add 2 more notification handlers.<br />
<br />
This is to subscribe in setupNotifications function<br />
<pre><br />
dojo.subscribe( 'trickWin', this, "notif_trickWin" );<br />
this.notifqueue.setSynchronous( 'trickWin', 1000 );<br />
dojo.subscribe( 'giveAllCardsToPlayer', this, "notif_giveAllCardsToPlayer" );<br />
</pre><br />
<br />
And this are handlers<br />
<pre><br />
notif_trickWin : function(notif) {<br />
// We do nothing here (just wait in order players can view the 4 cards played before they're gone.<br />
},<br />
notif_giveAllCardsToPlayer : function(notif) {<br />
// Move all cards on table to given table, then destroy them<br />
var winner_id = notif.args.player_id;<br />
for ( var player_id in this.gamedatas.players) {<br />
var anim = this.slideToObject('cardontable_' + player_id, 'overall_player_board_' + winner_id);<br />
dojo.connect(anim, 'onEnd', function(node) {<br />
dojo.destroy(node);<br />
});<br />
anim.play();<br />
}<br />
},<br />
</pre><br />
<br />
So 'trickWin' notification does not do much except it will delay the processing of next notification by 1 second (1000 ms)<br />
and it will log the message (that happens independent of what handler does).<br />
<i>Note: if on the other hand you don't want to log but what what to do something else send empty message</i><br />
<br />
Now after the trick you see all cards move the "player's stash".<br />
<br />
== Scoring and End of game handling ==<br />
<br />
Now we should calculate scoring and for that we need to actually track who wins the trick.<br />
Trick is won by the player with highest card (no trump). We just need to remember what is trick suite.<br />
For which we will use state variable 'trickColor' which we already conveniently created.<br />
<br />
In .game.php file find playCard function and add this before notify functions<br />
$currentTrickColor = self::getGameStateValue( 'trickColor' ) ;<br />
if( $currentTrickColor == 0 )<br />
self::setGameStateValue( 'trickColor', $currentCard['type'] );<br />
<br />
This will make sure we remember first suit being played, now to use it modify stNextPlayer function to fix our TODO comment<br />
<pre><br />
function stNextPlayer() {<br />
// Active next player OR end the trick and go to the next trick OR end the hand<br />
if ($this->cards->countCardInLocation('cardsontable') == 4) {<br />
// This is the end of the trick<br />
$cards_on_table = $this->cards->getCardsInLocation('cardsontable');<br />
$best_value = 0;<br />
$best_value_player_id = null;<br />
$currentTrickColor = self::getGameStateValue('trickColor');<br />
foreach ( $cards_on_table as $card ) {<br />
// Note: type = card color<br />
if ($card ['type'] == $currentTrickColor) {<br />
if ($best_value_player_id === null || $card ['type_arg'] > $best_value) {<br />
$best_value_player_id = $card ['location_arg']; // Note: location_arg = player who played this card on table<br />
$best_value = $card ['type_arg']; // Note: type_arg = value of the card<br />
}<br />
}<br />
}<br />
<br />
// Active this player => he's the one who starts the next trick<br />
$this->gamestate->changeActivePlayer( $best_value_player_id );<br />
<br />
// Move all cards to "cardswon" of the given player<br />
$this->cards->moveAllCardsInLocation('cardsontable', 'cardswon', null, $best_value_player_id);<br />
<br />
// Notify<br />
// ... same code here as before<br />
</pre><br />
<br />
The scoring rule in the studio example code is huge multi-page function, for this tutorial we will make simplier.<br />
Lets score -1 point per heart and call it a day. And game will end when somebody goes -100 or below.<br />
<br />
As UI goes for scoring, the main thing to update is the scoring on the mini boards represented by stars, also<br />
we want to show that in the log. <br />
In addition scoring can be shown in [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialog]] using tableWindow notification, but it is a tutorial on its own and you can do it as homework (it is part of original heart game).<br />
<br />
In .js file we need to add one more subscription and notification handler:<br />
dojo.subscribe( 'newScores', this, "notif_newScores" );<br />
in setupNotifications<br />
<br />
and <br />
notif_newScores : function(notif) {<br />
// Update players' scores<br />
for ( var player_id in notif.args.newScores) {<br />
this.scoreCtrl[player_id].toValue(notif.args.newScores[player_id]);<br />
}<br />
},<br />
somewhere after. this.scoreCtrl is pre-existing object that shows the scoring and this function will update score values per player from notification argument<br />
<br />
so in .game.php our stEndHand function will look like<br />
<br />
<pre><br />
function stEndHand() {<br />
// Count and score points, then end the game or go to the next hand.<br />
$players = self::loadPlayersBasicInfos();<br />
// Gets all "hearts" + queen of spades<br />
<br />
$player_to_points = array ();<br />
foreach ( $players as $player_id => $player ) {<br />
$player_to_points [$player_id] = 0;<br />
}<br />
$cards = $this->cards->getCardsInLocation("cardswon");<br />
foreach ( $cards as $card ) {<br />
$player_id = $card ['location_arg'];<br />
// Note: 2 = heart<br />
if ($card ['type'] == 2) {<br />
$player_to_points [$player_id] ++;<br />
}<br />
}<br />
// Apply scores to player<br />
foreach ( $player_to_points as $player_id => $points ) {<br />
if ($points != 0) {<br />
$sql = "UPDATE player SET player_score=player_score-$points WHERE player_id='$player_id'";<br />
self::DbQuery($sql);<br />
$heart_number = $player_to_points [$player_id];<br />
self::notifyAllPlayers("points", clienttranslate('${player_name} gets ${nbr} hearts and looses ${nbr} points'), array (<br />
'player_id' => $player_id,'player_name' => $players [$player_id] ['player_name'],<br />
'nbr' => $heart_number ));<br />
} else {<br />
// No point lost (just notify)<br />
self::notifyAllPlayers("points", clienttranslate('${player_name} did not get any hearts'), array (<br />
'player_id' => $player_id,'player_name' => $players [$player_id] ['player_name'] ));<br />
}<br />
}<br />
$newScores = self::getCollectionFromDb("SELECT player_id, player_score FROM player", true );<br />
self::notifyAllPlayers( "newScores", '', array( 'newScores' => $newScores ) );<br />
<br />
///// Test if this is the end of the game<br />
foreach ( $newScores as $player_id => $score ) {<br />
if ($score <= -100) {<br />
// Trigger the end of the game !<br />
$this->gamestate->nextState("endGame");<br />
return;<br />
}<br />
}<br />
<br />
<br />
$this->gamestate->nextState("nextHand");<br />
}<br />
</pre><br />
<br />
<br />
So it should more less work now, including end of game condition. Try to play it!<br />
<br />
== Additional stuff ==<br />
<br />
The following things were not implemented and can add them yourself by looking at the code of original hearts game:<br />
<br />
* Remove debug code from setupNewGame to deal cards, cards are now dealt in stNewHand state handler<br />
* Rule checking and rule enforcements in playCard function<br />
* Start scoring with 100 points each and end when <= 0<br />
* Fix scoring rules with Q of spades and 26 point reverse scoring<br />
* First player one with 2 club<br />
* Add progress handling<br />
* Add statistics<br />
* Add card exchange states<br />
* Add game option to start with 75 points instead of 100</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Tips_barbu&diff=4594
Tips barbu
2020-06-03T08:50:38Z
<p>Cpasbanal: Created page with "No Hearts and No Barbu (King of Hearts) are generally best bid on hands with many hearts. A hand with long hearts will be short in the other suits, and will thus get more oppo..."</p>
<hr />
<div>No Hearts and No Barbu (King of Hearts) are generally best bid on hands with many hearts. A hand with long hearts will be short in the other suits, and will thus get more opportunities to discard.<br />
<br />
In No King of Hearts, a player who holds the King and is confident of not taking it herself can chose that hand.<br />
<br />
The total score for No Tricks is -13 or -8, but this is in effect the smallest contract, as the tricks tend to be spread around. It is therefore a suitable hand to keep to last, as playing it with an unsuitable hand is rarely a total disaster. In playing No Tricks, ingenious sacrifice plays (such as are skillful in ramsch and other negative games) are not appropriate. It is better to keep your head down and avoid each trick as it comes round.<br />
<br />
Choose wisefuly the Salade and Anti-Salade because they can be worth a lot, especially the Barbu is worth a lot.<br />
<br />
No King of Hearts only scores -12 or -13, but is really a big hand. This is because the penalty all comes in one lump.<br />
<br />
In Domino, Aces and Kings are liabilities. A suite of cards, like 3-4-5 can be powerful because you can hold this type (provided you don't have too high cards like Queen, King or Ace in this type).</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Gamehelpbarbu&diff=4564
Gamehelpbarbu
2020-06-02T19:40:34Z
<p>Cpasbanal: /* Playing the game */</p>
<hr />
<div>== Goal == <br />
<br />
Your goal is to loose '''the least point''' you can.<br />
<br />
== Playing the game == <br />
<br />
The game is made of multiple hands (number of hands depends on the options you play):<br />
# avoid making '''tricks''' (every trick counts -1 point)<br />
# avoid taking '''hearts''' in tricks (every heart card counts -1 point)<br />
# avoid taking '''queens''' in tricks (each queeen counts -3 points for a 52-cards deck and -2 points when playing with a 32-cards deck)<br />
# avoir taking the '''King of Heartss''' (the ''Barbu'') in your tricks (Barbu counts -12 point for a 52-cards deck and -8 when playing with a 32-cards deck)<br />
<br />
An extra hand (optional), the '''Salade''' when everything counts, so avoid making tricks, taking hearts, taking queens or the King of Hearts. All the point counts so Queen of heart is worth -4 points (with 52-cards deck), Barbu is worth -13 points (still 52-cards deck)...<br />
<br />
Another extra hand (optional), the '''Anti-salade''' counts all the same points (tricks, hearts, queens, Barbu) with '''positive score'''.<br />
<br />
== Option "Domino" (Klondike) ==<br />
<br />
You can play an extra hand: the '''Domino'''.<br />
<br />
During this hand, players have to play cards in a suite, starting from 2 (or 7 when playing 32-cards deck) to the Ace, for each of the 4 types.<br />
<br />
When a player plays an Ace, he/she can play again. If a player can't play, he/she passes. But you can't pass if you can play any card (even if you wanted to).<br />
<br />
Domino also counts points positively.<br />
<br />
If you play with a 52-cards deck, the first player with no card left wins 25 points, the second 15 points, the third 5 points. The other players score nothing.<br />
<br />
If you play with a 32-cards deck, the first player with no card left wins 20 points, the second 10 points. The other players score nothing.<br />
<br />
== 3 or 5 players ==<br />
<br />
The game is recommended to play at 4 players but you can play at 3 or 5 players. Some cards are removed from the game so that everyone has the same quantity of cards in hand. Here are the cards removed:<br />
<br />
{| class="wikitable"<br />
! '''Number of players'''<br />
! 32-cards deck<br />
! 52-cards deck<br />
|-<br />
|'''3 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds<br />
|-<br />
|'''4 joueurs'''<br />
|nothing<br />
|nothing<br />
|-<br />
|'''5 joueurs'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds and 2 clubs<br />
|}</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Gamehelpbarbu&diff=4563
Gamehelpbarbu
2020-06-02T19:40:23Z
<p>Cpasbanal: /* 3 or 5 players */ corrected typo</p>
<hr />
<div>== Goal == <br />
<br />
Your goal is to loose '''the least point''' you can.<br />
<br />
== Playing the game == <br />
<br />
The game is made of multiple hands (number of hands depends on the options you play):<br />
# avoid making '''tricks''' (every trick counts -1 point)<br />
# avoid taking '''hearts''' in tricks (every heart card counts -1 point)<br />
# avoid taking '''queens''' in tricks (each queeen counts -3 points for a 52-cards deck and -2 points when playing with a 32-cards deck)<br />
# avoir taking the '''King of Heartss''' (the ''Barbu'') in your tricks (Barbu counts -12 point for a 52-cards deck and -8 when playing with a 32-cards deck)<br />
<br />
An extra hand (optional), the '''Salade''' when everything counts, so avoid making tricks, taking hearts, taking queens or the King of Hearts. All the point counts so Queen of heart is worth -4 points (with 52-cards deck), Barbu is worth -13 points (still 52-cards deck)...<br />
<br />
Another extra hand (optional), the '''Anti-salade''' counts all the same points (tricks, hearts, queens, Barbu) with '''positive score'''.<br />
<br />
<br />
== Option "Domino" (Klondike) ==<br />
<br />
You can play an extra hand: the '''Domino'''.<br />
<br />
During this hand, players have to play cards in a suite, starting from 2 (or 7 when playing 32-cards deck) to the Ace, for each of the 4 types.<br />
<br />
When a player plays an Ace, he/she can play again. If a player can't play, he/she passes. But you can't pass if you can play any card (even if you wanted to).<br />
<br />
Domino also counts points positively.<br />
<br />
If you play with a 52-cards deck, the first player with no card left wins 25 points, the second 15 points, the third 5 points. The other players score nothing.<br />
<br />
If you play with a 32-cards deck, the first player with no card left wins 20 points, the second 10 points. The other players score nothing.<br />
<br />
== 3 or 5 players ==<br />
<br />
The game is recommended to play at 4 players but you can play at 3 or 5 players. Some cards are removed from the game so that everyone has the same quantity of cards in hand. Here are the cards removed:<br />
<br />
{| class="wikitable"<br />
! '''Number of players'''<br />
! 32-cards deck<br />
! 52-cards deck<br />
|-<br />
|'''3 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds<br />
|-<br />
|'''4 joueurs'''<br />
|nothing<br />
|nothing<br />
|-<br />
|'''5 joueurs'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds and 2 clubs<br />
|}</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Gamehelpbarbu&diff=4562
Gamehelpbarbu
2020-06-02T19:40:02Z
<p>Cpasbanal: Created page with "== Goal == Your goal is to loose '''the least point''' you can. == Playing the game == The game is made of multiple hands (number of hands depends on the options you play..."</p>
<hr />
<div>== Goal == <br />
<br />
Your goal is to loose '''the least point''' you can.<br />
<br />
== Playing the game == <br />
<br />
The game is made of multiple hands (number of hands depends on the options you play):<br />
# avoid making '''tricks''' (every trick counts -1 point)<br />
# avoid taking '''hearts''' in tricks (every heart card counts -1 point)<br />
# avoid taking '''queens''' in tricks (each queeen counts -3 points for a 52-cards deck and -2 points when playing with a 32-cards deck)<br />
# avoir taking the '''King of Heartss''' (the ''Barbu'') in your tricks (Barbu counts -12 point for a 52-cards deck and -8 when playing with a 32-cards deck)<br />
<br />
An extra hand (optional), the '''Salade''' when everything counts, so avoid making tricks, taking hearts, taking queens or the King of Hearts. All the point counts so Queen of heart is worth -4 points (with 52-cards deck), Barbu is worth -13 points (still 52-cards deck)...<br />
<br />
Another extra hand (optional), the '''Anti-salade''' counts all the same points (tricks, hearts, queens, Barbu) with '''positive score'''.<br />
<br />
<br />
== Option "Domino" (Klondike) ==<br />
<br />
You can play an extra hand: the '''Domino'''.<br />
<br />
During this hand, players have to play cards in a suite, starting from 2 (or 7 when playing 32-cards deck) to the Ace, for each of the 4 types.<br />
<br />
When a player plays an Ace, he/she can play again. If a player can't play, he/she passes. But you can't pass if you can play any card (even if you wanted to).<br />
<br />
Domino also counts points positively.<br />
<br />
If you play with a 52-cards deck, the first player with no card left wins 25 points, the second 15 points, the third 5 points. The other players score nothing.<br />
<br />
If you play with a 32-cards deck, the first player with no card left wins 20 points, the second 10 points. The other players score nothing.<br />
<br />
== 3 or 5 playerss ==<br />
<br />
The game is recommended to play at 4 players but you can play at 3 or 5 players. Some cards are removed from the game so that everyone has the same quantity of cards in hand. Here are the cards removed:<br />
<br />
{| class="wikitable"<br />
! '''Number of players'''<br />
! 32-cards deck<br />
! 52-cards deck<br />
|-<br />
|'''3 players'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds<br />
|-<br />
|'''4 joueurs'''<br />
|nothing<br />
|nothing<br />
|-<br />
|'''5 joueurs'''<br />
|7 diamonds and 7 clubs<br />
|2 diamonds and 2 clubs<br />
|}</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Main_game_logic:_yourgamename.game.php&diff=4406
Main game logic: yourgamename.game.php
2020-05-20T10:30:01Z
<p>Cpasbanal: /* Only "winners" and "losers */ typo</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
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.<br />
<br />
== File Structure ==<br />
<br />
The details of how the file is structured are described directly with comments in the code skeleton provided to you.<br />
<br />
Here is the basic structure:<br />
<br />
* EmptyGame (constructor): where you define global variables.<br />
* setupNewGame: initial setup of the game.<br />
* getAllDatas: where you retrieve all game data during a complete reload of the game.<br />
* getGameProgression: where you compute the game progression indicator.<br />
* Utility functions: your utility functions.<br />
* Player actions: the entry points for players actions. <br />
* 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]).<br />
* 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]).<br />
* zombieTurn: what to do it's the turn of a zombie player.<br />
<br />
== Accessing player information ==<br />
<br />
'''Important''': In the following methods, be mindful of the difference between the "active" player and the "current" player. The '''active''' player is the player whose turn it is - not necessarily the player who sent a request! The '''current''' player is the player who sent the request and will see the results returned by your methods: not necessarily the player whose turn it is!<br />
<br />
<br />
; getPlayersNumber()<br />
: Returns the number of players playing at the table<br />
: Note: doesn't work in setupNewGame (use count($players) instead).<br />
<br />
; getActivePlayerId()<br />
: Get the "active_player", whatever what is the current state type.<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multiplayer"<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; getActivePlayerName()<br />
: Get the "active_player" name<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
; loadPlayersBasicInfos()<br />
: Get an associative array with generic data about players (ie: not game specific data).<br />
: The key of the associative array is the player id. The returned table is cached, so ok to call multiple times without performance concerns.<br />
: The content of each value is:<br />
: * player_name - the name of the player<br />
: * player_color (ex: ff0000) - the color code of the player<br />
: * player_no - the position of the player at the start of the game in natural table order, i.e. 1,2,3<br />
<br />
; getCurrentPlayerId()<br />
: Get the "current_player". The current player is the one from which the action originated (the one who sent the request).<br />
: '''Be careful''': This is not necessarily the active player!<br />
: In general, you shouldn't use this method, unless you are in "multiplayer" state.<br />
: '''Very important''': in your setupNewGame and zombieTurn function, you must never use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message (these actions are triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to these actions).<br />
<br />
; getCurrentPlayerName()<br />
: Get the "current_player" name<br />
: Be careful using this method (see above).<br />
<br />
; getCurrentPlayerColor()<br />
: Get the "current_player" color<br />
: Be careful using this method (see above).<br />
<br />
; isCurrentPlayerZombie()<br />
: Check the "current_player" zombie status. If true, player is zombie, i.e. left or was kicked out of the game.<br />
<br />
== Accessing the database ==<br />
<br />
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.<br />
<br />
'''IMPORTANT'''<br />
<br />
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.<br />
<br />
; DbQuery( $sql )<br />
: This is the generic method to access the database.<br />
: It can execute any type of SELECT/UPDATE/DELETE/REPLACE query on the database.<br />
: You should use it for UPDATE/DELETE/REPLACE queries. For SELECT queries, the specialized methods below are much better.<br />
<br />
; getUniqueValueFromDB( $sql )<br />
: Returns a unique value from DB or null if no value is found.<br />
: $sql must be a SELECT query.<br />
: Raise an exception if more than 1 row is returned.<br />
<br />
; getCollectionFromDB( $sql, $bSingleValue=false )<br />
: Returns an associative array of rows for a sql SELECT query.<br />
: The key of the resulting associative array is the first field specified in the SELECT query.<br />
: The value of the resulting associative array if an associative array with all the field specified in the SELECT query and associated values.<br />
: First column must be a primary or alternate key.<br />
: The resulting collection can be empty.<br />
: If you specified $bSingleValue=true and if your SQL query request 2 fields A and B, the method returns an associative array "A=>B"<br />
<br />
Example 1:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
1234 => array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
1235 => array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getCollectionFromDB( "SELECT player_id id, player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
1234 => 'myuser0',<br />
1235 => 'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getNonEmptyCollectionFromDB( $sql )<br />
: Idem than previous one, but raise an exception if the collection is empty<br />
<br />
; getObjectFromDB( $sql )<br />
: Returns one row for the sql SELECT query as an associative array or null if there is no result<br />
: Raise an exception if the query return more than one row<br />
<br />
Example:<br />
<pre><br />
self::getObjectFromDB( "SELECT player_id id, player_name name, player_score score FROM player WHERE player_id='$player_id'" );<br />
<br />
Result:<br />
array(<br />
'id'=>1234, 'name'=>'myuser0', 'score'=>1 <br />
)<br />
</pre><br />
<br />
; getNonEmptyObjectFromDB( $sql )<br />
: Idem than previous one, but raise an exception if no row is found<br />
<br />
; getObjectListFromDB( $sql, $bUniqueValue=false )<br />
: Return an array of rows for a sql SELECT query.<br />
: the result if the same than "getCollectionFromDB" except that the result is a simple array (and not an associative array).<br />
: The result can be empty.<br />
: If you specified $bUniqueValue=true and if your SQL query request 1 field, the method returns directly an array of values.<br />
<br />
Example 1:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_id id, player_name name, player_score score FROM player" );<br />
<br />
Result:<br />
array(<br />
array( 'id'=>1234, 'name'=>'myuser0', 'score'=>1 ),<br />
array( 'id'=>1235, 'name'=>'myuser1', 'score'=>0 )<br />
)<br />
</pre><br />
Example 2:<br />
<pre><br />
self::getObjectListFromDB( "SELECT player_name name FROM player", true );<br />
<br />
Result:<br />
array(<br />
'myuser0',<br />
'myuser1'<br />
)<br />
<br />
</pre><br />
<br />
; getDoubleKeyCollectionFromDB( $sql, $bSingleValue=false )<br />
: Return an associative array of associative array, from a SQL SELECT query.<br />
: First array level correspond to first column specified in SQL query.<br />
: Second array level correspond to second column specified in SQL query.<br />
: If bSingleValue = true, keep only third column on result<br />
<br />
<br />
; DbGetLastId()<br />
: Return the PRIMARY key of the last inserted row (see PHP mysql_insert_id function).<br />
<br />
; DbAffectedRow()<br />
: Return the number of row affected by the last operation<br />
<br />
; escapeStringForDB( $string )<br />
: You must use this function on every string type data in your database that contains unsafe data.<br />
: (unsafe = can be modified by a player).<br />
: This method makes sure that no SQL injection will be done through the string used.<br />
: Note: if you using standard types in ajax actions, like AT_alphanum it is sanitized before arrival,<br />
: this is only needed if you manage to get unchecked string, like in the games where user has to enter text as a response.<br />
<br />
<pre><br />
<br />
<br />
</pre><br />
<br />
Note: see Editing [[Game database model: dbmodel.sql]] to know how to define your database model.<br />
<br />
== Use globals ==<br />
<br />
Sometimes, you want a single global integer value for your game, and you don't want to create a DB table specifically for it.<br />
<br />
You can do this with the BGA framework "global." Your value will be stored in the "global" table in the database, and you can access it with simple methods.<br />
<br />
'''initGameStateLabels'''<br />
<br />
This method should be located at the beginning of ''yourgamename.php.'' This is where you define the globals used in your game logic, by assigning them IDs.<br />
<br />
You can define up to 79 globals, with IDs from 10 to 89 (inclusive). You must '''not''' use globals outside this range, as those values are used by other components of the framework.<br />
<br />
<pre><br />
self::initGameStateLabels( array( <br />
"my_first_global_variable" => 10,<br />
"my_second_global_variable" => 11<br />
) );<br />
</pre><br />
<br />
'''setGameStateInitialValue( $value_label, $value_value )'''<br />
<br />
Initialize your global value. Must be called before any use of your global, so you should call this method from your "setupNewGame" method.<br />
<br />
'''getGameStateValue( $value_label )'''<br />
<br />
Retrieve the current value of a global.<br />
<br />
'''setGameStateValue( $value_label, $value_value )'''<br />
<br />
Set the current value of a global.<br />
<br />
'''incGameStateValue( $value_label, $increment )'''<br />
<br />
Increment the current value of a global. If increment is negative, decrement the value of the global.<br />
<br />
Return the final value of the global.<br />
<br />
== Game states and active players ==<br />
<br />
<br />
=== Activate player handling ===<br />
<br />
; $this->activeNextPlayer()<br />
: Make the next player active in the natural player order.<br />
: Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->activePrevPlayer()<br />
: Make the previous player active (in the natural player order).<br />
: Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->gamestate->changeActivePlayer( $player_id )<br />
: You can call this method to make any player active.<br />
: Note: you CANT use this method in a "activeplayer" or "multipleactiveplayer" state. You must use a "game" type game state for this.<br />
<br />
; $this->getActivePlayerId()<br />
: Return the "active_player" id<br />
: Note: it does NOT mean that this player is active right now, because state type could be "game" or "multiplayer"<br />
: Note: avoid using this method in a "multiplayer" state because it does not mean anything.<br />
<br />
=== Multiactivate player handling ===<br />
<br />
; $this->gamestate->setAllPlayersMultiactive()<br />
: All playing players are made active. Update notification is sent to all players (triggers onUpdateActionButtons).<br />
: 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.<br />
<br />
; $this->gamestate->setAllPlayersNonMultiactive( $next_state )<br />
: All playing players are made inactive. Transition to next state<br />
<br />
; $this->gamestate->setPlayersMultiactive( $players, $next_state, $bExclusive = false )<br />
: Make a specific list of players active during a multiactive gamestate. Update notification is sent to all players who's state changed.<br />
: "players" is the array of player id that should be made active.<br />
: 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 "$players" array<br />
<br />
: In case "players" is empty, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
<br />
; $this->gamestate->setPlayerNonMultiactive( $player_id, $next_state )<br />
: During a multiactive game state, make the specified player inactive.<br />
: Usually, you call this method during a multiactive game state after a player did his action.<br />
: If this player was the last active player, the method trigger the "next_state" transition to go to the next game state.<br />
: returns true if state transition happened, false otherwise<br />
<br />
; $this->gamestate->updateMultiactiveOrNextState( $next_state_if_none )<br />
: 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.<br />
Example: you have player teams and you want to activate all players in one team<br />
<pre><br />
$sql = "UPDATE player SET player_is_multiactive='0'";<br />
self::DbQuery( $sql );<br />
$sql = "UPDATE player SET player_is_multiactive='1' WHERE player_id='$player_id' AND player_team='$team_no'";<br />
self::DbQuery( $sql );<br />
<br />
$this->gamestate->updateMultiactiveOrNextState( 'error' );<br />
</pre><br />
<br />
; $this->gamestate->getActivePlayerList()<br />
: With this method you can retrieve the list of the active player at any time.<br />
: During a "game" type gamestate, it will return a void array.<br />
: During a "activeplayer" type gamestate, it will return an array with one value (the active player id).<br />
: During a "multipleactiveplayer" type gamestate, it will return an array of the active players id.<br />
: Note: you should only use this method in the latter case.<br />
<br />
=== States functions ===<br />
; $this->gamestate->nextState( $transition )<br />
: Change current state to a new state. Important: the $transition parameter 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.<br />
<br />
; $this->checkAction( $actionName, $bThrowException=true )<br />
: Check if an action is valid for the current game state, and optionally, throw an exception if it isn't.<br />
: The action is valid if it is listed in the "possibleactions" array for the current game state (see game state description).<br />
: This method MUST be the first one called in ALL your PHP methods that handle player actions, in order to make sure a player doesn't perform an action not allowed by the rules at the point in the game.<br />
: If "bThrowException" is set to "false", the function returns '''false''' in case of failure instead of throwing an exception. This is useful when several actions are possible, in order to test each of them without throwing exceptions.<br />
<br />
; $this->gamestate->checkPossibleAction( $action )<br />
: (rarely used)<br />
: This works exactly like "checkAction" (above), except that it does NOT check if the current player is active.<br />
: This is used specifically in certain game states when you want to authorize additional actions for players that are not active at the moment.<br />
: Example: in ''Libertalia'', you want to authorize players to change their mind about the card played. They are of course not active at the time they change their mind, so you cannot use "checkAction"; use "checkPossibleAction" instead.<br />
<br />
; $this->gamestate->state()<br />
: Get an associative array of current game state attributes, see [[Your game state machine: states.inc.php]] for state attributes.<br />
$state=$this->gamestate->state(); if( $state['name'] == 'myGameState' ) {...}<br />
<br />
== Players turn order ==<br />
<br />
'''getNextPlayerTable()'''<br />
<br />
Return an associative array which associate each player with the next player around the table.<br />
<br />
In addition, key 0 is associated to the first player to play.<br />
<br />
Example: if three player with ID 1, 2 and 3 are around the table, in this order, the method returns:<br />
<pre><br />
array( <br />
1 => 2, <br />
2 => 3, <br />
3 => 1, <br />
0 => 1 <br />
);<br />
</pre><br />
<br />
'''getPrevPlayerTable()'''<br />
<br />
Same as above, but the associative array associate the previous player around the table.<br />
<br />
'''getPlayerAfter( $player_id )'''<br />
<br />
Get player playing after given player in natural playing order.<br />
<br />
'''getPlayerBefore( $player_id )'''<br />
<br />
Get player playing before given player in natural playing order.<br />
<br />
Note: There is no API to modify this order, if you have custom player order you have to maintain it in your database<br />
and have custom function to access it.<br />
<br />
== Notify players ==<br />
<br />
To understand notifications, please read [http://www.slideshare.net/boardgamearena/the-bga-framework-at-a-glance The BGA Framework at a glance] first.<br />
<br />
'''IMPORTANT'''<br />
<br />
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.<br />
Notifications sent between the game start (setupNewGame) and the end of the "action" method of the first active state will never reach their destination.<br />
<br />
'''notifyAllPlayers( $notification_type, $notification_log, $notification_args )'''<br />
<br />
Send a notification to all players of the game.<br />
<br />
* notification_type:<br />
A string that defines the type of your notification.<br />
<br />
Your game interface Javascript logic will use this to know what is the type of the received notification (and to trigger the corresponding method).<br />
<br />
* notification_log:<br />
A string that defines what is to be displayed in the game log.<br />
<br />
You can use an empty string here (""). In this case, nothing is displayed in the game log.<br />
<br />
If you define a real string here, you should use "clienttranslate" method to make sure it can be translate.<br />
<br />
You can use arguments in your notification_log strings, that refers to values defines in the "notification_args" argument (see below). <br />
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.<br />
<br />
<br />
<br />
* notification_args:<br />
The arguments of your notifications, as an associative array.<br />
<br />
This array will be transmitted to the game interface logic, in order the game interface can be updated.<br />
<br />
Complete notifyAllPlayers example (from "Reversi"):<br />
<br />
<pre><br />
self::notifyAllPlayers( "playDisc", clienttranslate( '${player_name} plays a disc and turns over ${returned_nbr} disc(s)' ),<br />
array(<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'returned_nbr' => count( $turnedOverDiscs ),<br />
'x' => $x,<br />
'y' => $y<br />
) );<br />
</pre><br />
<br />
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.<br />
<br />
'''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.<br />
<br />
'''Important''': this array is serialized to be sent to the browsers, and will be saved with the notification to be able to replay the game later. If it is too big, it can make notifications slower / less reliable, and replay archives very big (to the point of failing). So as a general rule, you should send only the minimum of information necessary to update the client interface with no overhead in order to keep the notifications as light as possible.<br />
<br />
Note: you CAN use some HTML inside your notification log, however it not recommended for many reasons:<br />
* Its bad architecture, ui elements leak into server now you have to manage ui in many places<br />
* If you decided to change something in ui in future version, old games reply and tutorials may not work, since they use stored notifications<br />
* 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)<br />
* Its more data to transfer and store in db<br />
* 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.<br />
<br />
If you still want to have pretty pictures in the log check this [[BGA_Studio_Cookbook#Inject_images_and_styled_html_in_the_log]].<br />
<br />
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<br />
an array itself, which contains 'log' and 'args', i.e.<br />
<br />
$this->notifyAllPlayers('playerLog',clienttranslate('Game moves ${token_name_rec}'),<br />
['token_name_rec'=>['log'=>'${token_name} #${token_number}',<br />
'args'=> ['token_name'=>clienttranslate('Boo'), 'token_number'=>$number, 'i18n'=>['token_name'] ]<br />
]<br />
]);<br />
<br />
<br />
<br />
'''notifyPlayer( $player_id, $notification_type, $notification_log, $notification_args )'''<br />
<br />
Same as above, except that the notification is sent to one player only.<br />
<br />
This method must be used each time some private information must be transmitted to a player.<br />
<br />
Important: the variable for player name must be ${player_name} in order to be highlighted with the player color in the game log<br />
<br />
== About random and randomness ==<br />
<br />
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.<br />
<br />
Here's are a list of techniques you should use in these situations, from the best to the worst.<br />
<br />
=== Dices and bga_rand ===<br />
<br />
'''bga_rand( min, max )''' <br />
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.<br />
<br />
This is the preferred function you should use, because we are updating it when a better method is introduced.<br />
<br />
At now, bga_rand is based on the PHP function "random_int", which ensure a cryptographic level of randomness.<br />
<br />
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).<br />
<br />
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".<br />
<br />
=== shuffle and cards shuffling ===<br />
<br />
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.<br />
<br />
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").<br />
<br />
=== Other methods ===<br />
<br />
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.<br />
<br />
== Game statistics ==<br />
<br />
There are 2 types of statistics:<br />
* a "player" statistic is a statistic associated to a player<br />
* a "table" statistics is a statistic not associated to a player (global statistic for this game).<br />
<br />
See [[Game statistics: stats.inc.php]] to see how you defines statistics for your game.<br />
<br />
<br />
'''initStat( $table_or_player, $name, $value, $player_id = null )'''<br />
<br />
Create a statistic entry with a default value.<br />
This method must be called for each statistics of your game, in your setupNewGame method.<br />
<br />
'$table_or_player' must be set to "table" if this is a table statistics, or "player" if this is a player statistics.<br />
<br />
'$name' is the name of your statistics, as it has been defined in your stats.inc.php file.<br />
<br />
'$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.<br />
<br />
<br />
'''setStat( $value, $name, $player_id = null )'''<br />
<br />
Set a statistic $name to $value.<br />
<br />
If "$player_id" is not specified, setStat consider it is a TABLE statistic.<br />
<br />
If "$player_id" is specified, setStat consider it is a PLAYER statistic.<br />
<br />
<br />
'''incStat( $delta, $name, $player_id = null )'''<br />
<br />
Increment (or decrement) specified statistic value by $delta value. Same behavior as setStat function.<br />
<br />
<br />
'''getStat( $name, $player_id = null )'''<br />
<br />
Return the value of statistic specified by $name. Useful when creating derivative statistics such as average.<br />
<br />
== Translations ==<br />
<br />
See [[Translations]]<br />
<br />
== Manage player scores and Tie breaker ==<br />
<br />
=== Normal scoring ===<br />
<br />
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...<br />
<br />
During the game, you update player's score directly by updating "player_score" field of "player" table in database.<br />
<br />
Examples:<br />
<pre><br />
<br />
// +2 points to active player<br />
self::DbQuery( "UPDATE player SET player_score=player_score+2 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
// Set score of active player to 5<br />
self::DbQuery( "UPDATE player SET player_score=5 WHERE player_id='".self::getActivePlayerId()."'" );<br />
<br />
</pre><br />
<br />
Note: don't forget to notify the client side in order the score control can be updated accordingly.<br />
<br />
=== Tie breaker ===<br />
<br />
Tie breaker is used when two players get the same score at the end of a game.<br />
<br />
Tie breaker is using "player_score_aux" field of "player" table. It is updated exactly like the "player_score" field.<br />
<br />
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.<br />
<br />
When you are using "player_score_aux" functionality, you must describe the formula to use in your gameinfos.inc.php file like this:<br />
<br />
<pre><br />
'tie_breaker_description' => totranslate("Describe here your tie breaker formula"),<br />
</pre><br />
<br />
This description will be used as a tooltip to explain to players how this auxiliary score has been calculated.<br />
<br />
=== Co-operative game ===<br />
<br />
To make everyone lose in full-coop game:<br />
<br />
Add the following in gameinfos.inc.php :<br />
'is_coop' => 1, // full cooperative<br />
<br />
And score zero to everyone.<br />
<br />
=== Semi-coop ===<br />
<br />
If the game is not full-coop, then everyone lose = everyone is tie. I.e. set score to 0 to everybody.<br />
<br />
=== Only "winners" and "losers" ===<br />
<br />
For some games, there is only a group (or a single) "winner", and everyone else is a "loser", with no "end of game rank" (1st, 2nd, 3rd...).<br />
<br />
Examples:<br />
* Coup<br />
* Not Alone<br />
* Werewolves<br />
* Quantum<br />
<br />
In this case:<br />
* Set the scores so that the winner has the best score, and the other players have the same (lower) score.<br />
* Add the following lines to gameinfos.php:<br />
<br />
<pre><br />
// 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 <br />
// The game end result will display "Winner" for the 1st player and "Loser" for all other players<br />
'losers_not_ranked' => true,<br />
</pre><br />
<br />
Werewolves and Coup are implemented like this, as you can see here:<br />
* https://boardgamearena.com/#!gamepanel?game=werewolves&section=lastresults<br />
* https://boardgamearena.com/#!gamepanel?game=coupcitystate&section=lastresults<br />
<br />
Adding this has the following effects:<br />
* On game results for this game, "Winner" or "Loser" is going to appear instead of the usual "1st, 2nd, 3rd, ...".<br />
* When a game is over, the result of the game will be "End of game: Victory" or "End of game: Defeat" depending on the result of the CURRENT player (instead of the usual "Victory of XXX").<br />
* When calculating ELO points, if there is at least one "Loser", no "victorious" player can lose ELO points, and no "losing" player can win ELO point. Usually it may happened because being tie with many players with a low rank is considered as a tie and may cost you points. If losers_not_ranked is set, we prevent this behavior and make sure you only gain/loss ELO when you get the corresponding results.<br />
<br />
Important: this SHOULD NOT be used for cooperative games (see is_coop parameter), or for 2 players games (it makes no sense in this case).<br />
<br />
=== Solo ===<br />
<br />
If game supports solo variant, a negative score means defeat, a positive score means victory.<br />
<br />
=== Player elimination ===<br />
<br />
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.<br />
<br />
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").<br />
<br />
Usage:<br />
<br />
* Player to eliminate should NOT be active anymore (preferably use the feature in a "game" type game state).<br />
* In your PHP code:<br />
self::eliminatePlayer( <player_to_eliminate_id> );<br />
* 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.<br />
* 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.<br />
<br />
=== Scoring Helper functions ===<br />
<br />
// get score<br />
function dbGetScore($player_id) {<br />
return $this->getUniqueValueFromDB("SELECT player_score FROM player WHERE player_id='$player_id'");<br />
}<br />
<br />
// set score<br />
function dbSetScore($player_id, $count) {<br />
$this->DbQuery("UPDATE player SET player_score='$count' WHERE player_id='$player_id'");<br />
}<br />
<br />
// set aux score (tie breaker)<br />
function dbSetAuxScore($player_id, $score) {<br />
$this->DbQuery("UPDATE player SET player_score_aux=$score WHERE player_id='$player_id'");<br />
}<br />
<br />
// increment score (can be negative too)<br />
function dbIncScore($player_id, $inc) {<br />
$count = $this->dbGetScore($player_id);<br />
if ($inc != 0) {<br />
$count += $inc;<br />
$this->dbSetScore($player_id, $count);<br />
}<br />
return $count;<br />
}<br />
<br />
== Reflexion time ==<br />
<br />
; function giveExtraTime( $player_id, $specific_time=null )<br />
: Give standard extra time to this player.<br />
: Standard extra time depends on the speed of the game (small with "slow" game option, bigger with other options).<br />
: You can also specify an exact time to add, in seconds, with the "specified_time" argument (rarely used).<br />
<br />
<br />
== Undo moves ==<br />
<br />
Please read our [[BGA Undo policy]] before.<br />
<br />
<br />
'''Important''': Before using these methods, you must also add the following to your "gameinfos.inc.php" file, otherwise these methods are ineffective:<br />
'db_undo_support' => true<br />
<br />
<br />
; function undoSavepoint( )<br />
: Save the whole game situation inside an "Undo save point".<br />
: There is only ONE undo save point available (see BGA Undo policy).<br />
<br />
<br />
; function undoRestorePoint()<br />
: Restore the situation previously saved as an "Undo save point".<br />
: You must make sure that the active player is the same after and before the undoRestorePoint (ie: this is your responsibility to ensure that the player that is active when this method is called is exactly the same than the player that was active when the undoSavePoint method has been called).<br />
<br />
== Managing errors and exceptions ==<br />
<br />
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.<br />
<br />
; throw new BgaUserException ( $error_message)<br />
: Base class to notify a user error<br />
: You must throw this exception when a player wants to do something that he is not allowed to do.<br />
: The error message will be shown to the player as a "red message", so it must be translated.<br />
: Throwing such an exception is NOT considered a bug, so it is not traced in BGA error logs.<br />
<br />
Example from Gomoku:<br />
<pre><br />
throw new BgaUserException( self::_("There is already a stone on this intersection, you can't play there") );<br />
</pre><br />
<br />
<br />
; throw new BgaVisibleSystemException ( $error_message)<br />
: You must throw this exception when you detect something that is not supposed to happened in your code.<br />
: The error message is shown to the user as an "Unexpected error", in order that he can report it in the forum.<br />
: The error message is logged in BGA error logs. If it happens regularly, we will report it to you.<br />
<br />
; throw new BgaSystemException ( $error_message)<br />
: 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.<br />
: 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.<br />
<br />
== Zombie mode ==<br />
<br />
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.<br />
<br />
While developing your zombie mode, keep in mind that:<br />
* Do not refer to the rules, because this situation is not planned by the rules.<br />
* 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?<br />
* The idea is NOT to develop an artificial intelligence for the game.<br />
<br />
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.<br />
<br />
Each time a zombie player must play, your "zombieTurn" method is called.<br />
<br />
Parameters:<br />
* $state: the name of the current game state.<br />
* $active_player: the id of the active player.<br />
<br />
Most of the time, your zombieTurn method looks like this:<br />
<br />
<pre><br />
function zombieTurn( $state, $active_player )<br />
{<br />
$statename = $state['name'];<br />
<br />
if( $statename == 'myFirstGameState'<br />
|| $statename == 'my2ndGameState'<br />
|| $statename == 'my3rdGameState'<br />
....<br />
)<br />
{<br />
$this->gamestate->nextState( "zombiePass" );<br />
}<br />
else<br />
throw new BgaVisibleSystemException( "Zombie mode not supported at this game state: ".$statename );<br />
}<br />
</pre><br />
<br />
Note that in the example above, all corresponding game state should implement "zombiePass" as a transition.<br />
<br />
'''Very important''': your zombie code will be called when the player leaves the game. This action is triggered from the main site and propagated to the gameserver from a server, not from a browser. As a consequence, there is no current player associated to this action. In your zombieTurn function, you must '''never''' use getCurrentPlayerId() or getCurrentPlayerName(), otherwise it will fail with a "Not logged" error message.<br />
<br />
== Player color preferences ==<br />
<br />
<br />
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.<br />
<br />
Making your game compatible with colors preferences is very easy and requires only 1 line of PHP and 1 configuration change :<br />
<br />
On your gameinfos.inc.php file, add the following lines :<br />
<br />
// Favorite colors support : if set to "true", support attribution of favorite colors based on player's preferences (see reattributeColorsBasedOnPreferences PHP method)<br />
'favorite_colors_support' => true,<br />
<br />
Then, on your main <your_game>.game.php file, find the "reloadPlayersBasicInfos" call in your "setupNewGame" method and replace :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reloadPlayersBasicInfos();<br />
<br />
By :<br />
<br />
$sql .= implode( $values, ',' );<br />
self::DbQuery( $sql );<br />
self::reattributeColorsBasedOnPreferences( $players, array( /* LIST HERE THE AVAILABLE COLORS OF YOUR GAME INSTEAD OF THESE ONES */"ff0000", "008000", "0000ff", "ffa500", "773300" ) );<br />
self::reloadPlayersBasicInfos();<br />
<br />
<br />
The "reattributeColorsBasedOnPreferences" method reattributes all colors, taking into account players color preferences and available colors.<br />
<br />
Note that you must update the colors to indicate the colors available for your game.<br />
<br />
2 important remarks :<br />
* 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.<br />
* 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.<br />
<br />
Colours currently listed as a choice in preferences:<br />
<br />
* #ff0000 Red<br />
* #008000 Green<br />
* #0000ff Blue<br />
* #ffa500 Yellow<br />
* #000000 Black<br />
* #ffffff White<br />
* #e94190 Pink<br />
* #982fff Purple<br />
* #72c3b1 Cyan<br />
* #f07f16 Orange<br />
* #bdd002 Khaki green<br />
* #7b7b7b Gray<br />
<br />
== Debugging and Tracing ==<br />
<br />
To debug php code you can use some tracing functions available from the parent class such as debug, trace, error, warn, dump.<br />
<br />
self::debug("Ahh!");<br />
self::dump('my_var',$my_var);<br />
<br />
See [[Practical_debugging]] section for complete information about debugging interfaces and where to find logs.</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Tutorial_hearts&diff=4355
Tutorial hearts
2020-05-10T15:34:58Z
<p>Cpasbanal: /* Client - Server interactions */ grammar fix</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Introduction ==<br />
<br />
Using this tutorial, you can build a complete working game on the BGA environment: Hearts.<br />
<br />
Before you read this tutorial, you must:<br />
* Read the overall presentations of the BGA Framework ([[Studio|see here]]).<br />
* Know the rules for Hearts<br />
* Some-what know the languages used on BGA: PHP, SQL, HTML, CSS, Javascript<br />
* Set up your development environment [http://en.doc.boardgamearena.com/First_steps_with_BGA_Studio First Steps with BGA Studio]<br />
* As part of setup you have to have access to your ftp home folder in studio, which would have 'hearts' game source code. We will be using some resources of this game in this tutorial, so copy it over to local disk if you have not done so.<br />
<br />
If you are stuck or have question about this tutorial, post on [https://forum.boardgamearena.com/viewforum.php?f=12 BGA Developers forum]<br />
<br />
== Create your first game ==<br />
<br />
If you have not already, you have to create a project in BGA Studio. For this tutorial you can create a project heartsYOURNAME where<br />
YOURNAME is your developer login name. You can also re-use the project you have created for the "First Steps" tutorial above.<br />
With the initial skeleton of code provided, you can already start a game from the BGA Studio. <br />
<br />
1. Find and start the game in turn-based mode with 4 players. Make sure it works. <br />
<br />
2. Modify the text in heartsYOURNAME_heartsYOURNAME.tpl, reload the page in the browser and make sure your ftp sync works as expected.<br />
Note: if you have not setup auto-sync do it now, manually copying files is a no-starter.<br />
<br />
3. Express stop from settings menu (the gear icon).<br />
<br />
<i>Note: please do '''not''' use the hearts project code as a base. This tutorial assumes you started with a TEMPLATE project with no prior modifications. Using the hearts project as a base will be very confusing and you won't be able to follow all the steps.<br />
</i><br />
<br />
== Hook version control system ==<br />
<br />
For a real game, or even for this tutorial, we recommend committing the code to version control right from the start. You are going to find yourself in a situation where the game doesn't even start anymore and no way of debugging it, unless you have a way to revert. That is where version control becomes very handy. If you are not familiar with version control (e.g. [https://git-scm.com/docs/gittutorial git]) then at least back up your files after each major change. Start now.<br />
<br />
Code for this tutorial available is on github: https://github.com/elaskavaia/bga-heartsla<br />
<br />
Different revisions represent different steps along the process, starting from original template to a complete game.<br />
<br />
== Update game infos and box graphics ==<br />
<br />
Even it does not nothing yet, always start by making sure the game looks decent in the game selector, meaning it has nice box graphics and its information is correct. For that we need to edit [[Game_meta-information: gameinfos.inc.php|gameinfos.inc.php]].<br />
<br />
For a real game, you would go to [http://boardgamegeek.com BoardGameGeek], find the game, and use the information from BGG to fill in the gameinfos.<br />
<br />
So let's do that. Find "hearts" on BoardGameGeek. (Hint: Original release 1850 :))<br />
<br />
You can fill in the year of publishing and bgg id, put ''Public Domain'' under publisher, and a publisher id of 171 for public domain. And as designer and author you can just put your own name just for fun. Set number of players to 4.<br />
<br />
// Players configuration that can be played (ex: 2 to 4 players)<br />
'players' => array( 4 ), <br />
<br />
<br />
The next step is to replace '''game_box.png''' with nicer images. For this tutorial, just copy all the files from the img/ folder of the hearts/ template into the img/ directory of your project. Replace publisher.png with a nicer image: for example https://github.com/elaskavaia/bga-sharedcode/blob/master/img/publisher.png.<br />
<br />
Details about images can be found here: [[Game art: img directory]].<br />
<br />
Now an important step: you have to LOAD these files into the Studio website through the control panel. So go to Control Panel -> Manage Games -> heartsYOURNAME<br />
and press Reload for 'Reload game informations' and 'Reload game box image'<br />
<br />
Now try to start the game again. If you somehow introduced a syntax error in the gameinfos file it may not work (the game won't start).<br />
Always use the "Express Start" button to start the game. You should see a standard state prompt from the template. You should see 4 players on the right: testdude0 .. testdude3.<br />
To switch between them press the red arrow button near their names, it will open another tab. This way you don't need to login and logout from multiple accounts!<br />
<br />
<br />
<i>Note: if you had run the game before with less than 4 players there is a bug that will prevent you from running it with 4 only (if you did not run it before or run it with 4 players as instructed stop reading this note), to workaround revert back to original players array (i.e. 1,2,3,4), reload game options, then create a table with 4 players, exit that game table, then change gameoptions to 4 only as above, reload game options, create table again.</i><br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/4b3a73eeb5acae961ade18473af119e8ce8d1a8f]<br />
<br />
== Layout and Graphics ==<br />
<br />
In this section we will do graphics of the game, and main layout of the game.<br />
<br />
First copy a sprite with cards image from hearts img/cards.jpg into img/ folder of your project. Project hearts is mounted to your home directory on bga server.<br />
<br />
Edit .tpl to add some divs to represent player table and hand area<br />
<br />
<pre><br />
<div id="myhand_wrap" class="whiteblock"><br />
<h3>My Hand</h3><br />
<div id="myhand"><br />
</div><br />
</div><br />
</pre><br />
<br />
If you refresh you should see now white area with My Hand title.<br />
<br />
<br />
[[File:Heartsla-tpl2.png]]<br />
<br />
Now lets add a card into the hand, just so you can feel it. Edit .tpl and a playertablecard div inside a hand div<br />
<pre><br />
...<br />
<div id="myhand"><br />
<div class="playertablecard"></div><br />
</div><br />
...<br />
</pre><br />
<br />
Edit .css file<br />
<pre><br />
.playertablecard {<br />
display: inline-block;<br />
position: relative;<br />
margin-top: 5px;<br />
width: 72px;<br />
height: 96px;<br />
background-image: url('img/cards.jpg'); /* temp hack to see it */<br />
}<br />
</pre><br />
<br />
When you edit CSS remember that you have to FORCE-reload page, i.e. Ctrl-F5, otherwise its cached.<br />
<i>Same when you change existing graphics files</i>.<br />
<br />
You should see this:<br />
<br />
[[File:Heartsla-tpl3.png]]<br />
<br />
Awesome! Now lets do the rest of layout.<br />
<br />
There are few ways of how html could have been generated, you could have start with nothing and generate<br />
all by java script. Or you could have started with complete game markup in html and make java script just hide and move pieces around. BGA framework provides also a third way which is mix of both plus template engine to generate HTML using php. So lets do that.<br />
<br />
Change .tpl file to have this inside<br />
<pre><br />
<div id="playertables"><br />
<br />
<!-- BEGIN player --><br />
<div class="playertable whiteblock playertable_{DIR}"><br />
<div class="playertablename" style="color:#{PLAYER_COLOR}"><br />
{PLAYER_NAME}<br />
</div><br />
<div class="playertablecard" id="playertablecard_{PLAYER_ID}"><br />
</div><br />
</div><br />
<!-- END player --><br />
<br />
</div><br />
<br />
<div id="myhand_wrap" class="whiteblock"><br />
<h3>{MY_HAND}</h3><br />
<div id="myhand"><br />
</div><br />
</div><br />
</pre><br />
<br />
What we did is we added "block" player, it is marked up using html comments. {VAR} notation is used<br />
to inject variables and <br />
<pre><br />
<!-- BEGIN xxx --> <br />
inside <br />
<!-- END xxx --> <br />
</pre><br />
effectively allows us to do template loops.<br />
<br />
In .view.php insert this code after 'Place your code below' comment<br />
<br />
<br />
<pre><br />
$template = self::getGameName() . "_" . self::getGameName();<br />
<br />
$directions = array( 'S', 'W', 'N', 'E' );<br />
<br />
// this will inflate our player block with actual players data<br />
$this->page->begin_block($template, "player");<br />
foreach ( $players as $player_id => $info ) {<br />
$dir = array_shift($directions);<br />
$this->page->insert_block("player", array ("PLAYER_ID" => $player_id,<br />
"PLAYER_NAME" => $players [$player_id] ['player_name'],<br />
"PLAYER_COLOR" => $players [$player_id] ['player_color'],<br />
"DIR" => $dir ));<br />
}<br />
// this will make our My Hand text translatable<br />
$this->tpl['MY_HAND'] = self::_("My hand");<br />
</pre><br />
<br />
What it does is for each player we have it will replicate the html between <!-- BEGIN player --> and <!-- END player --> tags, substituting the variable denoted by {XXX}<br />
with the values you provide. The DIR variable in this case we pulling from directions array (where array_shift will take first element and remove it from the array).<br />
<br />
Reload. If everything went well you should see this:<br />
<br />
[[File:Heartsla-tpl4.png]]<br />
<br />
These are "tableau" areas for 4 players plus My hand visible only to one player.<br />
They are not exactly how we wanted them to be because we did not edit .css yet.<br />
<br />
Now edit .css, add these lines after import before our previous definition<br />
<br />
<pre><br />
/** Table layout **/<br />
<br />
#playertables {<br />
position: relative;<br />
width: 710px;<br />
height: 340px;<br />
}<br />
<br />
.playertablename {<br />
font-weight: bold;<br />
}<br />
<br />
.playertable {<br />
position: absolute;<br />
text-align: center;<br />
width: 180px;<br />
height: 130px;<br />
}<br />
<br />
.playertable_N {<br />
left: 50%;<br />
top: 0px;<br />
margin-left: -90px; /* half of 180 */<br />
}<br />
.playertable_S {<br />
left: 50%;<br />
bottom: 0px;<br />
margin-left: -90px; /* half of 180 */<br />
}<br />
.playertable_W {<br />
left: 0px;<br />
top: 50%;<br />
margin-top: -55px; /* half of 130 */<br />
}<br />
.playertable_E {<br />
right: 0px;<br />
top: 50%;<br />
margin-top: -55px; /* half of 130 */<br />
}<br />
</pre><br />
<br />
<br />
Now you force Reload and you should see this:<br />
[[File:Heartsla-tpl5.png]]<br />
<br />
This is almost all we need for graphics and layout, there are few tweaks left there but lets do some more heavy lifting now.<br />
<br />
<i>Note: if you did not see changes you may have not force reloaded, force means you use Ctrl+F5 or Cltr+Shift-R, if you don't "force" browser will use cached version of .css and images! Which is not what you just changed</i><br />
<br />
<br />
<i>Another Note: In general if you have auto-sync you don't need to reload if you change game.php file, you need normal reload if you change js, and force reload for css and images. If you changed state machine or database you likely need to restart the game.</i><br />
<br />
== Game Interface JS Stock ==<br />
<br />
The BGA framework provides a few out of the box classes to deal with cards. The client side<br />
contains a class called [[Stock]] and it can be used for any dynamic html "pieces" management that uses<br />
common sprite images. On the server side we will use the [[Deck]] class which we discuss later.<br />
<br />
If you open cards.jpg in an image viewer you will see that it is a "sprite" image - a 13x4 grid of images stitched together,<br />
which is a very efficient way to transport images. So we will use the Stock class to mark up these images and create<br />
"card" divs for us.<br />
<br />
First, we need to add '''ebg/stock''' as a dependency in the hearts.js file:<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <==== HERE<br />
],<br />
</pre><br />
<br />
Then add this to the Javascript contructor, this will define size of our cards<br />
<pre><br />
console.log('hearts constructor');<br />
this.cardwidth = 72;<br />
this.cardheight = 96;<br />
</pre><br />
<br />
The stock is initialized in the Javascript "setup" method like this:<br />
<pre><br />
// TODO: Set up your game interface here, according to "gamedatas"<br />
<br />
// Player hand<br />
this.playerHand = new ebg.stock(); // new stock object for hand<br />
this.playerHand.create( this, $('myhand'), this.cardwidth, this.cardheight );<br />
</pre><br />
<br />
As parameters of the "create" method, we provided the width/height of an item (a card), and the container div "myhand" - which is an id of "div" element from our .tpl file representing a player hand.<br />
<br />
<br />
Then, we must tell the stock what items it is going to display during its life: the 52 cards of a standard card game from a CSS sprite image named "cards.jpg" with all the cards arranged in 4 rows and 13 columns.<br />
<br />
Here's how we tell stock what item types to display:<br />
<pre><br />
this.playerHand.image_items_per_row = 13; // 13 images per row<br />
<br />
<br />
// Create cards types:<br />
for (var color = 1; color <= 4; color++) {<br />
for (var value = 2; value <= 14; value++) {<br />
// Build card type id<br />
var card_type_id = this.getCardUniqueId(color, value);<br />
this.playerHand.addItemType(card_type_id, card_type_id, g_gamethemeurl + 'img/cards.jpg', card_type_id);<br />
}<br />
}<br />
</pre><br />
<br />
And add this function to the utilities section<br />
<pre><br />
// Get card unique identifier based on its color and value<br />
getCardUniqueId : function(color, value) {<br />
return (color - 1) * 13 + (value - 2);<br />
},<br />
</pre><br />
<br />
Explanations:<br />
* At first, we tell the stock component that our CSS sprite contains 13 items per row. This way, it can find the correct image for each card type id.<br />
* Then for the 4x13 cards, we call the '''addItemType''' method that creates the type. The arguments are the type id, the weight of the card (for sorting purpose), the URL of our CSS sprite, and the position of our card image in the CSS sprite. It happens to be the same number in our case.<br />
<br />
Note: we need to generate a unique ID for each type of card based on its color and value. For that we create a function '''getCardUniqueId'''. The type is the unique identifier of the TYPE of the card, e.g., the queen of spades encoded as an integer. If our deck had 2 standard card decks we would have had 2 queens of spades; they would share the same type and the same image but would have different ids. NOTE: It's unfortunate that they named this '''getCardUniqueId'''; it should have been '''getCardUniqueType''', because it really isn't an id, but a TYPE of card. The type of the item should either be a reversible function of its properties (i.e., kind of suite * 13 + value) or just an enumerator described in material.inc.php. In this specific case it's a synthetic type id, which also the same as the number of the card in the sprite image (i.e., if you enumerate each image in sprite going left to right, then top to bottom).<br />
<br />
Now let's add the 5 of Hearts to the player's hand just for fun (this code will go in setup method after types initialization):<br />
<br />
<pre><br />
// 2 = hearts, 5 is 5, and 42 is the card id, which normally would come from db<br />
this.playerHand.addToStockWithId( this.getCardUniqueId( 2, 5 ), 42 );<br />
</pre><br />
<br />
This will add the card with id 42 and type 16 ( (2-1)*13+(5-2)=16 ). <br />
<br />
Note that number 16 would not be something you can see in database, Deck database will have separate field for type and type_arg where type is suite and type_arg is number, so its not the same thing, but you can use same formula to convert. Number 42 on the other hand would be id field in database. But we get to database in the later section.<br />
<br />
If you reload now you should see the 5 of hearts in "your hand".<br />
<br />
Stock control can handle clicking on items and forms the selection. You can immediately react to selection<br />
or you can query it later; for example when user presses some other button.<br />
<br />
Let's hook it up. Add this in the setup method in .js file, after this.playerHand is initialised:<br />
<br />
dojo.connect( this.playerHand, 'onChangeSelection', this, 'onPlayerHandSelectionChanged' );<br />
<br />
<br />
Then find the Player's action comment section and add a handler after the comment:<br />
<br />
<pre><br />
onPlayerHandSelectionChanged : function() {<br />
var items = this.playerHand.getSelectedItems();<br />
<br />
if (items.length > 0) {<br />
if (this.checkAction('playCard', true)) {<br />
// Can play a card<br />
<br />
var card_id = items[0].id;<br />
console.log("on playCard "+card_id);<br />
<br />
this.playerHand.unselectAll();<br />
} else if (this.checkAction('giveCards')) {<br />
// Can give cards => let the player select some cards<br />
} else {<br />
this.playerHand.unselectAll();<br />
}<br />
}<br />
},<br />
</pre><br />
<br />
The function name of the handler is 4th parameter of the dojo.connect function. Make sure you spell it correctly or there will be unpredictable effects.<br />
<br />
Now if you reload, open the Javascript Console (F12), and then click on the card in My Hand, you should see:<br />
on playCard 42<br />
printed on the console<br />
<br />
''Note : You need to be the active player to have rights to play a card and so log your message in the console''<br />
<br />
== Game Database and Game Initialisation ==<br />
<br />
Next step, you want to design a game database and setup a new game (on the server side).<br />
For that we need to a) modify the database schema to add our cards data b) add some global variables into<br />
the existing globals table.<br />
<br />
To modify the schema, first exit your existing game(s). Open '''dbmodel.sql''' file and uncomment the card table creation.<br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `card` (<br />
`card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,<br />
`card_type` varchar(16) NOT NULL,<br />
`card_type_arg` int(11) NOT NULL,<br />
`card_location` varchar(16) NOT NULL,<br />
`card_location_arg` int(11) NOT NULL,<br />
PRIMARY KEY (`card_id`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;<br />
</pre><br />
<br />
This is the "card" table which will be managed by the Deck php class.<br />
<br />
In addition we want a little piece of information in the players table:<br />
<br />
-- add info about first player<br />
ALTER TABLE `player` ADD `player_first` BOOLEAN NOT NULL DEFAULT '0';<br />
<br />
Not sure why they put this into the player table, as we could use a global db variable to hold first player as easily.<br />
But I am just following the existing code more-or-less.<br />
<br />
Next we finally get into .game.php class, where the main logic and db interaction would be. Find php constructor which should be <br />
function __construct( )<br />
This is first function in a file. Add this code to constructor.<br />
<pre><br />
parent::__construct();<br />
self::initGameStateLabels( array( <br />
"currentHandType" => 10, <br />
"trickColor" => 11, <br />
"alreadyPlayedHearts" => 12,<br />
) );<br />
<br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
</pre><br />
<br />
Here we are initializing three "Game State Variables" which are variables stored in the database. They are integers.<br />
It must start with no values lower then 10 since values lower than 10 are reserved. These values are stored by numeric ids<br />
in the database, but in the php we associate them with string labels for convenience of access. The variables are "trickColor": numbers from 1 to 4 that map to card suit (not sure why it's called color; maybe it's a translation from French); "alreadyPlayedHearts": a boolean flag (0 or 1) indicating hether somebody used hearts on the trick; "currentHandType": stores the value to indicate who to give cards to during exchange.<br />
<br />
The next 2 lines are creating $this->cards object and associating it with "card" table in the the database.<br />
<br />
<i>If we called db table 'foo' instead of 'card' the last statement would have been $this->cards->init( "foo" )</i><br />
<br />
<br />
At this point I would start a new game and make sure it starts, then exit. <br />
<br />
<i><br />
If you made a mistake<br />
in the .sql or php constructor the game won't start, and good luck debugging it. (That is why it's important to check<br />
once in a while to make sure it still starts while you remember what you have changed.)<br />
</i><br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/e3a049257b592ff6167688d4d344f8a83d349b08]<br />
<br />
Now we can go to game initialization '''setupNewGame''' in game.php. This method is called once when the table is created.<br />
<br />
In your template project you should have code that deals with player table, just leave it as is. Start inserting the<br />
other code after "Start the game initialization" comment.<br />
<pre><br />
// Init global values with their initial values<br />
<br />
// Note: hand types: 0 = give 3 cards to player on the left<br />
// 1 = give 3 cards to player on the right<br />
// 2 = give 3 cards to player opposite<br />
// 3 = keep cards<br />
self::setGameStateInitialValue( 'currentHandType', 0 );<br />
<br />
// Set current trick color to zero (= no trick color)<br />
self::setGameStateInitialValue( 'trickColor', 0 );<br />
<br />
// Mark if we already played hearts during this hand<br />
self::setGameStateInitialValue( 'alreadyPlayedHearts', 0 );<br />
</pre><br />
<br />
Here we initialize all the globals to 0.<br />
<br />
Next is to create our cards in the database. We have one deck of cards so it's pretty simple.<br />
<pre><br />
// Create cards<br />
$cards = array ();<br />
foreach ( $this->colors as $color_id => $color ) {<br />
// spade, heart, diamond, club<br />
for ($value = 2; $value <= 14; $value ++) {<br />
// 2, 3, 4, ... K, A<br />
$cards [] = array ('type' => $color_id,'type_arg' => $value,'nbr' => 1 );<br />
}<br />
}<br />
<br />
$this->cards->createCards( $cards, 'deck' );<br />
</pre><br />
<br />
This code that will create one of each card. But don't run it yet, because we missing $this->colors.<br />
So we have state of the game in the database, but there is some static game information which never changes.<br />
This information should be stored in material.inc.php and this way it can be accessed from all .php files.<br />
We will edit this file now by adding these lines<br />
<br />
<pre><br />
$this->colors = array(<br />
1 => array( 'name' => clienttranslate('spade'),<br />
'nametr' => self::_('spade') ),<br />
2 => array( 'name' => clienttranslate('heart'),<br />
'nametr' => self::_('heart') ),<br />
3 => array( 'name' => clienttranslate('club'),<br />
'nametr' => self::_('club') ),<br />
4 => array( 'name' => clienttranslate('diamond'),<br />
'nametr' => self::_('diamond') )<br />
);<br />
<br />
$this->values_label = array(<br />
2 =>'2',<br />
3 => '3',<br />
4 => '4',<br />
5 => '5',<br />
6 => '6',<br />
7 => '7',<br />
8 => '8',<br />
9 => '9',<br />
10 => '10',<br />
11 => clienttranslate('J'),<br />
12 => clienttranslate('Q'),<br />
13 => clienttranslate('K'),<br />
14 => clienttranslate('A')<br />
);<br />
</pre><br />
<br />
Where $this->colors will define Suit labels and $this->values_label will define value labels.<br />
If you noticed, we have two of each label for suits. This is because sometimes we need translated values on the php<br />
side and sometimes we don't. In this case '''nametr''' will return a translated value in php, which is only useful when you throw exceptions to show the right strings. If you pass a value to the client via notification you should always use untranslated strings, and the client will translate it. 'clienttranslate' marks the value for translation but does not actually change it for php. For more about this wonderful translation stuff see [[Translations]].<br />
<br />
== Full game model synchronisation ==<br />
<br />
Now at any point in the game we need to make sure that database information can be reflected back in the UI, so we must fix the '''getAllDatas''' function<br />
to return all possible data we need to reconstruct the game. This is in the game.php file. The template for getAllDatas() already takes care of player info. Let's just<br />
add hand and tableau data before we return a result.<br />
<br />
<pre><br />
// Cards in player hand<br />
$result['hand'] = $this->cards->getCardsInLocation( 'hand', $current_player_id );<br />
<br />
// Cards played on the table<br />
$result['cardsontable'] = $this->cards->getCardsInLocation( 'cardsontable' );<br />
</pre><br />
<br />
Now on the client side we should display this data, so in your .js file in the setup function (which is the receiver of getAllDatas) replace our hack of putting 5 of Hearts directly into the hand with:<br />
<br />
<pre><br />
// Cards in player's hand<br />
for ( var i in this.gamedatas.hand) {<br />
var card = this.gamedatas.hand[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
this.playerHand.addToStockWithId(this.getCardUniqueId(color, value), card.id);<br />
}<br />
<br />
// Cards played on table<br />
for (i in this.gamedatas.cardsontable) {<br />
var card = this.gamedatas.cardsontable[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
var player_id = card.location_arg;<br />
this.playCardOnTable(player_id, color, value, card.id);<br />
}<br />
</pre><br />
<br />
This should show hand and tableau cards now, except we are missing the '''playCardOnTable''' function. So find the '''getCardUniqueId''' function which<br />
should be in the utilities section and add this after it:<br />
<br />
<pre><br />
playCardOnTable : function(player_id, color, value, card_id) {<br />
// player_id => direction<br />
dojo.place(this.format_block('jstpl_cardontable', {<br />
x : this.cardwidth * (value - 2),<br />
y : this.cardheight * (color - 1),<br />
player_id : player_id<br />
}), 'playertablecard_' + player_id);<br />
<br />
if (player_id != this.player_id) {<br />
// Some opponent played a card<br />
// Move card from player panel<br />
this.placeOnObject('cardontable_' + player_id, 'overall_player_board_' + player_id);<br />
} else {<br />
// You played a card. If it exists in your hand, move card from there and remove<br />
// corresponding item<br />
<br />
if ($('myhand_item_' + card_id)) {<br />
this.placeOnObject('cardontable_' + player_id, 'myhand_item_' + card_id);<br />
this.playerHand.removeFromStockById(card_id);<br />
}<br />
}<br />
<br />
// In any case: move it to its final destination<br />
this.slideToObject('cardontable_' + player_id, 'playertablecard_' + player_id).play();<br />
},<br />
</pre><br />
<br />
For this to work we also need to add a card template in the .tpl file<br />
<pre><br />
// Javascript HTML templates<br />
<br />
var jstpl_cardontable = '<div class="cardontable" id="cardontable_${player_id}" style="background-position:-${x}px -${y}px">\<br />
</div>';<br />
</pre><br />
<br />
<br />
What this does is basically create another card object, because if it is not our card it's not in our hand (Stock) so<br />
we have to create it out of thin air. The technique to do that is to implement a Javascript template object defined in the .tpl file with some<br />
parameters, which will basically create a "div" string (yes you could have used string concatenation but it would not be fancy).<br />
Now dojo.place places it (the div) on top of a placeholder. Now we have an object with an id of 'cardontable_' + player_id. Depending<br />
on who is playing it we either place it on the player miniboard or in hand (and remove it from hand stock). Then we animate the card move.<br />
<br />
We also should fix our .css file now to add style for cardontable and REMOVE background for playertablecard which really is a placeholder div and not a card. (Don't miss the remove step; it will be all screwy if you do!)<br />
<br />
<pre><br />
.playertablecard {<br />
display: inline-block;<br />
position: relative;<br />
margin-top: 5px;<br />
width: 72px;<br />
height: 96px;<br />
/* we remove background-image here */<br />
}<br />
<br />
/*** cards on table ***/<br />
<br />
.cardontable {<br />
position: absolute;<br />
width: 72px;<br />
height: 96px;<br />
background-image: url('img/cards.jpg'); <br />
}<br />
</pre><br />
<br />
Now to test that it actually works let's deal cards to players during game initialization:<br />
<br />
Add this after createCards in setupNewGame function in the game.php file<br />
<pre><br />
// Shuffle deck<br />
$this->cards->shuffle('deck');<br />
// Deal 13 cards to each players<br />
$players = self::loadPlayersBasicInfos();<br />
foreach ( $players as $player_id => $player ) {<br />
$cards = $this->cards->pickCards(13, 'deck', $player_id);<br />
} <br />
</pre><br />
<br />
Now when you start the game you should see 13 cards in your hand!<br />
<br />
We just need to hook-up clicking on card and test if our playCardOnTable works.<br />
<br />
Find onPlayerHandSelectionChanged function in the JS file, we should have logging there like console.log("on playCard "+card_id);<br />
So after that insert this:<br />
<pre><br />
console.log("on playCard "+card_id);<br />
// type is (color - 1) * 13 + (value - 2)<br />
var type = items[0].type;<br />
var color = Math.floor(type / 13) + 1;<br />
var value = type % 13 + 2;<br />
<br />
this.playCardOnTable(this.player_id,color,value,card_id);<br />
</pre><br />
Note: this code is for testing we will replace it with server interaction after we test it.<br />
<br />
Now if you force reload (because we changed .css before) you should be able to click on card from you have and see it moving,<br />
you can click on few cards this way. When you done enjoying the animation, press F5 to get your hand back.<br />
<br />
[[File:Heartsla-sync.png]]<br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/01d4e2f595fd14c2adcc97a957d21bb2766f78a8]<br />
<br />
== State Machine ==<br />
<br />
Now we need to create a game state machine. So the states are:<br />
<br />
* Cards are dealt to all players (lets call it "newHand")<br />
* Player is selected who will start a new trick ("newTrick")<br />
* Player start or respond to played card ("playerTurn")<br />
* Game control is passed to next player or trick is ended ("nextPlayer")<br />
* End of hand processing (scoring and check for end of game) ("nextHand")<br />
<br />
In addition players can exchange cards so we need two more states for that but we will skip it for now.<br />
<br />
<br />
The state handling spread across 4 files, so you have to make sure all pieces are connected together.<br />
The state machine states.php defines all the states, and function handlers on php side in a form of string,<br />
and if any of these functions are not implemented it would be very hard to debug because it will break in random places.<br />
<br />
So .states.php<br />
<br />
<pre><br />
$machinestates = array(<br />
<br />
// The initial state. Please do not modify.<br />
1 => array(<br />
"name" => "gameSetup",<br />
"description" => clienttranslate("Game setup"),<br />
"type" => "manager",<br />
"action" => "stGameSetup",<br />
"transitions" => array( "" => 20 )<br />
),<br />
<br />
<br />
/// New hand<br />
20 => array(<br />
"name" => "newHand",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNewHand",<br />
"updateGameProgression" => true, <br />
"transitions" => array( "" => 30 )<br />
), <br />
<br />
<br />
<br />
// Trick<br />
<br />
30 => array(<br />
"name" => "newTrick",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNewTrick",<br />
"transitions" => array( "" => 31 )<br />
), <br />
31 => array(<br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must play a card'),<br />
"descriptionmyturn" => clienttranslate('${you} must play a card'),<br />
"type" => "activeplayer",<br />
"possibleactions" => array( "playCard" ),<br />
"transitions" => array( "playCard" => 32 )<br />
), <br />
32 => array(<br />
"name" => "nextPlayer",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNextPlayer",<br />
"transitions" => array( "nextPlayer" => 31, "nextTrick" => 30, "endHand" => 40 )<br />
), <br />
<br />
<br />
// End of the hand (scoring, etc...)<br />
40 => array(<br />
"name" => "endHand",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stEndHand",<br />
"transitions" => array( "nextHand" => 20, "endGame" => 99 )<br />
), <br />
<br />
// Final state.<br />
// Please do not modify.<br />
99 => array(<br />
"name" => "gameEnd",<br />
"description" => clienttranslate("End of game"),<br />
"type" => "manager",<br />
"action" => "stGameEnd",<br />
"args" => "argGameEnd"<br />
)<br />
<br />
);<br />
</pre><br />
<br />
The full details about what these fields are you can find in [[Your_game_state_machine:_states.inc.php]].<br />
<br />
But basically we have Player states, in which human player has to perform an "action" by pressing some button in UI or selecting some game item, which will trigger js handler, which will do ajax call to server into API define <br />
by .action.php file. All functions in this file are API between client and server and has very simple<br />
and repetitive structure. In this case there is only two action player can do - play a card or pass cards to other player. So these 2 functions go into .action.php file, we will only define one now since we have not implemented card passing states yet:<br />
<br />
<pre><br />
public function playCard() {<br />
self::setAjaxMode();<br />
$card_id = self::getArg("id", AT_posint, true);<br />
$this->game->playCard($card_id);<br />
self::ajaxResponse();<br />
}<br />
</pre><br />
<br />
<br />
Now to make it run we have define all handler functions that we referenced in states, which are - one function for state arguments argGiveCards, 4 functions for robot states (where game performs some action)<br />
and 1 function for player actions handling.<br />
Find 'Game state arguments' section and paste this in:<br />
<pre><br />
function argGiveCards() {<br />
return array ();<br />
}<br />
</pre><br />
<br />
This normally pass some parameters to states, but we don't need anything yet. It good to have placeholder there anyway, so we can fix it later.<br />
Important: even when its a stub this function must return array not scalar.<br />
<br />
Lets do stubs for other functions, find game state actions section in .game.php file and insert these<br />
<pre><br />
function stNewHand() {<br />
// Take back all cards (from any location => null) to deck<br />
$this->cards->moveAllCardsInLocation(null, "deck");<br />
$this->cards->shuffle('deck');<br />
// Deal 13 cards to each players<br />
// Create deck, shuffle it and give 13 initial cards<br />
$players = self::loadPlayersBasicInfos();<br />
foreach ( $players as $player_id => $player ) {<br />
$cards = $this->cards->pickCards(13, 'deck', $player_id);<br />
// Notify player about his cards<br />
self::notifyPlayer($player_id, 'newHand', '', array ('cards' => $cards ));<br />
}<br />
self::setGameStateValue('alreadyPlayedHearts', 0);<br />
$this->gamestate->nextState("");<br />
}<br />
<br />
function stNewTrick() {<br />
// New trick: active the player who wins the last trick, or the player who own the club-2 card<br />
// Reset trick color to 0 (= no color)<br />
self::setGameStateInitialValue('trickColor', 0);<br />
$this->gamestate->nextState();<br />
}<br />
<br />
function stNextPlayer() {<br />
// Active next player OR end the trick and go to the next trick OR end the hand<br />
if ($this->cards->countCardInLocation('cardsontable') == 4) {<br />
// This is the end of the trick<br />
// Move all cards to "cardswon" of the given player<br />
$best_value_player_id = self::activeNextPlayer(); // TODO figure out winner of trick<br />
$this->cards->moveAllCardsInLocation('cardsontable', 'cardswon', null, $best_value_player_id);<br />
<br />
if ($this->cards->countCardInLocation('hand') == 0) {<br />
// End of the hand<br />
$this->gamestate->nextState("endHand");<br />
} else {<br />
// End of the trick<br />
$this->gamestate->nextState("nextTrick");<br />
}<br />
} else {<br />
// Standard case (not the end of the trick)<br />
// => just active the next player<br />
$player_id = self::activeNextPlayer();<br />
self::giveExtraTime($player_id);<br />
$this->gamestate->nextState('nextPlayer');<br />
}<br />
}<br />
<br />
function stEndHand() {<br />
$this->gamestate->nextState("nextHand");<br />
}<br />
<br />
</pre><br />
Important: All state actions game or player must end with state transition (or thrown exception). Also make sure its ONLY one state transition,<br />
if you accidentally fall though after state transition and do another one it will be a real mess and head scratching for long time.<br />
<br />
Now find 'player actions' section and paste this code there<br />
<pre><br />
function playCard($card_id) {<br />
self::checkAction("playCard");<br />
$player_id = self::getActivePlayerId();<br />
throw new BgaUserException(self::_("Not implemented: ") . "$player_id plays $card_id");<br />
}<br />
</pre><br />
We won't implement it yet but throw an exception which we will see if interaction is working properly<br />
<br />
Now the game should start but it would not be any different then before because we have to implement actual interactions.<br />
Its good to check if it still working though (and if it was running before you have to exit because we changed state machine and normally it will break stuff)<br />
<br />
== Client - Server interactions ==<br />
<br />
Now to implement things for real we have hook UI actions to ajax calls, and process notifications sent by the server.<br />
So previously we hooked playCardOnTable right into js handler which caused client animation, in real game its a two<br />
step operation. When user clicks on game element js client sends an ajax call to server, server processes it and updates database, server sends<br />
notification in response, client hooks animations to server notification.<br />
<br />
So in .js code replace onPlayerHandSelectionChanged with<br />
<pre><br />
onPlayerHandSelectionChanged : function() {<br />
var items = this.playerHand.getSelectedItems();<br />
<br />
if (items.length > 0) {<br />
var action = 'playCard';<br />
if (this.checkAction(action, true)) {<br />
// Can play a card<br />
var card_id = items[0].id; <br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", {<br />
id : card_id,<br />
lock : true<br />
}, this, function(result) {<br />
}, function(is_error) {<br />
});<br />
<br />
this.playerHand.unselectAll();<br />
} else if (this.checkAction('giveCards')) {<br />
// Can give cards => let the player select some cards<br />
} else {<br />
this.playerHand.unselectAll();<br />
}<br />
}<br />
},<br />
</pre><br />
<br />
<br />
Now when you click on card you should get a server response: Not implemented...<br />
<br />
Lets implement it, in .game.php<br />
<pre><br />
function playCard($card_id) {<br />
self::checkAction("playCard");<br />
$player_id = self::getActivePlayerId();<br />
$this->cards->moveCard($card_id, 'cardsontable', $player_id);<br />
// XXX check rules here<br />
$currentCard = $this->cards->getCard($card_id);<br />
// And notify<br />
self::notifyAllPlayers('playCard', clienttranslate('${player_name} plays ${value_displayed} ${color_displayed}'), array (<br />
'i18n' => array ('color_displayed','value_displayed' ),'card_id' => $card_id,'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),'value' => $currentCard ['type_arg'],<br />
'value_displayed' => $this->values_label [$currentCard ['type_arg']],'color' => $currentCard ['type'],<br />
'color_displayed' => $this->colors [$currentCard ['type']] ['name'] ));<br />
// Next player<br />
$this->gamestate->nextState('playCard');<br />
}<br />
</pre><br />
<br />
We get the card from client, we move it to the tableau (moveCard is hooked to database directly, its part of deck class),<br />
we notify all players and we change state. What we missing here is bunch of checks (rule enforcements), we will add it later.<br />
<br />
Interesting part about this notify is that we use i18n array for string that needs to be translated by client, so<br />
they sent as English text in notification, then client has to know which parameters needs translating.<br />
<br />
On the client side .js we have to implement a notification handler to do the animation<br />
<br />
<pre><br />
setupNotifications : function() {<br />
console.log('notifications subscriptions setup');<br />
<br />
dojo.subscribe('newHand', this, "notif_newHand");<br />
dojo.subscribe('playCard', this, "notif_playCard");<br />
<br />
},<br />
<br />
notif_newHand : function(notif) {<br />
// We received a new full hand of 13 cards.<br />
this.playerHand.removeAll();<br />
<br />
for ( var i in notif.args.cards) {<br />
var card = notif.args.cards[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
this.playerHand.addToStockWithId(this.getCardUniqueId(color, value), card.id);<br />
}<br />
},<br />
<br />
notif_playCard : function(notif) {<br />
// Play a card on the table<br />
this.playCardOnTable(notif.args.player_id, notif.args.color, notif.args.value, notif.args.card_id);<br />
},<br />
</pre><br />
<br />
Now it actually works through the server when you click on card - the move is recorded. If you testing it now you will notice<br />
after trick is done all cards remains on the table, but if you press F5 they would disappear, this is because<br />
we updated database to pick-up the cards but did not send notification about it, so we need to send notification about it<br />
and have a handler for it<br />
<br />
So in .game.php file add notification in stNextPlayer function after moveAllCardsInLocation call:<br />
<br />
<pre><br />
// Notify<br />
// Note: we use 2 notifications here in order we can pause the display during the first notification<br />
// before we move all cards to the winner (during the second)<br />
$players = self::loadPlayersBasicInfos();<br />
self::notifyAllPlayers( 'trickWin', clienttranslate('${player_name} wins the trick'), array(<br />
'player_id' => $best_value_player_id,<br />
'player_name' => $players[ $best_value_player_id ]['player_name']<br />
) ); <br />
self::notifyAllPlayers( 'giveAllCardsToPlayer','', array(<br />
'player_id' => $best_value_player_id<br />
) );<br />
</pre><br />
<br />
And in .js file add 2 more notification handlers.<br />
<br />
This is to subscribe in setupNotifications function<br />
<pre><br />
dojo.subscribe( 'trickWin', this, "notif_trickWin" );<br />
this.notifqueue.setSynchronous( 'trickWin', 1000 );<br />
dojo.subscribe( 'giveAllCardsToPlayer', this, "notif_giveAllCardsToPlayer" );<br />
</pre><br />
<br />
And this are handlers<br />
<pre><br />
notif_trickWin : function(notif) {<br />
// We do nothing here (just wait in order players can view the 4 cards played before they're gone.<br />
},<br />
notif_giveAllCardsToPlayer : function(notif) {<br />
// Move all cards on table to given table, then destroy them<br />
var winner_id = notif.args.player_id;<br />
for ( var player_id in this.gamedatas.players) {<br />
var anim = this.slideToObject('cardontable_' + player_id, 'overall_player_board_' + winner_id);<br />
dojo.connect(anim, 'onEnd', function(node) {<br />
dojo.destroy(node);<br />
});<br />
anim.play();<br />
}<br />
},<br />
</pre><br />
<br />
So 'trickWin' notification does not do much except it will delay the processing of next notification by 1 second (1000 ms)<br />
and it will log the message (that happens independent of what handler does).<br />
<i>Note: if on the other hand you don't want to log but what what to do something else send empty message</i><br />
<br />
Now after the trick you see all cards move the "player's stash".<br />
<br />
== Scoring and End of game handling ==<br />
<br />
Now we should calculate scoring and for that we need to actually track who wins the trick.<br />
Trick is won by the player with highest card (no trump). We just need to remember what is trick suite.<br />
For which we will use state variable 'trickColor' which we already conveniently created.<br />
<br />
In .game.php file find playCard function and add this before notify functions<br />
$currentTrickColor = self::getGameStateValue( 'trickColor' ) ;<br />
if( $currentTrickColor == 0 )<br />
self::setGameStateValue( 'trickColor', $currentCard['type'] );<br />
<br />
This will make sure we remember first suit being played, now to use it modify stNextPlayer function to fix our TODO comment<br />
<pre><br />
function stNextPlayer() {<br />
// Active next player OR end the trick and go to the next trick OR end the hand<br />
if ($this->cards->countCardInLocation('cardsontable') == 4) {<br />
// This is the end of the trick<br />
$cards_on_table = $this->cards->getCardsInLocation('cardsontable');<br />
$best_value = 0;<br />
$best_value_player_id = null;<br />
$currentTrickColor = self::getGameStateValue('trickColor');<br />
foreach ( $cards_on_table as $card ) {<br />
// Note: type = card color<br />
if ($card ['type'] == $currentTrickColor) {<br />
if ($best_value_player_id === null || $card ['type_arg'] > $best_value) {<br />
$best_value_player_id = $card ['location_arg']; // Note: location_arg = player who played this card on table<br />
$best_value = $card ['type_arg']; // Note: type_arg = value of the card<br />
}<br />
}<br />
}<br />
<br />
// Active this player => he's the one who starts the next trick<br />
$this->gamestate->changeActivePlayer( $best_value_player_id );<br />
<br />
// Move all cards to "cardswon" of the given player<br />
$this->cards->moveAllCardsInLocation('cardsontable', 'cardswon', null, $best_value_player_id);<br />
<br />
// Notify<br />
// ... same code here as before<br />
</pre><br />
<br />
The scoring rule in the studio example code is huge multi-page function, for this tutorial we will make simplier.<br />
Lets score -1 point per heart and call it a day. And game will end when somebody goes -100 or below.<br />
<br />
As UI goes for scoring, the main thing to update is the scoring on the mini boards represented by stars, also<br />
we want to show that in the log. <br />
In addition scoring can be shown in [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialog]] using tableWindow notification, but it is a tutorial on its own and you can do it as homework (it is part of original heart game).<br />
<br />
In .js file we need to add one more subscription and notification handler:<br />
dojo.subscribe( 'newScores', this, "notif_newScores" );<br />
in setupNotifications<br />
<br />
and <br />
notif_newScores : function(notif) {<br />
// Update players' scores<br />
for ( var player_id in notif.args.newScores) {<br />
this.scoreCtrl[player_id].toValue(notif.args.newScores[player_id]);<br />
}<br />
},<br />
somewhere after. this.scoreCtrl is pre-existing object that shows the scoring and this function will update score values per player from notification argument<br />
<br />
so in .game.php our stEndHand function will look like<br />
<br />
<pre><br />
function stEndHand() {<br />
// Count and score points, then end the game or go to the next hand.<br />
$players = self::loadPlayersBasicInfos();<br />
// Gets all "hearts" + queen of spades<br />
<br />
$player_to_points = array ();<br />
foreach ( $players as $player_id => $player ) {<br />
$player_to_points [$player_id] = 0;<br />
}<br />
$cards = $this->cards->getCardsInLocation("cardswon");<br />
foreach ( $cards as $card ) {<br />
$player_id = $card ['location_arg'];<br />
// Note: 2 = heart<br />
if ($card ['type'] == 2) {<br />
$player_to_points [$player_id] ++;<br />
}<br />
}<br />
// Apply scores to player<br />
foreach ( $player_to_points as $player_id => $points ) {<br />
if ($points != 0) {<br />
$sql = "UPDATE player SET player_score=player_score-$points WHERE player_id='$player_id'";<br />
self::DbQuery($sql);<br />
$heart_number = $player_to_points [$player_id];<br />
self::notifyAllPlayers("points", clienttranslate('${player_name} gets ${nbr} hearts and looses ${nbr} points'), array (<br />
'player_id' => $player_id,'player_name' => $players [$player_id] ['player_name'],<br />
'nbr' => $heart_number ));<br />
} else {<br />
// No point lost (just notify)<br />
self::notifyAllPlayers("points", clienttranslate('${player_name} did not get any hearts'), array (<br />
'player_id' => $player_id,'player_name' => $players [$player_id] ['player_name'] ));<br />
}<br />
}<br />
$newScores = self::getCollectionFromDb("SELECT player_id, player_score FROM player", true );<br />
self::notifyAllPlayers( "newScores", '', array( 'newScores' => $newScores ) );<br />
<br />
///// Test if this is the end of the game<br />
foreach ( $newScores as $player_id => $score ) {<br />
if ($score <= -100) {<br />
// Trigger the end of the game !<br />
$this->gamestate->nextState("endGame");<br />
return;<br />
}<br />
}<br />
<br />
<br />
$this->gamestate->nextState("nextHand");<br />
}<br />
</pre><br />
<br />
<br />
So it should more less work now, including end of game condition. Try to play it!<br />
<br />
== Additional stuff ==<br />
<br />
The following things were not implemented and can add them yourself by looking at the code of original hearts game:<br />
<br />
* Remove debug code from setupNewGame to deal cards, cards are now dealt in stNewHand state handler<br />
* Rule checking and rule enforcements in playCard function<br />
* Start scoring with 100 points each and end when <= 0<br />
* Fix scoring rules with Q of spades and 26 point reverse scoring<br />
* First player one with 2 club<br />
* Add progress handling<br />
* Add statistics<br />
* Add card exchange states<br />
* Add game option to start with 75 points instead of 100</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Tutorial_hearts&diff=4354
Tutorial hearts
2020-05-10T15:22:00Z
<p>Cpasbanal: /* State Machine */ grammar issue</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Introduction ==<br />
<br />
Using this tutorial, you can build a complete working game on the BGA environment: Hearts.<br />
<br />
Before you read this tutorial, you must:<br />
* Read the overall presentations of the BGA Framework ([[Studio|see here]]).<br />
* Know the rules for Hearts<br />
* Some-what know the languages used on BGA: PHP, SQL, HTML, CSS, Javascript<br />
* Set up your development environment [http://en.doc.boardgamearena.com/First_steps_with_BGA_Studio First Steps with BGA Studio]<br />
* As part of setup you have to have access to your ftp home folder in studio, which would have 'hearts' game source code. We will be using some resources of this game in this tutorial, so copy it over to local disk if you have not done so.<br />
<br />
If you are stuck or have question about this tutorial, post on [https://forum.boardgamearena.com/viewforum.php?f=12 BGA Developers forum]<br />
<br />
== Create your first game ==<br />
<br />
If you have not already, you have to create a project in BGA Studio. For this tutorial you can create a project heartsYOURNAME where<br />
YOURNAME is your developer login name. You can also re-use the project you have created for the "First Steps" tutorial above.<br />
With the initial skeleton of code provided, you can already start a game from the BGA Studio. <br />
<br />
1. Find and start the game in turn-based mode with 4 players. Make sure it works. <br />
<br />
2. Modify the text in heartsYOURNAME_heartsYOURNAME.tpl, reload the page in the browser and make sure your ftp sync works as expected.<br />
Note: if you have not setup auto-sync do it now, manually copying files is a no-starter.<br />
<br />
3. Express stop from settings menu (the gear icon).<br />
<br />
<i>Note: please do '''not''' use the hearts project code as a base. This tutorial assumes you started with a TEMPLATE project with no prior modifications. Using the hearts project as a base will be very confusing and you won't be able to follow all the steps.<br />
</i><br />
<br />
== Hook version control system ==<br />
<br />
For a real game, or even for this tutorial, we recommend committing the code to version control right from the start. You are going to find yourself in a situation where the game doesn't even start anymore and no way of debugging it, unless you have a way to revert. That is where version control becomes very handy. If you are not familiar with version control (e.g. [https://git-scm.com/docs/gittutorial git]) then at least back up your files after each major change. Start now.<br />
<br />
Code for this tutorial available is on github: https://github.com/elaskavaia/bga-heartsla<br />
<br />
Different revisions represent different steps along the process, starting from original template to a complete game.<br />
<br />
== Update game infos and box graphics ==<br />
<br />
Even it does not nothing yet, always start by making sure the game looks decent in the game selector, meaning it has nice box graphics and its information is correct. For that we need to edit [[Game_meta-information: gameinfos.inc.php|gameinfos.inc.php]].<br />
<br />
For a real game, you would go to [http://boardgamegeek.com BoardGameGeek], find the game, and use the information from BGG to fill in the gameinfos.<br />
<br />
So let's do that. Find "hearts" on BoardGameGeek. (Hint: Original release 1850 :))<br />
<br />
You can fill in the year of publishing and bgg id, put ''Public Domain'' under publisher, and a publisher id of 171 for public domain. And as designer and author you can just put your own name just for fun. Set number of players to 4.<br />
<br />
// Players configuration that can be played (ex: 2 to 4 players)<br />
'players' => array( 4 ), <br />
<br />
<br />
The next step is to replace '''game_box.png''' with nicer images. For this tutorial, just copy all the files from the img/ folder of the hearts/ template into the img/ directory of your project. Replace publisher.png with a nicer image: for example https://github.com/elaskavaia/bga-sharedcode/blob/master/img/publisher.png.<br />
<br />
Details about images can be found here: [[Game art: img directory]].<br />
<br />
Now an important step: you have to LOAD these files into the Studio website through the control panel. So go to Control Panel -> Manage Games -> heartsYOURNAME<br />
and press Reload for 'Reload game informations' and 'Reload game box image'<br />
<br />
Now try to start the game again. If you somehow introduced a syntax error in the gameinfos file it may not work (the game won't start).<br />
Always use the "Express Start" button to start the game. You should see a standard state prompt from the template. You should see 4 players on the right: testdude0 .. testdude3.<br />
To switch between them press the red arrow button near their names, it will open another tab. This way you don't need to login and logout from multiple accounts!<br />
<br />
<br />
<i>Note: if you had run the game before with less than 4 players there is a bug that will prevent you from running it with 4 only (if you did not run it before or run it with 4 players as instructed stop reading this note), to workaround revert back to original players array (i.e. 1,2,3,4), reload game options, then create a table with 4 players, exit that game table, then change gameoptions to 4 only as above, reload game options, create table again.</i><br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/4b3a73eeb5acae961ade18473af119e8ce8d1a8f]<br />
<br />
== Layout and Graphics ==<br />
<br />
In this section we will do graphics of the game, and main layout of the game.<br />
<br />
First copy a sprite with cards image from hearts img/cards.jpg into img/ folder of your project. Project hearts is mounted to your home directory on bga server.<br />
<br />
Edit .tpl to add some divs to represent player table and hand area<br />
<br />
<pre><br />
<div id="myhand_wrap" class="whiteblock"><br />
<h3>My Hand</h3><br />
<div id="myhand"><br />
</div><br />
</div><br />
</pre><br />
<br />
If you refresh you should see now white area with My Hand title.<br />
<br />
<br />
[[File:Heartsla-tpl2.png]]<br />
<br />
Now lets add a card into the hand, just so you can feel it. Edit .tpl and a playertablecard div inside a hand div<br />
<pre><br />
...<br />
<div id="myhand"><br />
<div class="playertablecard"></div><br />
</div><br />
...<br />
</pre><br />
<br />
Edit .css file<br />
<pre><br />
.playertablecard {<br />
display: inline-block;<br />
position: relative;<br />
margin-top: 5px;<br />
width: 72px;<br />
height: 96px;<br />
background-image: url('img/cards.jpg'); /* temp hack to see it */<br />
}<br />
</pre><br />
<br />
When you edit CSS remember that you have to FORCE-reload page, i.e. Ctrl-F5, otherwise its cached.<br />
<i>Same when you change existing graphics files</i>.<br />
<br />
You should see this:<br />
<br />
[[File:Heartsla-tpl3.png]]<br />
<br />
Awesome! Now lets do the rest of layout.<br />
<br />
There are few ways of how html could have been generated, you could have start with nothing and generate<br />
all by java script. Or you could have started with complete game markup in html and make java script just hide and move pieces around. BGA framework provides also a third way which is mix of both plus template engine to generate HTML using php. So lets do that.<br />
<br />
Change .tpl file to have this inside<br />
<pre><br />
<div id="playertables"><br />
<br />
<!-- BEGIN player --><br />
<div class="playertable whiteblock playertable_{DIR}"><br />
<div class="playertablename" style="color:#{PLAYER_COLOR}"><br />
{PLAYER_NAME}<br />
</div><br />
<div class="playertablecard" id="playertablecard_{PLAYER_ID}"><br />
</div><br />
</div><br />
<!-- END player --><br />
<br />
</div><br />
<br />
<div id="myhand_wrap" class="whiteblock"><br />
<h3>{MY_HAND}</h3><br />
<div id="myhand"><br />
</div><br />
</div><br />
</pre><br />
<br />
What we did is we added "block" player, it is marked up using html comments. {VAR} notation is used<br />
to inject variables and <br />
<pre><br />
<!-- BEGIN xxx --> <br />
inside <br />
<!-- END xxx --> <br />
</pre><br />
effectively allows us to do template loops.<br />
<br />
In .view.php insert this code after 'Place your code below' comment<br />
<br />
<br />
<pre><br />
$template = self::getGameName() . "_" . self::getGameName();<br />
<br />
$directions = array( 'S', 'W', 'N', 'E' );<br />
<br />
// this will inflate our player block with actual players data<br />
$this->page->begin_block($template, "player");<br />
foreach ( $players as $player_id => $info ) {<br />
$dir = array_shift($directions);<br />
$this->page->insert_block("player", array ("PLAYER_ID" => $player_id,<br />
"PLAYER_NAME" => $players [$player_id] ['player_name'],<br />
"PLAYER_COLOR" => $players [$player_id] ['player_color'],<br />
"DIR" => $dir ));<br />
}<br />
// this will make our My Hand text translatable<br />
$this->tpl['MY_HAND'] = self::_("My hand");<br />
</pre><br />
<br />
What it does is for each player we have it will replicate the html between <!-- BEGIN player --> and <!-- END player --> tags, substituting the variable denoted by {XXX}<br />
with the values you provide. The DIR variable in this case we pulling from directions array (where array_shift will take first element and remove it from the array).<br />
<br />
Reload. If everything went well you should see this:<br />
<br />
[[File:Heartsla-tpl4.png]]<br />
<br />
These are "tableau" areas for 4 players plus My hand visible only to one player.<br />
They are not exactly how we wanted them to be because we did not edit .css yet.<br />
<br />
Now edit .css, add these lines after import before our previous definition<br />
<br />
<pre><br />
/** Table layout **/<br />
<br />
#playertables {<br />
position: relative;<br />
width: 710px;<br />
height: 340px;<br />
}<br />
<br />
.playertablename {<br />
font-weight: bold;<br />
}<br />
<br />
.playertable {<br />
position: absolute;<br />
text-align: center;<br />
width: 180px;<br />
height: 130px;<br />
}<br />
<br />
.playertable_N {<br />
left: 50%;<br />
top: 0px;<br />
margin-left: -90px; /* half of 180 */<br />
}<br />
.playertable_S {<br />
left: 50%;<br />
bottom: 0px;<br />
margin-left: -90px; /* half of 180 */<br />
}<br />
.playertable_W {<br />
left: 0px;<br />
top: 50%;<br />
margin-top: -55px; /* half of 130 */<br />
}<br />
.playertable_E {<br />
right: 0px;<br />
top: 50%;<br />
margin-top: -55px; /* half of 130 */<br />
}<br />
</pre><br />
<br />
<br />
Now you force Reload and you should see this:<br />
[[File:Heartsla-tpl5.png]]<br />
<br />
This is almost all we need for graphics and layout, there are few tweaks left there but lets do some more heavy lifting now.<br />
<br />
<i>Note: if you did not see changes you may have not force reloaded, force means you use Ctrl+F5 or Cltr+Shift-R, if you don't "force" browser will use cached version of .css and images! Which is not what you just changed</i><br />
<br />
<br />
<i>Another Note: In general if you have auto-sync you don't need to reload if you change game.php file, you need normal reload if you change js, and force reload for css and images. If you changed state machine or database you likely need to restart the game.</i><br />
<br />
== Game Interface JS Stock ==<br />
<br />
The BGA framework provides a few out of the box classes to deal with cards. The client side<br />
contains a class called [[Stock]] and it can be used for any dynamic html "pieces" management that uses<br />
common sprite images. On the server side we will use the [[Deck]] class which we discuss later.<br />
<br />
If you open cards.jpg in an image viewer you will see that it is a "sprite" image - a 13x4 grid of images stitched together,<br />
which is a very efficient way to transport images. So we will use the Stock class to mark up these images and create<br />
"card" divs for us.<br />
<br />
First, we need to add '''ebg/stock''' as a dependency in the hearts.js file:<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <==== HERE<br />
],<br />
</pre><br />
<br />
Then add this to the Javascript contructor, this will define size of our cards<br />
<pre><br />
console.log('hearts constructor');<br />
this.cardwidth = 72;<br />
this.cardheight = 96;<br />
</pre><br />
<br />
The stock is initialized in the Javascript "setup" method like this:<br />
<pre><br />
// TODO: Set up your game interface here, according to "gamedatas"<br />
<br />
// Player hand<br />
this.playerHand = new ebg.stock(); // new stock object for hand<br />
this.playerHand.create( this, $('myhand'), this.cardwidth, this.cardheight );<br />
</pre><br />
<br />
As parameters of the "create" method, we provided the width/height of an item (a card), and the container div "myhand" - which is an id of "div" element from our .tpl file representing a player hand.<br />
<br />
<br />
Then, we must tell the stock what items it is going to display during its life: the 52 cards of a standard card game from a CSS sprite image named "cards.jpg" with all the cards arranged in 4 rows and 13 columns.<br />
<br />
Here's how we tell stock what item types to display:<br />
<pre><br />
this.playerHand.image_items_per_row = 13; // 13 images per row<br />
<br />
<br />
// Create cards types:<br />
for (var color = 1; color <= 4; color++) {<br />
for (var value = 2; value <= 14; value++) {<br />
// Build card type id<br />
var card_type_id = this.getCardUniqueId(color, value);<br />
this.playerHand.addItemType(card_type_id, card_type_id, g_gamethemeurl + 'img/cards.jpg', card_type_id);<br />
}<br />
}<br />
</pre><br />
<br />
And add this function to the utilities section<br />
<pre><br />
// Get card unique identifier based on its color and value<br />
getCardUniqueId : function(color, value) {<br />
return (color - 1) * 13 + (value - 2);<br />
},<br />
</pre><br />
<br />
Explanations:<br />
* At first, we tell the stock component that our CSS sprite contains 13 items per row. This way, it can find the correct image for each card type id.<br />
* Then for the 4x13 cards, we call the '''addItemType''' method that creates the type. The arguments are the type id, the weight of the card (for sorting purpose), the URL of our CSS sprite, and the position of our card image in the CSS sprite. It happens to be the same number in our case.<br />
<br />
Note: we need to generate a unique ID for each type of card based on its color and value. For that we create a function '''getCardUniqueId'''. The type is the unique identifier of the TYPE of the card, e.g., the queen of spades encoded as an integer. If our deck had 2 standard card decks we would have had 2 queens of spades; they would share the same type and the same image but would have different ids. NOTE: It's unfortunate that they named this '''getCardUniqueId'''; it should have been '''getCardUniqueType''', because it really isn't an id, but a TYPE of card. The type of the item should either be a reversible function of its properties (i.e., kind of suite * 13 + value) or just an enumerator described in material.inc.php. In this specific case it's a synthetic type id, which also the same as the number of the card in the sprite image (i.e., if you enumerate each image in sprite going left to right, then top to bottom).<br />
<br />
Now let's add the 5 of Hearts to the player's hand just for fun (this code will go in setup method after types initialization):<br />
<br />
<pre><br />
// 2 = hearts, 5 is 5, and 42 is the card id, which normally would come from db<br />
this.playerHand.addToStockWithId( this.getCardUniqueId( 2, 5 ), 42 );<br />
</pre><br />
<br />
This will add the card with id 42 and type 16 ( (2-1)*13+(5-2)=16 ). <br />
<br />
Note that number 16 would not be something you can see in database, Deck database will have separate field for type and type_arg where type is suite and type_arg is number, so its not the same thing, but you can use same formula to convert. Number 42 on the other hand would be id field in database. But we get to database in the later section.<br />
<br />
If you reload now you should see the 5 of hearts in "your hand".<br />
<br />
Stock control can handle clicking on items and forms the selection. You can immediately react to selection<br />
or you can query it later; for example when user presses some other button.<br />
<br />
Let's hook it up. Add this in the setup method in .js file, after this.playerHand is initialised:<br />
<br />
dojo.connect( this.playerHand, 'onChangeSelection', this, 'onPlayerHandSelectionChanged' );<br />
<br />
<br />
Then find the Player's action comment section and add a handler after the comment:<br />
<br />
<pre><br />
onPlayerHandSelectionChanged : function() {<br />
var items = this.playerHand.getSelectedItems();<br />
<br />
if (items.length > 0) {<br />
if (this.checkAction('playCard', true)) {<br />
// Can play a card<br />
<br />
var card_id = items[0].id;<br />
console.log("on playCard "+card_id);<br />
<br />
this.playerHand.unselectAll();<br />
} else if (this.checkAction('giveCards')) {<br />
// Can give cards => let the player select some cards<br />
} else {<br />
this.playerHand.unselectAll();<br />
}<br />
}<br />
},<br />
</pre><br />
<br />
The function name of the handler is 4th parameter of the dojo.connect function. Make sure you spell it correctly or there will be unpredictable effects.<br />
<br />
Now if you reload, open the Javascript Console (F12), and then click on the card in My Hand, you should see:<br />
on playCard 42<br />
printed on the console<br />
<br />
''Note : You need to be the active player to have rights to play a card and so log your message in the console''<br />
<br />
== Game Database and Game Initialisation ==<br />
<br />
Next step, you want to design a game database and setup a new game (on the server side).<br />
For that we need to a) modify the database schema to add our cards data b) add some global variables into<br />
the existing globals table.<br />
<br />
To modify the schema, first exit your existing game(s). Open '''dbmodel.sql''' file and uncomment the card table creation.<br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `card` (<br />
`card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,<br />
`card_type` varchar(16) NOT NULL,<br />
`card_type_arg` int(11) NOT NULL,<br />
`card_location` varchar(16) NOT NULL,<br />
`card_location_arg` int(11) NOT NULL,<br />
PRIMARY KEY (`card_id`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;<br />
</pre><br />
<br />
This is the "card" table which will be managed by the Deck php class.<br />
<br />
In addition we want a little piece of information in the players table:<br />
<br />
-- add info about first player<br />
ALTER TABLE `player` ADD `player_first` BOOLEAN NOT NULL DEFAULT '0';<br />
<br />
Not sure why they put this into the player table, as we could use a global db variable to hold first player as easily.<br />
But I am just following the existing code more-or-less.<br />
<br />
Next we finally get into .game.php class, where the main logic and db interaction would be. Find php constructor which should be <br />
function __construct( )<br />
This is first function in a file. Add this code to constructor.<br />
<pre><br />
parent::__construct();<br />
self::initGameStateLabels( array( <br />
"currentHandType" => 10, <br />
"trickColor" => 11, <br />
"alreadyPlayedHearts" => 12,<br />
) );<br />
<br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
</pre><br />
<br />
Here we are initializing three "Game State Variables" which are variables stored in the database. They are integers.<br />
It must start with no values lower then 10 since values lower than 10 are reserved. These values are stored by numeric ids<br />
in the database, but in the php we associate them with string labels for convenience of access. The variables are "trickColor": numbers from 1 to 4 that map to card suit (not sure why it's called color; maybe it's a translation from French); "alreadyPlayedHearts": a boolean flag (0 or 1) indicating hether somebody used hearts on the trick; "currentHandType": stores the value to indicate who to give cards to during exchange.<br />
<br />
The next 2 lines are creating $this->cards object and associating it with "card" table in the the database.<br />
<br />
<i>If we called db table 'foo' instead of 'card' the last statement would have been $this->cards->init( "foo" )</i><br />
<br />
<br />
At this point I would start a new game and make sure it starts, then exit. <br />
<br />
<i><br />
If you made a mistake<br />
in the .sql or php constructor the game won't start, and good luck debugging it. (That is why it's important to check<br />
once in a while to make sure it still starts while you remember what you have changed.)<br />
</i><br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/e3a049257b592ff6167688d4d344f8a83d349b08]<br />
<br />
Now we can go to game initialization '''setupNewGame''' in game.php. This method is called once when the table is created.<br />
<br />
In your template project you should have code that deals with player table, just leave it as is. Start inserting the<br />
other code after "Start the game initialization" comment.<br />
<pre><br />
// Init global values with their initial values<br />
<br />
// Note: hand types: 0 = give 3 cards to player on the left<br />
// 1 = give 3 cards to player on the right<br />
// 2 = give 3 cards to player opposite<br />
// 3 = keep cards<br />
self::setGameStateInitialValue( 'currentHandType', 0 );<br />
<br />
// Set current trick color to zero (= no trick color)<br />
self::setGameStateInitialValue( 'trickColor', 0 );<br />
<br />
// Mark if we already played hearts during this hand<br />
self::setGameStateInitialValue( 'alreadyPlayedHearts', 0 );<br />
</pre><br />
<br />
Here we initialize all the globals to 0.<br />
<br />
Next is to create our cards in the database. We have one deck of cards so it's pretty simple.<br />
<pre><br />
// Create cards<br />
$cards = array ();<br />
foreach ( $this->colors as $color_id => $color ) {<br />
// spade, heart, diamond, club<br />
for ($value = 2; $value <= 14; $value ++) {<br />
// 2, 3, 4, ... K, A<br />
$cards [] = array ('type' => $color_id,'type_arg' => $value,'nbr' => 1 );<br />
}<br />
}<br />
<br />
$this->cards->createCards( $cards, 'deck' );<br />
</pre><br />
<br />
This code that will create one of each card. But don't run it yet, because we missing $this->colors.<br />
So we have state of the game in the database, but there is some static game information which never changes.<br />
This information should be stored in material.inc.php and this way it can be accessed from all .php files.<br />
We will edit this file now by adding these lines<br />
<br />
<pre><br />
$this->colors = array(<br />
1 => array( 'name' => clienttranslate('spade'),<br />
'nametr' => self::_('spade') ),<br />
2 => array( 'name' => clienttranslate('heart'),<br />
'nametr' => self::_('heart') ),<br />
3 => array( 'name' => clienttranslate('club'),<br />
'nametr' => self::_('club') ),<br />
4 => array( 'name' => clienttranslate('diamond'),<br />
'nametr' => self::_('diamond') )<br />
);<br />
<br />
$this->values_label = array(<br />
2 =>'2',<br />
3 => '3',<br />
4 => '4',<br />
5 => '5',<br />
6 => '6',<br />
7 => '7',<br />
8 => '8',<br />
9 => '9',<br />
10 => '10',<br />
11 => clienttranslate('J'),<br />
12 => clienttranslate('Q'),<br />
13 => clienttranslate('K'),<br />
14 => clienttranslate('A')<br />
);<br />
</pre><br />
<br />
Where $this->colors will define Suit labels and $this->values_label will define value labels.<br />
If you noticed, we have two of each label for suits. This is because sometimes we need translated values on the php<br />
side and sometimes we don't. In this case '''nametr''' will return a translated value in php, which is only useful when you throw exceptions to show the right strings. If you pass a value to the client via notification you should always use untranslated strings, and the client will translate it. 'clienttranslate' marks the value for translation but does not actually change it for php. For more about this wonderful translation stuff see [[Translations]].<br />
<br />
== Full game model synchronisation ==<br />
<br />
Now at any point in the game we need to make sure that database information can be reflected back in the UI, so we must fix the '''getAllDatas''' function<br />
to return all possible data we need to reconstruct the game. This is in the game.php file. The template for getAllDatas() already takes care of player info. Let's just<br />
add hand and tableau data before we return a result.<br />
<br />
<pre><br />
// Cards in player hand<br />
$result['hand'] = $this->cards->getCardsInLocation( 'hand', $current_player_id );<br />
<br />
// Cards played on the table<br />
$result['cardsontable'] = $this->cards->getCardsInLocation( 'cardsontable' );<br />
</pre><br />
<br />
Now on the client side we should display this data, so in your .js file in the setup function (which is the receiver of getAllDatas) replace our hack of putting 5 of Hearts directly into the hand with:<br />
<br />
<pre><br />
// Cards in player's hand<br />
for ( var i in this.gamedatas.hand) {<br />
var card = this.gamedatas.hand[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
this.playerHand.addToStockWithId(this.getCardUniqueId(color, value), card.id);<br />
}<br />
<br />
// Cards played on table<br />
for (i in this.gamedatas.cardsontable) {<br />
var card = this.gamedatas.cardsontable[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
var player_id = card.location_arg;<br />
this.playCardOnTable(player_id, color, value, card.id);<br />
}<br />
</pre><br />
<br />
This should show hand and tableau cards now, except we are missing the '''playCardOnTable''' function. So find the '''getCardUniqueId''' function which<br />
should be in the utilities section and add this after it:<br />
<br />
<pre><br />
playCardOnTable : function(player_id, color, value, card_id) {<br />
// player_id => direction<br />
dojo.place(this.format_block('jstpl_cardontable', {<br />
x : this.cardwidth * (value - 2),<br />
y : this.cardheight * (color - 1),<br />
player_id : player_id<br />
}), 'playertablecard_' + player_id);<br />
<br />
if (player_id != this.player_id) {<br />
// Some opponent played a card<br />
// Move card from player panel<br />
this.placeOnObject('cardontable_' + player_id, 'overall_player_board_' + player_id);<br />
} else {<br />
// You played a card. If it exists in your hand, move card from there and remove<br />
// corresponding item<br />
<br />
if ($('myhand_item_' + card_id)) {<br />
this.placeOnObject('cardontable_' + player_id, 'myhand_item_' + card_id);<br />
this.playerHand.removeFromStockById(card_id);<br />
}<br />
}<br />
<br />
// In any case: move it to its final destination<br />
this.slideToObject('cardontable_' + player_id, 'playertablecard_' + player_id).play();<br />
},<br />
</pre><br />
<br />
For this to work we also need to add a card template in the .tpl file<br />
<pre><br />
// Javascript HTML templates<br />
<br />
var jstpl_cardontable = '<div class="cardontable" id="cardontable_${player_id}" style="background-position:-${x}px -${y}px">\<br />
</div>';<br />
</pre><br />
<br />
<br />
What this does is basically create another card object, because if it is not our card it's not in our hand (Stock) so<br />
we have to create it out of thin air. The technique to do that is to implement a Javascript template object defined in the .tpl file with some<br />
parameters, which will basically create a "div" string (yes you could have used string concatenation but it would not be fancy).<br />
Now dojo.place places it (the div) on top of a placeholder. Now we have an object with an id of 'cardontable_' + player_id. Depending<br />
on who is playing it we either place it on the player miniboard or in hand (and remove it from hand stock). Then we animate the card move.<br />
<br />
We also should fix our .css file now to add style for cardontable and REMOVE background for playertablecard which really is a placeholder div and not a card. (Don't miss the remove step; it will be all screwy if you do!)<br />
<br />
<pre><br />
.playertablecard {<br />
display: inline-block;<br />
position: relative;<br />
margin-top: 5px;<br />
width: 72px;<br />
height: 96px;<br />
/* we remove background-image here */<br />
}<br />
<br />
/*** cards on table ***/<br />
<br />
.cardontable {<br />
position: absolute;<br />
width: 72px;<br />
height: 96px;<br />
background-image: url('img/cards.jpg'); <br />
}<br />
</pre><br />
<br />
Now to test that it actually works let's deal cards to players during game initialization:<br />
<br />
Add this after createCards in setupNewGame function in the game.php file<br />
<pre><br />
// Shuffle deck<br />
$this->cards->shuffle('deck');<br />
// Deal 13 cards to each players<br />
$players = self::loadPlayersBasicInfos();<br />
foreach ( $players as $player_id => $player ) {<br />
$cards = $this->cards->pickCards(13, 'deck', $player_id);<br />
} <br />
</pre><br />
<br />
Now when you start the game you should see 13 cards in your hand!<br />
<br />
We just need to hook-up clicking on card and test if our playCardOnTable works.<br />
<br />
Find onPlayerHandSelectionChanged function in the JS file, we should have logging there like console.log("on playCard "+card_id);<br />
So after that insert this:<br />
<pre><br />
console.log("on playCard "+card_id);<br />
// type is (color - 1) * 13 + (value - 2)<br />
var type = items[0].type;<br />
var color = Math.floor(type / 13) + 1;<br />
var value = type % 13 + 2;<br />
<br />
this.playCardOnTable(this.player_id,color,value,card_id);<br />
</pre><br />
Note: this code is for testing we will replace it with server interaction after we test it.<br />
<br />
Now if you force reload (because we changed .css before) you should be able to click on card from you have and see it moving,<br />
you can click on few cards this way. When you done enjoying the animation, press F5 to get your hand back.<br />
<br />
[[File:Heartsla-sync.png]]<br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/01d4e2f595fd14c2adcc97a957d21bb2766f78a8]<br />
<br />
== State Machine ==<br />
<br />
Now we need to create a game state machine. So the states are:<br />
<br />
* Cards are dealt to all players (lets call it "newHand")<br />
* Player is selected who will start a new trick ("newTrick")<br />
* Player start or respond to played card ("playerTurn")<br />
* Game control is passed to next player or trick is ended ("nextPlayer")<br />
* End of hand processing (scoring and check for end of game) ("nextHand")<br />
<br />
In addition players can exchange cards so we need two more states for that but we will skip it for now.<br />
<br />
<br />
The state handling spread across 4 files, so you have to make sure all pieces are connected together.<br />
The state machine states.php defines all the states, and function handlers on php side in a form of string,<br />
and if any of these functions are not implemented it would be very hard to debug because it will break in random places.<br />
<br />
So .states.php<br />
<br />
<pre><br />
$machinestates = array(<br />
<br />
// The initial state. Please do not modify.<br />
1 => array(<br />
"name" => "gameSetup",<br />
"description" => clienttranslate("Game setup"),<br />
"type" => "manager",<br />
"action" => "stGameSetup",<br />
"transitions" => array( "" => 20 )<br />
),<br />
<br />
<br />
/// New hand<br />
20 => array(<br />
"name" => "newHand",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNewHand",<br />
"updateGameProgression" => true, <br />
"transitions" => array( "" => 30 )<br />
), <br />
<br />
<br />
<br />
// Trick<br />
<br />
30 => array(<br />
"name" => "newTrick",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNewTrick",<br />
"transitions" => array( "" => 31 )<br />
), <br />
31 => array(<br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must play a card'),<br />
"descriptionmyturn" => clienttranslate('${you} must play a card'),<br />
"type" => "activeplayer",<br />
"possibleactions" => array( "playCard" ),<br />
"transitions" => array( "playCard" => 32 )<br />
), <br />
32 => array(<br />
"name" => "nextPlayer",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNextPlayer",<br />
"transitions" => array( "nextPlayer" => 31, "nextTrick" => 30, "endHand" => 40 )<br />
), <br />
<br />
<br />
// End of the hand (scoring, etc...)<br />
40 => array(<br />
"name" => "endHand",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stEndHand",<br />
"transitions" => array( "nextHand" => 20, "endGame" => 99 )<br />
), <br />
<br />
// Final state.<br />
// Please do not modify.<br />
99 => array(<br />
"name" => "gameEnd",<br />
"description" => clienttranslate("End of game"),<br />
"type" => "manager",<br />
"action" => "stGameEnd",<br />
"args" => "argGameEnd"<br />
)<br />
<br />
);<br />
</pre><br />
<br />
The full details about what these fields are you can find in [[Your_game_state_machine:_states.inc.php]].<br />
<br />
But basically we have Player states, in which human player has to perform an "action" by pressing some button in UI or selecting some game item, which will trigger js handler, which will do ajax call to server into API define <br />
by .action.php file. All functions in this file are API between client and server and has very simple<br />
and repetitive structure. In this case there is only two action player can do - play a card or pass cards to other player. So these 2 functions go into .action.php file, we will only define one now since we have not implemented card passing states yet:<br />
<br />
<pre><br />
public function playCard() {<br />
self::setAjaxMode();<br />
$card_id = self::getArg("id", AT_posint, true);<br />
$this->game->playCard($card_id);<br />
self::ajaxResponse();<br />
}<br />
</pre><br />
<br />
<br />
Now to make it run we have define all handler functions that we referenced in states, which are - one function for state arguments argGiveCards, 4 functions for robot states (where game performs some action)<br />
and 1 function for player actions handling.<br />
Find 'Game state arguments' section and paste this in:<br />
<pre><br />
function argGiveCards() {<br />
return array ();<br />
}<br />
</pre><br />
<br />
This normally pass some parameters to states, but we don't need anything yet. It good to have placeholder there anyway, so we can fix it later.<br />
Important: even when its a stub this function must return array not scalar.<br />
<br />
Lets do stubs for other functions, find game state actions section in .game.php file and insert these<br />
<pre><br />
function stNewHand() {<br />
// Take back all cards (from any location => null) to deck<br />
$this->cards->moveAllCardsInLocation(null, "deck");<br />
$this->cards->shuffle('deck');<br />
// Deal 13 cards to each players<br />
// Create deck, shuffle it and give 13 initial cards<br />
$players = self::loadPlayersBasicInfos();<br />
foreach ( $players as $player_id => $player ) {<br />
$cards = $this->cards->pickCards(13, 'deck', $player_id);<br />
// Notify player about his cards<br />
self::notifyPlayer($player_id, 'newHand', '', array ('cards' => $cards ));<br />
}<br />
self::setGameStateValue('alreadyPlayedHearts', 0);<br />
$this->gamestate->nextState("");<br />
}<br />
<br />
function stNewTrick() {<br />
// New trick: active the player who wins the last trick, or the player who own the club-2 card<br />
// Reset trick color to 0 (= no color)<br />
self::setGameStateInitialValue('trickColor', 0);<br />
$this->gamestate->nextState();<br />
}<br />
<br />
function stNextPlayer() {<br />
// Active next player OR end the trick and go to the next trick OR end the hand<br />
if ($this->cards->countCardInLocation('cardsontable') == 4) {<br />
// This is the end of the trick<br />
// Move all cards to "cardswon" of the given player<br />
$best_value_player_id = self::activeNextPlayer(); // TODO figure out winner of trick<br />
$this->cards->moveAllCardsInLocation('cardsontable', 'cardswon', null, $best_value_player_id);<br />
<br />
if ($this->cards->countCardInLocation('hand') == 0) {<br />
// End of the hand<br />
$this->gamestate->nextState("endHand");<br />
} else {<br />
// End of the trick<br />
$this->gamestate->nextState("nextTrick");<br />
}<br />
} else {<br />
// Standard case (not the end of the trick)<br />
// => just active the next player<br />
$player_id = self::activeNextPlayer();<br />
self::giveExtraTime($player_id);<br />
$this->gamestate->nextState('nextPlayer');<br />
}<br />
}<br />
<br />
function stEndHand() {<br />
$this->gamestate->nextState("nextHand");<br />
}<br />
<br />
</pre><br />
Important: All state actions game or player must end with state transition (or thrown exception). Also make sure its ONLY one state transition,<br />
if you accidentally fall though after state transition and do another one it will be a real mess and head scratching for long time.<br />
<br />
Now find 'player actions' section and paste this code there<br />
<pre><br />
function playCard($card_id) {<br />
self::checkAction("playCard");<br />
$player_id = self::getActivePlayerId();<br />
throw new BgaUserException(self::_("Not implemented: ") . "$player_id plays $card_id");<br />
}<br />
</pre><br />
We won't implement it yet but throw an exception which we will see if interaction is working properly<br />
<br />
Now the game should start but it would not be any different then before because we have to implement actual interactions.<br />
Its good to check if it still working though (and if it was running before you have to exit because we changed state machine and normally it will break stuff)<br />
<br />
== Client - Server interactions ==<br />
<br />
Now to implement things for real we have hook UI actions to ajax calls, and process notifications send by server.<br />
So previously we hooked playCardOnTable right into js handler which caused client animation, in real game its a two<br />
step operation. When user clicks on game element js client sends an ajax call to server, server processes it and updates database, server sends<br />
notification in response, client hooks animations to server notification.<br />
<br />
So in .js code replace onPlayerHandSelectionChanged with<br />
<pre><br />
onPlayerHandSelectionChanged : function() {<br />
var items = this.playerHand.getSelectedItems();<br />
<br />
if (items.length > 0) {<br />
var action = 'playCard';<br />
if (this.checkAction(action, true)) {<br />
// Can play a card<br />
var card_id = items[0].id; <br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", {<br />
id : card_id,<br />
lock : true<br />
}, this, function(result) {<br />
}, function(is_error) {<br />
});<br />
<br />
this.playerHand.unselectAll();<br />
} else if (this.checkAction('giveCards')) {<br />
// Can give cards => let the player select some cards<br />
} else {<br />
this.playerHand.unselectAll();<br />
}<br />
}<br />
},<br />
</pre><br />
<br />
<br />
Now when you click on card you should get a server response: Not implemented...<br />
<br />
Lets implement it, in .game.php<br />
<pre><br />
function playCard($card_id) {<br />
self::checkAction("playCard");<br />
$player_id = self::getActivePlayerId();<br />
$this->cards->moveCard($card_id, 'cardsontable', $player_id);<br />
// XXX check rules here<br />
$currentCard = $this->cards->getCard($card_id);<br />
// And notify<br />
self::notifyAllPlayers('playCard', clienttranslate('${player_name} plays ${value_displayed} ${color_displayed}'), array (<br />
'i18n' => array ('color_displayed','value_displayed' ),'card_id' => $card_id,'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),'value' => $currentCard ['type_arg'],<br />
'value_displayed' => $this->values_label [$currentCard ['type_arg']],'color' => $currentCard ['type'],<br />
'color_displayed' => $this->colors [$currentCard ['type']] ['name'] ));<br />
// Next player<br />
$this->gamestate->nextState('playCard');<br />
}<br />
</pre><br />
<br />
We get the card from client, we move it to the tableau (moveCard is hooked to database directly, its part of deck class),<br />
we notify all players and we change state. What we missing here is bunch of checks (rule enforcements), we will add it later.<br />
<br />
Interesting part about this notify is that we use i18n array for string that needs to be translated by client, so<br />
they sent as English text in notification, then client has to know which parameters needs translating.<br />
<br />
On the client side .js we have to implement a notification handler to do the animation<br />
<br />
<pre><br />
setupNotifications : function() {<br />
console.log('notifications subscriptions setup');<br />
<br />
dojo.subscribe('newHand', this, "notif_newHand");<br />
dojo.subscribe('playCard', this, "notif_playCard");<br />
<br />
},<br />
<br />
notif_newHand : function(notif) {<br />
// We received a new full hand of 13 cards.<br />
this.playerHand.removeAll();<br />
<br />
for ( var i in notif.args.cards) {<br />
var card = notif.args.cards[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
this.playerHand.addToStockWithId(this.getCardUniqueId(color, value), card.id);<br />
}<br />
},<br />
<br />
notif_playCard : function(notif) {<br />
// Play a card on the table<br />
this.playCardOnTable(notif.args.player_id, notif.args.color, notif.args.value, notif.args.card_id);<br />
},<br />
</pre><br />
<br />
Now it actually works through the server when you click on card - the move is recorded. If you testing it now you will notice<br />
after trick is done all cards remains on the table, but if you press F5 they would disappear, this is because<br />
we updated database to pick-up the cards but did not send notification about it, so we need to send notification about it<br />
and have a handler for it<br />
<br />
So in .game.php file add notification in stNextPlayer function after moveAllCardsInLocation call:<br />
<br />
<pre><br />
// Notify<br />
// Note: we use 2 notifications here in order we can pause the display during the first notification<br />
// before we move all cards to the winner (during the second)<br />
$players = self::loadPlayersBasicInfos();<br />
self::notifyAllPlayers( 'trickWin', clienttranslate('${player_name} wins the trick'), array(<br />
'player_id' => $best_value_player_id,<br />
'player_name' => $players[ $best_value_player_id ]['player_name']<br />
) ); <br />
self::notifyAllPlayers( 'giveAllCardsToPlayer','', array(<br />
'player_id' => $best_value_player_id<br />
) );<br />
</pre><br />
<br />
And in .js file add 2 more notification handlers.<br />
<br />
This is to subscribe in setupNotifications function<br />
<pre><br />
dojo.subscribe( 'trickWin', this, "notif_trickWin" );<br />
this.notifqueue.setSynchronous( 'trickWin', 1000 );<br />
dojo.subscribe( 'giveAllCardsToPlayer', this, "notif_giveAllCardsToPlayer" );<br />
</pre><br />
<br />
And this are handlers<br />
<pre><br />
notif_trickWin : function(notif) {<br />
// We do nothing here (just wait in order players can view the 4 cards played before they're gone.<br />
},<br />
notif_giveAllCardsToPlayer : function(notif) {<br />
// Move all cards on table to given table, then destroy them<br />
var winner_id = notif.args.player_id;<br />
for ( var player_id in this.gamedatas.players) {<br />
var anim = this.slideToObject('cardontable_' + player_id, 'overall_player_board_' + winner_id);<br />
dojo.connect(anim, 'onEnd', function(node) {<br />
dojo.destroy(node);<br />
});<br />
anim.play();<br />
}<br />
},<br />
</pre><br />
<br />
So 'trickWin' notification does not do much except it will delay the processing of next notification by 1 second (1000 ms)<br />
and it will log the message (that happens independent of what handler does).<br />
<i>Note: if on the other hand you don't want to log but what what to do something else send empty message</i><br />
<br />
Now after the trick you see all cards move the "player's stash".<br />
<br />
== Scoring and End of game handling ==<br />
<br />
Now we should calculate scoring and for that we need to actually track who wins the trick.<br />
Trick is won by the player with highest card (no trump). We just need to remember what is trick suite.<br />
For which we will use state variable 'trickColor' which we already conveniently created.<br />
<br />
In .game.php file find playCard function and add this before notify functions<br />
$currentTrickColor = self::getGameStateValue( 'trickColor' ) ;<br />
if( $currentTrickColor == 0 )<br />
self::setGameStateValue( 'trickColor', $currentCard['type'] );<br />
<br />
This will make sure we remember first suit being played, now to use it modify stNextPlayer function to fix our TODO comment<br />
<pre><br />
function stNextPlayer() {<br />
// Active next player OR end the trick and go to the next trick OR end the hand<br />
if ($this->cards->countCardInLocation('cardsontable') == 4) {<br />
// This is the end of the trick<br />
$cards_on_table = $this->cards->getCardsInLocation('cardsontable');<br />
$best_value = 0;<br />
$best_value_player_id = null;<br />
$currentTrickColor = self::getGameStateValue('trickColor');<br />
foreach ( $cards_on_table as $card ) {<br />
// Note: type = card color<br />
if ($card ['type'] == $currentTrickColor) {<br />
if ($best_value_player_id === null || $card ['type_arg'] > $best_value) {<br />
$best_value_player_id = $card ['location_arg']; // Note: location_arg = player who played this card on table<br />
$best_value = $card ['type_arg']; // Note: type_arg = value of the card<br />
}<br />
}<br />
}<br />
<br />
// Active this player => he's the one who starts the next trick<br />
$this->gamestate->changeActivePlayer( $best_value_player_id );<br />
<br />
// Move all cards to "cardswon" of the given player<br />
$this->cards->moveAllCardsInLocation('cardsontable', 'cardswon', null, $best_value_player_id);<br />
<br />
// Notify<br />
// ... same code here as before<br />
</pre><br />
<br />
The scoring rule in the studio example code is huge multi-page function, for this tutorial we will make simplier.<br />
Lets score -1 point per heart and call it a day. And game will end when somebody goes -100 or below.<br />
<br />
As UI goes for scoring, the main thing to update is the scoring on the mini boards represented by stars, also<br />
we want to show that in the log. <br />
In addition scoring can be shown in [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialog]] using tableWindow notification, but it is a tutorial on its own and you can do it as homework (it is part of original heart game).<br />
<br />
In .js file we need to add one more subscription and notification handler:<br />
dojo.subscribe( 'newScores', this, "notif_newScores" );<br />
in setupNotifications<br />
<br />
and <br />
notif_newScores : function(notif) {<br />
// Update players' scores<br />
for ( var player_id in notif.args.newScores) {<br />
this.scoreCtrl[player_id].toValue(notif.args.newScores[player_id]);<br />
}<br />
},<br />
somewhere after. this.scoreCtrl is pre-existing object that shows the scoring and this function will update score values per player from notification argument<br />
<br />
so in .game.php our stEndHand function will look like<br />
<br />
<pre><br />
function stEndHand() {<br />
// Count and score points, then end the game or go to the next hand.<br />
$players = self::loadPlayersBasicInfos();<br />
// Gets all "hearts" + queen of spades<br />
<br />
$player_to_points = array ();<br />
foreach ( $players as $player_id => $player ) {<br />
$player_to_points [$player_id] = 0;<br />
}<br />
$cards = $this->cards->getCardsInLocation("cardswon");<br />
foreach ( $cards as $card ) {<br />
$player_id = $card ['location_arg'];<br />
// Note: 2 = heart<br />
if ($card ['type'] == 2) {<br />
$player_to_points [$player_id] ++;<br />
}<br />
}<br />
// Apply scores to player<br />
foreach ( $player_to_points as $player_id => $points ) {<br />
if ($points != 0) {<br />
$sql = "UPDATE player SET player_score=player_score-$points WHERE player_id='$player_id'";<br />
self::DbQuery($sql);<br />
$heart_number = $player_to_points [$player_id];<br />
self::notifyAllPlayers("points", clienttranslate('${player_name} gets ${nbr} hearts and looses ${nbr} points'), array (<br />
'player_id' => $player_id,'player_name' => $players [$player_id] ['player_name'],<br />
'nbr' => $heart_number ));<br />
} else {<br />
// No point lost (just notify)<br />
self::notifyAllPlayers("points", clienttranslate('${player_name} did not get any hearts'), array (<br />
'player_id' => $player_id,'player_name' => $players [$player_id] ['player_name'] ));<br />
}<br />
}<br />
$newScores = self::getCollectionFromDb("SELECT player_id, player_score FROM player", true );<br />
self::notifyAllPlayers( "newScores", '', array( 'newScores' => $newScores ) );<br />
<br />
///// Test if this is the end of the game<br />
foreach ( $newScores as $player_id => $score ) {<br />
if ($score <= -100) {<br />
// Trigger the end of the game !<br />
$this->gamestate->nextState("endGame");<br />
return;<br />
}<br />
}<br />
<br />
<br />
$this->gamestate->nextState("nextHand");<br />
}<br />
</pre><br />
<br />
<br />
So it should more less work now, including end of game condition. Try to play it!<br />
<br />
== Additional stuff ==<br />
<br />
The following things were not implemented and can add them yourself by looking at the code of original hearts game:<br />
<br />
* Remove debug code from setupNewGame to deal cards, cards are now dealt in stNewHand state handler<br />
* Rule checking and rule enforcements in playCard function<br />
* Start scoring with 100 points each and end when <= 0<br />
* Fix scoring rules with Q of spades and 26 point reverse scoring<br />
* First player one with 2 club<br />
* Add progress handling<br />
* Add statistics<br />
* Add card exchange states<br />
* Add game option to start with 75 points instead of 100</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Translations&diff=4352
Translations
2020-05-10T13:56:39Z
<p>Cpasbanal: /* On server side (PHP) */ minor typo on transate -> translate</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
<br />
Using BGA Studio, the game you create is ready to be translated to each language by the BGA community. To make this possible, you only need to specify which string must be translated and how to combine them.<br />
<br />
== How translation works? ==<br />
<br />
When developing your game, all strings must be in English. Strings must be coherent with the English version of the game.<br />
<br />
Before the release of the game, BGA team will do the French translation of the game.<br />
<br />
After the release of the game, the BGA players community will translate the game in every language.<br />
<br />
== What should be translated? ==<br />
<br />
Every text that can be visible by the player when the game is running normally. This includes tooltips, texts on cards, error messages, ...<br />
<br />
This does NOT include error messages that are not supposed to happen (unexpected errors).<br />
<br />
== What rules should I follow for the original English strings? ==<br />
<br />
For a coherent and homogeneous interface, here are some rules about ending a sentence with a final dot '.'<br />
<br />
* As a general rule:<br />
** If a sentence is displayed isolated in the interface => no final dot<br />
** If a sentence is followed or could be followed by another sentence in the same interface space => final dot.<br />
<br />
* In detail:<br />
** No final dot:<br />
*** button labels<br />
*** section titles<br />
*** menu elements<br />
*** links triggering an isolated action<br />
*** anything that is not a full sentence<br />
*** current action in the status bar<br />
** Final dot:<br />
*** complete explanation sentence, that can be chained with another sentence<br />
** We can tolerate a dot or no dot (but it should be consistent inside the game) for:<br />
*** isolated tooltip / isolated small sentence<br />
*** game log (no dot is usually preferable)<br />
*** error messages (except if more than one sentence in the error message => final dot mandatory in this case)<br />
<br />
Otherwise, you should try to follow as closely as possible the general style and format (including capitalization) used in the English rulebook and game material of the game.<br />
<br />
== Focus on translating notifications ==<br />
<br />
Usually, translating a website is simple: you just call a function on every string you have to translate, and the string is translated in the player's language. On Board Game Arena, this is exactly the same with the "_( string )" function.<br />
<br />
However, there is one difference on BGA: notifications. The server is sending notifications to players, and most of the time the notifications are the same for every players, no matter what language each player is using. This is why notifications are translated on client side in the proper language, even if the strings are defined on server side.<br />
<br />
== WARNING: how to make sure your strings will be translated ==<br />
<br />
For each game, our translation tool is doing a full scan of the code, looking for translator markers like "_()" or "clientranslate()"... (see below the list of translation markers).<br />
<br />
If your original string is not "physically" inside one of this marker, it won't be translated.<br />
<br />
<pre><br />
// Examples: the following strings will be translated:<br />
var mystring_translated = _("my string"); // JS<br />
$mystring_translated = self::_("my string"); // PHP<br />
$mystring_translated = sprintf( _("my string with an %s argument"), $argument ); // PHP<br />
<br />
// Examples: the following strings WILL NOT be translated:<br />
$my_string = "my string";<br />
$not_translated = self::_( $my_string ); // The original string is not bordered by a translator marker => no translation<br />
$not_translated = self::_( sprintf( "my string with a %s argument", $argument ) ); // Same thing<br />
</pre><br />
<br />
== How to not make translators crazy ;) ==<br />
<br />
* When you need the same string twice, try to reuse exactly the same string (with the same case) to minimize the number of strings.<br />
* Do not mark as translatable a game element that does not have to be translated (ex: if the name of a monster on a card is "Zzzzz", maybe there's no need to translate it).<br />
* Words does not come in the same order in each language. Thus, when you have to translate a string with an argument, do not write something like:<br />
<pre>_("First part of the string, ").$argument.' '._("second part of the string")</pre><br />
Write instead:<br />
<pre>sprintf( _("First part of the string, %s second part of the string"), $argument )</pre><br />
(or the equivalent "dojo.string.substitute" in Javascript)<br />
* When translators are going to translate your game, the most difficult task for them is to get the context of the string to be translated. The more the string is a short insignificant string, the more difficult is the task for them. As a rule of thumb, try to avoid insignificant short strings. You can also leave a comment on what is the context of the string in the translation program (English to English) if you are the developer of the game.<br />
* The BGA translation policy is to be flexible on grammar... We prefer to write "player gets 1 coin(s)" than write two versions of the same string for plural and singular - it reduces the number of strings to translate.<br />
* Instead of writing nice strings like "With the effect of ZZZ, player XXX gets a new YYY", which is very difficult to translate, write strings like "ZZZ: XXX gets YYY".<br />
* Use present tense instead of past, i.e. "player gets wood" instead of "player got wood"<br />
* Avoid using gender specific pronouns, i.e. "player returns card to their hand" instead of "player returns card to his hand"<br />
<br />
== On client side (Javascript) ==<br />
<br />
On client side, things are quite simple: you just have to use the "_()" function for all strings you want to translate.<br />
<br />
Examples:<br />
<pre><br />
// Get a string in player's language:<br />
var translated = _("original english string");<br />
<br />
// Get a string in player's language with parameter:<br />
var translated = dojo.string.substitute( _("You can pick ${p} cards and discard ${d}"), {<br />
p: 2,<br />
d: 4<br />
} );<br />
</pre><br />
<br />
'''WARNING:''' in Javascript strings to translate, you should never use '\n', '\t' or such, as it will break the translation bundle and result in all the Javascript translation to fail. In any case, the strings will result in HTML code, and such character codes won't have any impact on the HTML rendering. You should use HTML markup instead.<br />
<br />
== On server side (PHP) ==<br />
<br />
On PHP side, you can use 3 different functions to specify that a string must be translated.<br />
<br />
'''clienttranslate( "my string to translate" ):'''<br />
<br />
This function is '''transparent''': it will return the original English string without any change. It's only purpose is to mark this string as "must be translated", and to make sure the translated version of the string will be available on client side.<br />
<br />
In general, you use clienttranslate:<br />
* On your states.inc.php, for field "description" and "descriptionmyturn".<br />
<br />
<pre><br />
"description" => clienttranslate('${card_name}: ${actplayer} must discard 4 identical energies'),<br />
</pre><br />
<br />
* On "material.inc.php", when defining texts for game material that must be displayed on client side.<br />
<pre><br />
$this->card_types = array(<br />
<br />
1 => array(<br />
'name' => clienttranslate("Amulet of Air"), // Thus, we can use "_( card_name )" on Javascript side.<br />
</pre><br />
<br />
* When sending a notification with "notifyAllPlayers" or "notifyPlayer", for the game log string and all game log arguments that need a translation.<br />
<br />
<pre><br />
// A game log string with no argument:<br />
self::notifyAllPlayers( 'pickLibraryCards', clienttranslate("Everyone draw cards from his library"), array() );<br />
</pre><br />
<br />
Translating arguments is a little bit more complex. It is using the "i18n" special argument as below:<br />
<br />
<pre><br />
// In the following example, we translate the game log itself, but also the "card_name" argument:<br />
<br />
self::notifyAllPlayers( 'winPoints', clienttranslate('${card_name}: ${player_name} gains ${points} point(s)'), array(<br />
'i18n' => array( 'card_name' ), // <===== We specify here that "card_name" argument must be translated<br />
'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),<br />
'points' => $points,<br />
'card_name' => $this->card_types[8]['name'] // <==== Here, we provide original English string.<br />
) ); <br />
</pre><br />
<br />
Pay attention when using 'i18n' argument when translating argument for client : do NOT use same argument for both translation AND key code for client side action (like using 'card_name' to move it on player board as described in the example). It's pretty obvious in the example, but it can be very tricky when translation is made at the end of the development (which is often the case). Use explicit argument name like 'card_name_translated' by example.<br />
<br />
'''self::_( "my string to translate" ):'''<br />
<br />
This function returns a string translated in the language of CURRENT user (ie: player who send the request to the server) (be careful, this is NOT the active player).<br />
<br />
Most of the time, you don't need to translate strings on server side, except on the following 3 situations:<br />
* When throwing an exception because the player did a forbidden move.<br />
<br />
<pre><br />
// This will display a translatable red message to the player that just do some wrong action:<br />
throw new BgaUserException( self::_('You must choose 3 cards') );<br />
<br />
// ... notice the use of BgaUserException that signals that this exception is "expected". In theory, all exception that are expected should be translated.<br />
</pre><br />
<br />
* In "yourgame.view.php", when creating the labels for the game interface used in your template (.tpl) file.<br />
<br />
<pre><br />
$this->tpl['CARDS_FOR_YEAR_2'] = self::_("Your cards for year II");<br />
</pre><br />
<br />
* Eventually, in your material.inc.php, if for example you need to use some string elements in your exceptions.<br />
<br />
<pre><br />
// In material.inc.php, $this->energies[n]['nametr'] has been created with the self::_() method. This we can do this:<br />
throw new BgaUserException( self::_("To execute this action you need more: ").' '.$this->energies[$resource_id]['nametr'] );<br />
</pre><br />
<br />
* Eventually, in your "getAllDatas" PHP method, as the data return by this method is used only by current user.<br />
<br />
'''totranslate( "my string to translate" ):'''<br />
<br />
This function works exactly like 'clienttranslate', except it tells BGA that the string is not needed on client side.<br />
<br />
You should not use this function, except on the following cases:<br />
* Statistics name in stats.inc.php<br />
* Option names and option values name in gameoptions.inc.php</div>
Cpasbanal
http://en.doc.boardgamearena.com/index.php?title=Tutorial_hearts&diff=4351
Tutorial hearts
2020-05-10T13:11:51Z
<p>Cpasbanal: /* Game Interface JS Stock */ corrected some minor typo</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Introduction ==<br />
<br />
Using this tutorial, you can build a complete working game on the BGA environment: Hearts.<br />
<br />
Before you read this tutorial, you must:<br />
* Read the overall presentations of the BGA Framework ([[Studio|see here]]).<br />
* Know the rules for Hearts<br />
* Some-what know the languages used on BGA: PHP, SQL, HTML, CSS, Javascript<br />
* Set up your development environment [http://en.doc.boardgamearena.com/First_steps_with_BGA_Studio First Steps with BGA Studio]<br />
* As part of setup you have to have access to your ftp home folder in studio, which would have 'hearts' game source code. We will be using some resources of this game in this tutorial, so copy it over to local disk if you have not done so.<br />
<br />
If you are stuck or have question about this tutorial, post on [https://forum.boardgamearena.com/viewforum.php?f=12 BGA Developers forum]<br />
<br />
== Create your first game ==<br />
<br />
If you have not already, you have to create a project in BGA Studio. For this tutorial you can create a project heartsYOURNAME where<br />
YOURNAME is your developer login name. You can also re-use the project you have created for the "First Steps" tutorial above.<br />
With the initial skeleton of code provided, you can already start a game from the BGA Studio. <br />
<br />
1. Find and start the game in turn-based mode with 4 players. Make sure it works. <br />
<br />
2. Modify the text in heartsYOURNAME_heartsYOURNAME.tpl, reload the page in the browser and make sure your ftp sync works as expected.<br />
Note: if you have not setup auto-sync do it now, manually copying files is a no-starter.<br />
<br />
3. Express stop from settings menu (the gear icon).<br />
<br />
<i>Note: please do '''not''' use the hearts project code as a base. This tutorial assumes you started with a TEMPLATE project with no prior modifications. Using the hearts project as a base will be very confusing and you won't be able to follow all the steps.<br />
</i><br />
<br />
== Hook version control system ==<br />
<br />
For a real game, or even for this tutorial, we recommend committing the code to version control right from the start. You are going to find yourself in a situation where the game doesn't even start anymore and no way of debugging it, unless you have a way to revert. That is where version control becomes very handy. If you are not familiar with version control (e.g. [https://git-scm.com/docs/gittutorial git]) then at least back up your files after each major change. Start now.<br />
<br />
Code for this tutorial available is on github: https://github.com/elaskavaia/bga-heartsla<br />
<br />
Different revisions represent different steps along the process, starting from original template to a complete game.<br />
<br />
== Update game infos and box graphics ==<br />
<br />
Even it does not nothing yet, always start by making sure the game looks decent in the game selector, meaning it has nice box graphics and its information is correct. For that we need to edit [[Game_meta-information: gameinfos.inc.php|gameinfos.inc.php]].<br />
<br />
For a real game, you would go to [http://boardgamegeek.com BoardGameGeek], find the game, and use the information from BGG to fill in the gameinfos.<br />
<br />
So let's do that. Find "hearts" on BoardGameGeek. (Hint: Original release 1850 :))<br />
<br />
You can fill in the year of publishing and bgg id, put ''Public Domain'' under publisher, and a publisher id of 171 for public domain. And as designer and author you can just put your own name just for fun. Set number of players to 4.<br />
<br />
// Players configuration that can be played (ex: 2 to 4 players)<br />
'players' => array( 4 ), <br />
<br />
<br />
The next step is to replace '''game_box.png''' with nicer images. For this tutorial, just copy all the files from the img/ folder of the hearts/ template into the img/ directory of your project. Replace publisher.png with a nicer image: for example https://github.com/elaskavaia/bga-sharedcode/blob/master/img/publisher.png.<br />
<br />
Details about images can be found here: [[Game art: img directory]].<br />
<br />
Now an important step: you have to LOAD these files into the Studio website through the control panel. So go to Control Panel -> Manage Games -> heartsYOURNAME<br />
and press Reload for 'Reload game informations' and 'Reload game box image'<br />
<br />
Now try to start the game again. If you somehow introduced a syntax error in the gameinfos file it may not work (the game won't start).<br />
Always use the "Express Start" button to start the game. You should see a standard state prompt from the template. You should see 4 players on the right: testdude0 .. testdude3.<br />
To switch between them press the red arrow button near their names, it will open another tab. This way you don't need to login and logout from multiple accounts!<br />
<br />
<br />
<i>Note: if you had run the game before with less than 4 players there is a bug that will prevent you from running it with 4 only (if you did not run it before or run it with 4 players as instructed stop reading this note), to workaround revert back to original players array (i.e. 1,2,3,4), reload game options, then create a table with 4 players, exit that game table, then change gameoptions to 4 only as above, reload game options, create table again.</i><br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/4b3a73eeb5acae961ade18473af119e8ce8d1a8f]<br />
<br />
== Layout and Graphics ==<br />
<br />
In this section we will do graphics of the game, and main layout of the game.<br />
<br />
First copy a sprite with cards image from hearts img/cards.jpg into img/ folder of your project. Project hearts is mounted to your home directory on bga server.<br />
<br />
Edit .tpl to add some divs to represent player table and hand area<br />
<br />
<pre><br />
<div id="myhand_wrap" class="whiteblock"><br />
<h3>My Hand</h3><br />
<div id="myhand"><br />
</div><br />
</div><br />
</pre><br />
<br />
If you refresh you should see now white area with My Hand title.<br />
<br />
<br />
[[File:Heartsla-tpl2.png]]<br />
<br />
Now lets add a card into the hand, just so you can feel it. Edit .tpl and a playertablecard div inside a hand div<br />
<pre><br />
...<br />
<div id="myhand"><br />
<div class="playertablecard"></div><br />
</div><br />
...<br />
</pre><br />
<br />
Edit .css file<br />
<pre><br />
.playertablecard {<br />
display: inline-block;<br />
position: relative;<br />
margin-top: 5px;<br />
width: 72px;<br />
height: 96px;<br />
background-image: url('img/cards.jpg'); /* temp hack to see it */<br />
}<br />
</pre><br />
<br />
When you edit CSS remember that you have to FORCE-reload page, i.e. Ctrl-F5, otherwise its cached.<br />
<i>Same when you change existing graphics files</i>.<br />
<br />
You should see this:<br />
<br />
[[File:Heartsla-tpl3.png]]<br />
<br />
Awesome! Now lets do the rest of layout.<br />
<br />
There are few ways of how html could have been generated, you could have start with nothing and generate<br />
all by java script. Or you could have started with complete game markup in html and make java script just hide and move pieces around. BGA framework provides also a third way which is mix of both plus template engine to generate HTML using php. So lets do that.<br />
<br />
Change .tpl file to have this inside<br />
<pre><br />
<div id="playertables"><br />
<br />
<!-- BEGIN player --><br />
<div class="playertable whiteblock playertable_{DIR}"><br />
<div class="playertablename" style="color:#{PLAYER_COLOR}"><br />
{PLAYER_NAME}<br />
</div><br />
<div class="playertablecard" id="playertablecard_{PLAYER_ID}"><br />
</div><br />
</div><br />
<!-- END player --><br />
<br />
</div><br />
<br />
<div id="myhand_wrap" class="whiteblock"><br />
<h3>{MY_HAND}</h3><br />
<div id="myhand"><br />
</div><br />
</div><br />
</pre><br />
<br />
What we did is we added "block" player, it is marked up using html comments. {VAR} notation is used<br />
to inject variables and <br />
<pre><br />
<!-- BEGIN xxx --> <br />
inside <br />
<!-- END xxx --> <br />
</pre><br />
effectively allows us to do template loops.<br />
<br />
In .view.php insert this code after 'Place your code below' comment<br />
<br />
<br />
<pre><br />
$template = self::getGameName() . "_" . self::getGameName();<br />
<br />
$directions = array( 'S', 'W', 'N', 'E' );<br />
<br />
// this will inflate our player block with actual players data<br />
$this->page->begin_block($template, "player");<br />
foreach ( $players as $player_id => $info ) {<br />
$dir = array_shift($directions);<br />
$this->page->insert_block("player", array ("PLAYER_ID" => $player_id,<br />
"PLAYER_NAME" => $players [$player_id] ['player_name'],<br />
"PLAYER_COLOR" => $players [$player_id] ['player_color'],<br />
"DIR" => $dir ));<br />
}<br />
// this will make our My Hand text translatable<br />
$this->tpl['MY_HAND'] = self::_("My hand");<br />
</pre><br />
<br />
What it does is for each player we have it will replicate the html between <!-- BEGIN player --> and <!-- END player --> tags, substituting the variable denoted by {XXX}<br />
with the values you provide. The DIR variable in this case we pulling from directions array (where array_shift will take first element and remove it from the array).<br />
<br />
Reload. If everything went well you should see this:<br />
<br />
[[File:Heartsla-tpl4.png]]<br />
<br />
These are "tableau" areas for 4 players plus My hand visible only to one player.<br />
They are not exactly how we wanted them to be because we did not edit .css yet.<br />
<br />
Now edit .css, add these lines after import before our previous definition<br />
<br />
<pre><br />
/** Table layout **/<br />
<br />
#playertables {<br />
position: relative;<br />
width: 710px;<br />
height: 340px;<br />
}<br />
<br />
.playertablename {<br />
font-weight: bold;<br />
}<br />
<br />
.playertable {<br />
position: absolute;<br />
text-align: center;<br />
width: 180px;<br />
height: 130px;<br />
}<br />
<br />
.playertable_N {<br />
left: 50%;<br />
top: 0px;<br />
margin-left: -90px; /* half of 180 */<br />
}<br />
.playertable_S {<br />
left: 50%;<br />
bottom: 0px;<br />
margin-left: -90px; /* half of 180 */<br />
}<br />
.playertable_W {<br />
left: 0px;<br />
top: 50%;<br />
margin-top: -55px; /* half of 130 */<br />
}<br />
.playertable_E {<br />
right: 0px;<br />
top: 50%;<br />
margin-top: -55px; /* half of 130 */<br />
}<br />
</pre><br />
<br />
<br />
Now you force Reload and you should see this:<br />
[[File:Heartsla-tpl5.png]]<br />
<br />
This is almost all we need for graphics and layout, there are few tweaks left there but lets do some more heavy lifting now.<br />
<br />
<i>Note: if you did not see changes you may have not force reloaded, force means you use Ctrl+F5 or Cltr+Shift-R, if you don't "force" browser will use cached version of .css and images! Which is not what you just changed</i><br />
<br />
<br />
<i>Another Note: In general if you have auto-sync you don't need to reload if you change game.php file, you need normal reload if you change js, and force reload for css and images. If you changed state machine or database you likely need to restart the game.</i><br />
<br />
== Game Interface JS Stock ==<br />
<br />
The BGA framework provides a few out of the box classes to deal with cards. The client side<br />
contains a class called [[Stock]] and it can be used for any dynamic html "pieces" management that uses<br />
common sprite images. On the server side we will use the [[Deck]] class which we discuss later.<br />
<br />
If you open cards.jpg in an image viewer you will see that it is a "sprite" image - a 13x4 grid of images stitched together,<br />
which is a very efficient way to transport images. So we will use the Stock class to mark up these images and create<br />
"card" divs for us.<br />
<br />
First, we need to add '''ebg/stock''' as a dependency in the hearts.js file:<br />
<pre><br />
define([<br />
"dojo","dojo/_base/declare",<br />
"ebg/core/gamegui",<br />
"ebg/counter",<br />
"ebg/stock" /// <==== HERE<br />
],<br />
</pre><br />
<br />
Then add this to the Javascript contructor, this will define size of our cards<br />
<pre><br />
console.log('hearts constructor');<br />
this.cardwidth = 72;<br />
this.cardheight = 96;<br />
</pre><br />
<br />
The stock is initialized in the Javascript "setup" method like this:<br />
<pre><br />
// TODO: Set up your game interface here, according to "gamedatas"<br />
<br />
// Player hand<br />
this.playerHand = new ebg.stock(); // new stock object for hand<br />
this.playerHand.create( this, $('myhand'), this.cardwidth, this.cardheight );<br />
</pre><br />
<br />
As parameters of the "create" method, we provided the width/height of an item (a card), and the container div "myhand" - which is an id of "div" element from our .tpl file representing a player hand.<br />
<br />
<br />
Then, we must tell the stock what items it is going to display during its life: the 52 cards of a standard card game from a CSS sprite image named "cards.jpg" with all the cards arranged in 4 rows and 13 columns.<br />
<br />
Here's how we tell stock what item types to display:<br />
<pre><br />
this.playerHand.image_items_per_row = 13; // 13 images per row<br />
<br />
<br />
// Create cards types:<br />
for (var color = 1; color <= 4; color++) {<br />
for (var value = 2; value <= 14; value++) {<br />
// Build card type id<br />
var card_type_id = this.getCardUniqueId(color, value);<br />
this.playerHand.addItemType(card_type_id, card_type_id, g_gamethemeurl + 'img/cards.jpg', card_type_id);<br />
}<br />
}<br />
</pre><br />
<br />
And add this function to the utilities section<br />
<pre><br />
// Get card unique identifier based on its color and value<br />
getCardUniqueId : function(color, value) {<br />
return (color - 1) * 13 + (value - 2);<br />
},<br />
</pre><br />
<br />
Explanations:<br />
* At first, we tell the stock component that our CSS sprite contains 13 items per row. This way, it can find the correct image for each card type id.<br />
* Then for the 4x13 cards, we call the '''addItemType''' method that creates the type. The arguments are the type id, the weight of the card (for sorting purpose), the URL of our CSS sprite, and the position of our card image in the CSS sprite. It happens to be the same number in our case.<br />
<br />
Note: we need to generate a unique ID for each type of card based on its color and value. For that we create a function '''getCardUniqueId'''. The type is the unique identifier of the TYPE of the card, e.g., the queen of spades encoded as an integer. If our deck had 2 standard card decks we would have had 2 queens of spades; they would share the same type and the same image but would have different ids. NOTE: It's unfortunate that they named this '''getCardUniqueId'''; it should have been '''getCardUniqueType''', because it really isn't an id, but a TYPE of card. The type of the item should either be a reversible function of its properties (i.e., kind of suite * 13 + value) or just an enumerator described in material.inc.php. In this specific case it's a synthetic type id, which also the same as the number of the card in the sprite image (i.e., if you enumerate each image in sprite going left to right, then top to bottom).<br />
<br />
Now let's add the 5 of Hearts to the player's hand just for fun (this code will go in setup method after types initialization):<br />
<br />
<pre><br />
// 2 = hearts, 5 is 5, and 42 is the card id, which normally would come from db<br />
this.playerHand.addToStockWithId( this.getCardUniqueId( 2, 5 ), 42 );<br />
</pre><br />
<br />
This will add the card with id 42 and type 16 ( (2-1)*13+(5-2)=16 ). <br />
<br />
Note that number 16 would not be something you can see in database, Deck database will have separate field for type and type_arg where type is suite and type_arg is number, so its not the same thing, but you can use same formula to convert. Number 42 on the other hand would be id field in database. But we get to database in the later section.<br />
<br />
If you reload now you should see the 5 of hearts in "your hand".<br />
<br />
Stock control can handle clicking on items and forms the selection. You can immediately react to selection<br />
or you can query it later; for example when user presses some other button.<br />
<br />
Let's hook it up. Add this in the setup method in .js file, after this.playerHand is initialised:<br />
<br />
dojo.connect( this.playerHand, 'onChangeSelection', this, 'onPlayerHandSelectionChanged' );<br />
<br />
<br />
Then find the Player's action comment section and add a handler after the comment:<br />
<br />
<pre><br />
onPlayerHandSelectionChanged : function() {<br />
var items = this.playerHand.getSelectedItems();<br />
<br />
if (items.length > 0) {<br />
if (this.checkAction('playCard', true)) {<br />
// Can play a card<br />
<br />
var card_id = items[0].id;<br />
console.log("on playCard "+card_id);<br />
<br />
this.playerHand.unselectAll();<br />
} else if (this.checkAction('giveCards')) {<br />
// Can give cards => let the player select some cards<br />
} else {<br />
this.playerHand.unselectAll();<br />
}<br />
}<br />
},<br />
</pre><br />
<br />
The function name of the handler is 4th parameter of the dojo.connect function. Make sure you spell it correctly or there will be unpredictable effects.<br />
<br />
Now if you reload, open the Javascript Console (F12), and then click on the card in My Hand, you should see:<br />
on playCard 42<br />
printed on the console<br />
<br />
''Note : You need to be the active player to have rights to play a card and so log your message in the console''<br />
<br />
== Game Database and Game Initialisation ==<br />
<br />
Next step, you want to design a game database and setup a new game (on the server side).<br />
For that we need to a) modify the database schema to add our cards data b) add some global variables into<br />
the existing globals table.<br />
<br />
To modify the schema, first exit your existing game(s). Open '''dbmodel.sql''' file and uncomment the card table creation.<br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `card` (<br />
`card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,<br />
`card_type` varchar(16) NOT NULL,<br />
`card_type_arg` int(11) NOT NULL,<br />
`card_location` varchar(16) NOT NULL,<br />
`card_location_arg` int(11) NOT NULL,<br />
PRIMARY KEY (`card_id`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;<br />
</pre><br />
<br />
This is the "card" table which will be managed by the Deck php class.<br />
<br />
In addition we want a little piece of information in the players table:<br />
<br />
-- add info about first player<br />
ALTER TABLE `player` ADD `player_first` BOOLEAN NOT NULL DEFAULT '0';<br />
<br />
Not sure why they put this into the player table, as we could use a global db variable to hold first player as easily.<br />
But I am just following the existing code more-or-less.<br />
<br />
Next we finally get into .game.php class, where the main logic and db interaction would be. Find php constructor which should be <br />
function __construct( )<br />
This is first function in a file. Add this code to constructor.<br />
<pre><br />
parent::__construct();<br />
self::initGameStateLabels( array( <br />
"currentHandType" => 10, <br />
"trickColor" => 11, <br />
"alreadyPlayedHearts" => 12,<br />
) );<br />
<br />
$this->cards = self::getNew( "module.common.deck" );<br />
$this->cards->init( "card" );<br />
</pre><br />
<br />
Here we are initializing three "Game State Variables" which are variables stored in the database. They are integers.<br />
It must start with no values lower then 10 since values lower than 10 are reserved. These values are stored by numeric ids<br />
in the database, but in the php we associate them with string labels for convenience of access. The variables are "trickColor": numbers from 1 to 4 that map to card suit (not sure why it's called color; maybe it's a translation from French); "alreadyPlayedHearts": a boolean flag (0 or 1) indicating hether somebody used hearts on the trick; "currentHandType": stores the value to indicate who to give cards to during exchange.<br />
<br />
The next 2 lines are creating $this->cards object and associating it with "card" table in the the database.<br />
<br />
<i>If we called db table 'foo' instead of 'card' the last statement would have been $this->cards->init( "foo" )</i><br />
<br />
<br />
At this point I would start a new game and make sure it starts, then exit. <br />
<br />
<i><br />
If you made a mistake<br />
in the .sql or php constructor the game won't start, and good luck debugging it. (That is why it's important to check<br />
once in a while to make sure it still starts while you remember what you have changed.)<br />
</i><br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/e3a049257b592ff6167688d4d344f8a83d349b08]<br />
<br />
Now we can go to game initialization '''setupNewGame''' in game.php. This method is called once when the table is created.<br />
<br />
In your template project you should have code that deals with player table, just leave it as is. Start inserting the<br />
other code after "Start the game initialization" comment.<br />
<pre><br />
// Init global values with their initial values<br />
<br />
// Note: hand types: 0 = give 3 cards to player on the left<br />
// 1 = give 3 cards to player on the right<br />
// 2 = give 3 cards to player opposite<br />
// 3 = keep cards<br />
self::setGameStateInitialValue( 'currentHandType', 0 );<br />
<br />
// Set current trick color to zero (= no trick color)<br />
self::setGameStateInitialValue( 'trickColor', 0 );<br />
<br />
// Mark if we already played hearts during this hand<br />
self::setGameStateInitialValue( 'alreadyPlayedHearts', 0 );<br />
</pre><br />
<br />
Here we initialize all the globals to 0.<br />
<br />
Next is to create our cards in the database. We have one deck of cards so it's pretty simple.<br />
<pre><br />
// Create cards<br />
$cards = array ();<br />
foreach ( $this->colors as $color_id => $color ) {<br />
// spade, heart, diamond, club<br />
for ($value = 2; $value <= 14; $value ++) {<br />
// 2, 3, 4, ... K, A<br />
$cards [] = array ('type' => $color_id,'type_arg' => $value,'nbr' => 1 );<br />
}<br />
}<br />
<br />
$this->cards->createCards( $cards, 'deck' );<br />
</pre><br />
<br />
This code that will create one of each card. But don't run it yet, because we missing $this->colors.<br />
So we have state of the game in the database, but there is some static game information which never changes.<br />
This information should be stored in material.inc.php and this way it can be accessed from all .php files.<br />
We will edit this file now by adding these lines<br />
<br />
<pre><br />
$this->colors = array(<br />
1 => array( 'name' => clienttranslate('spade'),<br />
'nametr' => self::_('spade') ),<br />
2 => array( 'name' => clienttranslate('heart'),<br />
'nametr' => self::_('heart') ),<br />
3 => array( 'name' => clienttranslate('club'),<br />
'nametr' => self::_('club') ),<br />
4 => array( 'name' => clienttranslate('diamond'),<br />
'nametr' => self::_('diamond') )<br />
);<br />
<br />
$this->values_label = array(<br />
2 =>'2',<br />
3 => '3',<br />
4 => '4',<br />
5 => '5',<br />
6 => '6',<br />
7 => '7',<br />
8 => '8',<br />
9 => '9',<br />
10 => '10',<br />
11 => clienttranslate('J'),<br />
12 => clienttranslate('Q'),<br />
13 => clienttranslate('K'),<br />
14 => clienttranslate('A')<br />
);<br />
</pre><br />
<br />
Where $this->colors will define Suit labels and $this->values_label will define value labels.<br />
If you noticed, we have two of each label for suits. This is because sometimes we need translated values on the php<br />
side and sometimes we don't. In this case '''nametr''' will return a translated value in php, which is only useful when you throw exceptions to show the right strings. If you pass a value to the client via notification you should always use untranslated strings, and the client will translate it. 'clienttranslate' marks the value for translation but does not actually change it for php. For more about this wonderful translation stuff see [[Translations]].<br />
<br />
== Full game model synchronisation ==<br />
<br />
Now at any point in the game we need to make sure that database information can be reflected back in the UI, so we must fix the '''getAllDatas''' function<br />
to return all possible data we need to reconstruct the game. This is in the game.php file. The template for getAllDatas() already takes care of player info. Let's just<br />
add hand and tableau data before we return a result.<br />
<br />
<pre><br />
// Cards in player hand<br />
$result['hand'] = $this->cards->getCardsInLocation( 'hand', $current_player_id );<br />
<br />
// Cards played on the table<br />
$result['cardsontable'] = $this->cards->getCardsInLocation( 'cardsontable' );<br />
</pre><br />
<br />
Now on the client side we should display this data, so in your .js file in the setup function (which is the receiver of getAllDatas) replace our hack of putting 5 of Hearts directly into the hand with:<br />
<br />
<pre><br />
// Cards in player's hand<br />
for ( var i in this.gamedatas.hand) {<br />
var card = this.gamedatas.hand[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
this.playerHand.addToStockWithId(this.getCardUniqueId(color, value), card.id);<br />
}<br />
<br />
// Cards played on table<br />
for (i in this.gamedatas.cardsontable) {<br />
var card = this.gamedatas.cardsontable[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
var player_id = card.location_arg;<br />
this.playCardOnTable(player_id, color, value, card.id);<br />
}<br />
</pre><br />
<br />
This should show hand and tableau cards now, except we are missing the '''playCardOnTable''' function. So find the '''getCardUniqueId''' function which<br />
should be in the utilities section and add this after it:<br />
<br />
<pre><br />
playCardOnTable : function(player_id, color, value, card_id) {<br />
// player_id => direction<br />
dojo.place(this.format_block('jstpl_cardontable', {<br />
x : this.cardwidth * (value - 2),<br />
y : this.cardheight * (color - 1),<br />
player_id : player_id<br />
}), 'playertablecard_' + player_id);<br />
<br />
if (player_id != this.player_id) {<br />
// Some opponent played a card<br />
// Move card from player panel<br />
this.placeOnObject('cardontable_' + player_id, 'overall_player_board_' + player_id);<br />
} else {<br />
// You played a card. If it exists in your hand, move card from there and remove<br />
// corresponding item<br />
<br />
if ($('myhand_item_' + card_id)) {<br />
this.placeOnObject('cardontable_' + player_id, 'myhand_item_' + card_id);<br />
this.playerHand.removeFromStockById(card_id);<br />
}<br />
}<br />
<br />
// In any case: move it to its final destination<br />
this.slideToObject('cardontable_' + player_id, 'playertablecard_' + player_id).play();<br />
},<br />
</pre><br />
<br />
For this to work we also need to add a card template in the .tpl file<br />
<pre><br />
// Javascript HTML templates<br />
<br />
var jstpl_cardontable = '<div class="cardontable" id="cardontable_${player_id}" style="background-position:-${x}px -${y}px">\<br />
</div>';<br />
</pre><br />
<br />
<br />
What this does is basically create another card object, because if it is not our card it's not in our hand (Stock) so<br />
we have to create it out of thin air. The technique to do that is to implement a Javascript template object defined in the .tpl file with some<br />
parameters, which will basically create a "div" string (yes you could have used string concatenation but it would not be fancy).<br />
Now dojo.place places it (the div) on top of a placeholder. Now we have an object with an id of 'cardontable_' + player_id. Depending<br />
on who is playing it we either place it on the player miniboard or in hand (and remove it from hand stock). Then we animate the card move.<br />
<br />
We also should fix our .css file now to add style for cardontable and REMOVE background for playertablecard which really is a placeholder div and not a card. (Don't miss the remove step; it will be all screwy if you do!)<br />
<br />
<pre><br />
.playertablecard {<br />
display: inline-block;<br />
position: relative;<br />
margin-top: 5px;<br />
width: 72px;<br />
height: 96px;<br />
/* we remove background-image here */<br />
}<br />
<br />
/*** cards on table ***/<br />
<br />
.cardontable {<br />
position: absolute;<br />
width: 72px;<br />
height: 96px;<br />
background-image: url('img/cards.jpg'); <br />
}<br />
</pre><br />
<br />
Now to test that it actually works let's deal cards to players during game initialization:<br />
<br />
Add this after createCards in setupNewGame function in the game.php file<br />
<pre><br />
// Shuffle deck<br />
$this->cards->shuffle('deck');<br />
// Deal 13 cards to each players<br />
$players = self::loadPlayersBasicInfos();<br />
foreach ( $players as $player_id => $player ) {<br />
$cards = $this->cards->pickCards(13, 'deck', $player_id);<br />
} <br />
</pre><br />
<br />
Now when you start the game you should see 13 cards in your hand!<br />
<br />
We just need to hook-up clicking on card and test if our playCardOnTable works.<br />
<br />
Find onPlayerHandSelectionChanged function in the JS file, we should have logging there like console.log("on playCard "+card_id);<br />
So after that insert this:<br />
<pre><br />
console.log("on playCard "+card_id);<br />
// type is (color - 1) * 13 + (value - 2)<br />
var type = items[0].type;<br />
var color = Math.floor(type / 13) + 1;<br />
var value = type % 13 + 2;<br />
<br />
this.playCardOnTable(this.player_id,color,value,card_id);<br />
</pre><br />
Note: this code is for testing we will replace it with server interaction after we test it.<br />
<br />
Now if you force reload (because we changed .css before) you should be able to click on card from you have and see it moving,<br />
you can click on few cards this way. When you done enjoying the animation, press F5 to get your hand back.<br />
<br />
[[File:Heartsla-sync.png]]<br />
<br />
Code Rev [https://github.com/elaskavaia/bga-heartsla/tree/01d4e2f595fd14c2adcc97a957d21bb2766f78a8]<br />
<br />
== State Machine ==<br />
<br />
Now we need to create a game state machine. So the states are:<br />
<br />
* Cards are dealt to all players (lets call it "newHand")<br />
* Player is selected who will start a new trick ("newTrick")<br />
* Player start or respond to played card ("playerTurn")<br />
* Game control is passed to next player or trick is ended ("nextPlayer")<br />
* End of hand processing (scoring and check for end of game) ("nextHand")<br />
<br />
In addition players can exchange cards so we need two more states for that but we will skip it for now.<br />
<br />
<br />
The state handling spread across 4 files, so you have to make sure all pieces are connected together.<br />
The state machine states.php defines all the states, and function handlers on php side in a form of string,<br />
and if any of these functions are not implemented it would be very hard to debug because it will break in random places.<br />
<br />
So .states.php<br />
<br />
<pre><br />
$machinestates = array(<br />
<br />
// The initial state. Please do not modify.<br />
1 => array(<br />
"name" => "gameSetup",<br />
"description" => clienttranslate("Game setup"),<br />
"type" => "manager",<br />
"action" => "stGameSetup",<br />
"transitions" => array( "" => 20 )<br />
),<br />
<br />
<br />
/// New hand<br />
20 => array(<br />
"name" => "newHand",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNewHand",<br />
"updateGameProgression" => true, <br />
"transitions" => array( "" => 30 )<br />
), <br />
<br />
<br />
<br />
// Trick<br />
<br />
30 => array(<br />
"name" => "newTrick",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNewTrick",<br />
"transitions" => array( "" => 31 )<br />
), <br />
31 => array(<br />
"name" => "playerTurn",<br />
"description" => clienttranslate('${actplayer} must play a card'),<br />
"descriptionmyturn" => clienttranslate('${you} must play a card'),<br />
"type" => "activeplayer",<br />
"possibleactions" => array( "playCard" ),<br />
"transitions" => array( "playCard" => 32 )<br />
), <br />
32 => array(<br />
"name" => "nextPlayer",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stNextPlayer",<br />
"transitions" => array( "nextPlayer" => 31, "nextTrick" => 30, "endHand" => 40 )<br />
), <br />
<br />
<br />
// End of the hand (scoring, etc...)<br />
40 => array(<br />
"name" => "endHand",<br />
"description" => "",<br />
"type" => "game",<br />
"action" => "stEndHand",<br />
"transitions" => array( "nextHand" => 20, "endGame" => 99 )<br />
), <br />
<br />
// Final state.<br />
// Please do not modify.<br />
99 => array(<br />
"name" => "gameEnd",<br />
"description" => clienttranslate("End of game"),<br />
"type" => "manager",<br />
"action" => "stGameEnd",<br />
"args" => "argGameEnd"<br />
)<br />
<br />
);<br />
</pre><br />
<br />
The full details about what these fields are you can find in [[Your_game_state_machine:_states.inc.php]].<br />
<br />
But basically we have Player states, in which human player has to perform an "action" by pressing some button in UI or selecting some game item, which will trigger js handler, which will do ajax call to server into API define <br />
by .action.php file. All functions in this file are API between client and server and has very simple<br />
and repetitive structure. In this case there is only two action player can do - play a card or pass cards to other player. So these 2 functions go into .action.php file, we will only define one now since we not implementing card passing states yet:<br />
<br />
<pre><br />
public function playCard() {<br />
self::setAjaxMode();<br />
$card_id = self::getArg("id", AT_posint, true);<br />
$this->game->playCard($card_id);<br />
self::ajaxResponse();<br />
}<br />
</pre><br />
<br />
<br />
Now to make it run we have define all handler functions that we referenced in states, which are - one function for state arguments argGiveCards, 4 functions for robot states (where game performs some action)<br />
and 1 function for player actions handling.<br />
Find 'Game state arguments' section and paste this in:<br />
<pre><br />
function argGiveCards() {<br />
return array ();<br />
}<br />
</pre><br />
<br />
This normally pass some parameters to states, but we don't need anything yet. It good to have placeholder there anyway, so we can fix it later.<br />
Important: even when its a stub this function must return array not scalar.<br />
<br />
Lets do stubs for other functions, find game state actions section in .game.php file and insert these<br />
<pre><br />
function stNewHand() {<br />
// Take back all cards (from any location => null) to deck<br />
$this->cards->moveAllCardsInLocation(null, "deck");<br />
$this->cards->shuffle('deck');<br />
// Deal 13 cards to each players<br />
// Create deck, shuffle it and give 13 initial cards<br />
$players = self::loadPlayersBasicInfos();<br />
foreach ( $players as $player_id => $player ) {<br />
$cards = $this->cards->pickCards(13, 'deck', $player_id);<br />
// Notify player about his cards<br />
self::notifyPlayer($player_id, 'newHand', '', array ('cards' => $cards ));<br />
}<br />
self::setGameStateValue('alreadyPlayedHearts', 0);<br />
$this->gamestate->nextState("");<br />
}<br />
<br />
function stNewTrick() {<br />
// New trick: active the player who wins the last trick, or the player who own the club-2 card<br />
// Reset trick color to 0 (= no color)<br />
self::setGameStateInitialValue('trickColor', 0);<br />
$this->gamestate->nextState();<br />
}<br />
<br />
function stNextPlayer() {<br />
// Active next player OR end the trick and go to the next trick OR end the hand<br />
if ($this->cards->countCardInLocation('cardsontable') == 4) {<br />
// This is the end of the trick<br />
// Move all cards to "cardswon" of the given player<br />
$best_value_player_id = self::activeNextPlayer(); // TODO figure out winner of trick<br />
$this->cards->moveAllCardsInLocation('cardsontable', 'cardswon', null, $best_value_player_id);<br />
<br />
if ($this->cards->countCardInLocation('hand') == 0) {<br />
// End of the hand<br />
$this->gamestate->nextState("endHand");<br />
} else {<br />
// End of the trick<br />
$this->gamestate->nextState("nextTrick");<br />
}<br />
} else {<br />
// Standard case (not the end of the trick)<br />
// => just active the next player<br />
$player_id = self::activeNextPlayer();<br />
self::giveExtraTime($player_id);<br />
$this->gamestate->nextState('nextPlayer');<br />
}<br />
}<br />
<br />
function stEndHand() {<br />
$this->gamestate->nextState("nextHand");<br />
}<br />
<br />
</pre><br />
Important: All state actions game or player must end with state transition (or thrown exception). Also make sure its ONLY one state transition,<br />
if you accidentally fall though after state transition and do another one it will be a real mess and head scratching for long time.<br />
<br />
Now find 'player actions' section and paste this code there<br />
<pre><br />
function playCard($card_id) {<br />
self::checkAction("playCard");<br />
$player_id = self::getActivePlayerId();<br />
throw new BgaUserException(self::_("Not implemented: ") . "$player_id plays $card_id");<br />
}<br />
</pre><br />
We won't implement it yet but throw an exception which we will see if interaction is working properly<br />
<br />
Now the game should start but it would not be any different then before because we have to implement actual interactions.<br />
Its good to check if it still working though (and if it was running before you have to exit because we changed state machine and normally it will break stuff)<br />
<br />
== Client - Server interactions ==<br />
<br />
Now to implement things for real we have hook UI actions to ajax calls, and process notifications send by server.<br />
So previously we hooked playCardOnTable right into js handler which caused client animation, in real game its a two<br />
step operation. When user clicks on game element js client sends an ajax call to server, server processes it and updates database, server sends<br />
notification in response, client hooks animations to server notification.<br />
<br />
So in .js code replace onPlayerHandSelectionChanged with<br />
<pre><br />
onPlayerHandSelectionChanged : function() {<br />
var items = this.playerHand.getSelectedItems();<br />
<br />
if (items.length > 0) {<br />
var action = 'playCard';<br />
if (this.checkAction(action, true)) {<br />
// Can play a card<br />
var card_id = items[0].id; <br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", {<br />
id : card_id,<br />
lock : true<br />
}, this, function(result) {<br />
}, function(is_error) {<br />
});<br />
<br />
this.playerHand.unselectAll();<br />
} else if (this.checkAction('giveCards')) {<br />
// Can give cards => let the player select some cards<br />
} else {<br />
this.playerHand.unselectAll();<br />
}<br />
}<br />
},<br />
</pre><br />
<br />
<br />
Now when you click on card you should get a server response: Not implemented...<br />
<br />
Lets implement it, in .game.php<br />
<pre><br />
function playCard($card_id) {<br />
self::checkAction("playCard");<br />
$player_id = self::getActivePlayerId();<br />
$this->cards->moveCard($card_id, 'cardsontable', $player_id);<br />
// XXX check rules here<br />
$currentCard = $this->cards->getCard($card_id);<br />
// And notify<br />
self::notifyAllPlayers('playCard', clienttranslate('${player_name} plays ${value_displayed} ${color_displayed}'), array (<br />
'i18n' => array ('color_displayed','value_displayed' ),'card_id' => $card_id,'player_id' => $player_id,<br />
'player_name' => self::getActivePlayerName(),'value' => $currentCard ['type_arg'],<br />
'value_displayed' => $this->values_label [$currentCard ['type_arg']],'color' => $currentCard ['type'],<br />
'color_displayed' => $this->colors [$currentCard ['type']] ['name'] ));<br />
// Next player<br />
$this->gamestate->nextState('playCard');<br />
}<br />
</pre><br />
<br />
We get the card from client, we move it to the tableau (moveCard is hooked to database directly, its part of deck class),<br />
we notify all players and we change state. What we missing here is bunch of checks (rule enforcements), we will add it later.<br />
<br />
Interesting part about this notify is that we use i18n array for string that needs to be translated by client, so<br />
they sent as English text in notification, then client has to know which parameters needs translating.<br />
<br />
On the client side .js we have to implement a notification handler to do the animation<br />
<br />
<pre><br />
setupNotifications : function() {<br />
console.log('notifications subscriptions setup');<br />
<br />
dojo.subscribe('newHand', this, "notif_newHand");<br />
dojo.subscribe('playCard', this, "notif_playCard");<br />
<br />
},<br />
<br />
notif_newHand : function(notif) {<br />
// We received a new full hand of 13 cards.<br />
this.playerHand.removeAll();<br />
<br />
for ( var i in notif.args.cards) {<br />
var card = notif.args.cards[i];<br />
var color = card.type;<br />
var value = card.type_arg;<br />
this.playerHand.addToStockWithId(this.getCardUniqueId(color, value), card.id);<br />
}<br />
},<br />
<br />
notif_playCard : function(notif) {<br />
// Play a card on the table<br />
this.playCardOnTable(notif.args.player_id, notif.args.color, notif.args.value, notif.args.card_id);<br />
},<br />
</pre><br />
<br />
Now it actually works through the server when you click on card - the move is recorded. If you testing it now you will notice<br />
after trick is done all cards remains on the table, but if you press F5 they would disappear, this is because<br />
we updated database to pick-up the cards but did not send notification about it, so we need to send notification about it<br />
and have a handler for it<br />
<br />
So in .game.php file add notification in stNextPlayer function after moveAllCardsInLocation call:<br />
<br />
<pre><br />
// Notify<br />
// Note: we use 2 notifications here in order we can pause the display during the first notification<br />
// before we move all cards to the winner (during the second)<br />
$players = self::loadPlayersBasicInfos();<br />
self::notifyAllPlayers( 'trickWin', clienttranslate('${player_name} wins the trick'), array(<br />
'player_id' => $best_value_player_id,<br />
'player_name' => $players[ $best_value_player_id ]['player_name']<br />
) ); <br />
self::notifyAllPlayers( 'giveAllCardsToPlayer','', array(<br />
'player_id' => $best_value_player_id<br />
) );<br />
</pre><br />
<br />
And in .js file add 2 more notification handlers.<br />
<br />
This is to subscribe in setupNotifications function<br />
<pre><br />
dojo.subscribe( 'trickWin', this, "notif_trickWin" );<br />
this.notifqueue.setSynchronous( 'trickWin', 1000 );<br />
dojo.subscribe( 'giveAllCardsToPlayer', this, "notif_giveAllCardsToPlayer" );<br />
</pre><br />
<br />
And this are handlers<br />
<pre><br />
notif_trickWin : function(notif) {<br />
// We do nothing here (just wait in order players can view the 4 cards played before they're gone.<br />
},<br />
notif_giveAllCardsToPlayer : function(notif) {<br />
// Move all cards on table to given table, then destroy them<br />
var winner_id = notif.args.player_id;<br />
for ( var player_id in this.gamedatas.players) {<br />
var anim = this.slideToObject('cardontable_' + player_id, 'overall_player_board_' + winner_id);<br />
dojo.connect(anim, 'onEnd', function(node) {<br />
dojo.destroy(node);<br />
});<br />
anim.play();<br />
}<br />
},<br />
</pre><br />
<br />
So 'trickWin' notification does not do much except it will delay the processing of next notification by 1 second (1000 ms)<br />
and it will log the message (that happens independent of what handler does).<br />
<i>Note: if on the other hand you don't want to log but what what to do something else send empty message</i><br />
<br />
Now after the trick you see all cards move the "player's stash".<br />
<br />
== Scoring and End of game handling ==<br />
<br />
Now we should calculate scoring and for that we need to actually track who wins the trick.<br />
Trick is won by the player with highest card (no trump). We just need to remember what is trick suite.<br />
For which we will use state variable 'trickColor' which we already conveniently created.<br />
<br />
In .game.php file find playCard function and add this before notify functions<br />
$currentTrickColor = self::getGameStateValue( 'trickColor' ) ;<br />
if( $currentTrickColor == 0 )<br />
self::setGameStateValue( 'trickColor', $currentCard['type'] );<br />
<br />
This will make sure we remember first suit being played, now to use it modify stNextPlayer function to fix our TODO comment<br />
<pre><br />
function stNextPlayer() {<br />
// Active next player OR end the trick and go to the next trick OR end the hand<br />
if ($this->cards->countCardInLocation('cardsontable') == 4) {<br />
// This is the end of the trick<br />
$cards_on_table = $this->cards->getCardsInLocation('cardsontable');<br />
$best_value = 0;<br />
$best_value_player_id = null;<br />
$currentTrickColor = self::getGameStateValue('trickColor');<br />
foreach ( $cards_on_table as $card ) {<br />
// Note: type = card color<br />
if ($card ['type'] == $currentTrickColor) {<br />
if ($best_value_player_id === null || $card ['type_arg'] > $best_value) {<br />
$best_value_player_id = $card ['location_arg']; // Note: location_arg = player who played this card on table<br />
$best_value = $card ['type_arg']; // Note: type_arg = value of the card<br />
}<br />
}<br />
}<br />
<br />
// Active this player => he's the one who starts the next trick<br />
$this->gamestate->changeActivePlayer( $best_value_player_id );<br />
<br />
// Move all cards to "cardswon" of the given player<br />
$this->cards->moveAllCardsInLocation('cardsontable', 'cardswon', null, $best_value_player_id);<br />
<br />
// Notify<br />
// ... same code here as before<br />
</pre><br />
<br />
The scoring rule in the studio example code is huge multi-page function, for this tutorial we will make simplier.<br />
Lets score -1 point per heart and call it a day. And game will end when somebody goes -100 or below.<br />
<br />
As UI goes for scoring, the main thing to update is the scoring on the mini boards represented by stars, also<br />
we want to show that in the log. <br />
In addition scoring can be shown in [[Game_interface_logic:_yourgamename.js#Scoring_dialogs|Scoring Dialog]] using tableWindow notification, but it is a tutorial on its own and you can do it as homework (it is part of original heart game).<br />
<br />
In .js file we need to add one more subscription and notification handler:<br />
dojo.subscribe( 'newScores', this, "notif_newScores" );<br />
in setupNotifications<br />
<br />
and <br />
notif_newScores : function(notif) {<br />
// Update players' scores<br />
for ( var player_id in notif.args.newScores) {<br />
this.scoreCtrl[player_id].toValue(notif.args.newScores[player_id]);<br />
}<br />
},<br />
somewhere after. this.scoreCtrl is pre-existing object that shows the scoring and this function will update score values per player from notification argument<br />
<br />
so in .game.php our stEndHand function will look like<br />
<br />
<pre><br />
function stEndHand() {<br />
// Count and score points, then end the game or go to the next hand.<br />
$players = self::loadPlayersBasicInfos();<br />
// Gets all "hearts" + queen of spades<br />
<br />
$player_to_points = array ();<br />
foreach ( $players as $player_id => $player ) {<br />
$player_to_points [$player_id] = 0;<br />
}<br />
$cards = $this->cards->getCardsInLocation("cardswon");<br />
foreach ( $cards as $card ) {<br />
$player_id = $card ['location_arg'];<br />
// Note: 2 = heart<br />
if ($card ['type'] == 2) {<br />
$player_to_points [$player_id] ++;<br />
}<br />
}<br />
// Apply scores to player<br />
foreach ( $player_to_points as $player_id => $points ) {<br />
if ($points != 0) {<br />
$sql = "UPDATE player SET player_score=player_score-$points WHERE player_id='$player_id'";<br />
self::DbQuery($sql);<br />
$heart_number = $player_to_points [$player_id];<br />
self::notifyAllPlayers("points", clienttranslate('${player_name} gets ${nbr} hearts and looses ${nbr} points'), array (<br />
'player_id' => $player_id,'player_name' => $players [$player_id] ['player_name'],<br />
'nbr' => $heart_number ));<br />
} else {<br />
// No point lost (just notify)<br />
self::notifyAllPlayers("points", clienttranslate('${player_name} did not get any hearts'), array (<br />
'player_id' => $player_id,'player_name' => $players [$player_id] ['player_name'] ));<br />
}<br />
}<br />
$newScores = self::getCollectionFromDb("SELECT player_id, player_score FROM player", true );<br />
self::notifyAllPlayers( "newScores", '', array( 'newScores' => $newScores ) );<br />
<br />
///// Test if this is the end of the game<br />
foreach ( $newScores as $player_id => $score ) {<br />
if ($score <= -100) {<br />
// Trigger the end of the game !<br />
$this->gamestate->nextState("endGame");<br />
return;<br />
}<br />
}<br />
<br />
<br />
$this->gamestate->nextState("nextHand");<br />
}<br />
</pre><br />
<br />
<br />
So it should more less work now, including end of game condition. Try to play it!<br />
<br />
== Additional stuff ==<br />
<br />
The following things were not implemented and can add them yourself by looking at the code of original hearts game:<br />
<br />
* Remove debug code from setupNewGame to deal cards, cards are now dealt in stNewHand state handler<br />
* Rule checking and rule enforcements in playCard function<br />
* Start scoring with 100 points each and end when <= 0<br />
* Fix scoring rules with Q of spades and 26 point reverse scoring<br />
* First player one with 2 club<br />
* Add progress handling<br />
* Add statistics<br />
* Add card exchange states<br />
* Add game option to start with 75 points instead of 100</div>
Cpasbanal