http://en.doc.boardgamearena.com/api.php?action=feedcontributions&user=Loenix34&feedformat=atom
Board Game Arena - User contributions [en]
2024-03-29T15:04:15Z
User contributions
MediaWiki 1.39.0
http://en.doc.boardgamearena.com/index.php?title=Game_interface_logic:_yourgamename.js&diff=10437
Game interface logic: yourgamename.js
2021-12-03T09:03:37Z
<p>Loenix34: Standardize and suggest to use title for sub section, so we can get permalink to this sections</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 />
;setup(gamedatas) <br />
This method must set up the game user interface according to current game situation specified in parameters.<br />
The method is called each time the game interface is displayed to a player, ie:<br />
<br />
- when the game starts<br />
<br />
- when a player refreshes the game page (F5)<br />
<br />
"gamedatas" argument contains all datas retrieved by your "getAllDatas" PHP method.<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 active 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 (this.isCurrentPlayerActive()) with (!this.isSpectator) <br />
for the main switch in that method.<br />
<br />
See more details in [[Your_game_state_machine:_states.inc.php#Diffrence_between_Single_active_and_Multi_active_states]]<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 (i.e. cleanup).<br />
<br />
;onUpdateActionButtons(stateName, args)<br />
In this method you can manage "action buttons" that are displayed in the action status bar and highlight active UI elements.<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 calls would depends on either you get into that state from transitions OR from reloading the whole game (i.e. F5).<br />
<br />
See more details in [[Your_game_state_machine:_states.inc.php#Difference_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). Note: see remarks above about usage of this function inside onEnteringState method.<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 />
; g_replayFrom<br />
: Global contains reply number in live game, it is set to undefined (i.e. not set) when it is not a reply mode, so consequentially the good check is '''g_replayFrom !== undefined''' which 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 (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 can use '''getElementById''' but a longer to type and less handy as it does not do some checks.<br />
Note²: It is safe to use if you don't know if variable is string (id of element) or element itself, i.e. <br />
<pre><br />
foo: function(card) {<br />
card = $(card); // now its node, no need to write if (typeof card === 'string') ...<br />
// but its good idea to check for null here<br />
...<br />
}<br />
</pre><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 />
'''Vanila JS style'''<br />
$('my_element').style.display='none'; // set<br />
var display = $('my_element').style.display; // get<br />
$('my_element').style.removeProperty('display'); // remove<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 />
'''Vanila JS attr'''<br />
<br />
$(token).id=new_id; // set attr for "id"<br />
var id = $(token).id; // get<br />
<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 />
'''Vanila JS query'''<br />
<br />
var cards=document.querySelectorAll(".hand .card");// all cards in all hands<br />
var cards=$('hand').querySelectorAll(".card");// all cards in specific hand<br />
var card=document.querySelector(".hand .card");// first card or null if none (super handy)<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 />
positive integer: This parameter can be a positive 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 />
But you can also relocate elements like that. Note: it won't animate if you do that.<br />
<br />
<br />
'''dojo.empty'''<br />
<br />
Remove all children of the node element<br />
dojo.empty('my_hand');<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 />
'''this.format_block'''<br />
This bga function that takes global var from template file and substitute variables, typical use would be<br />
<br />
var player = gamedatas.players[player_id];<br />
var div = this.format_block('jstpl_player_board', player ); // var jstpl_player_board = ... is defined in .tpl file <br />
<br />
'''this.format_string'''<br />
This bga function just substitute variables in a string, i.e.<br />
var div = this.format_string('<div color="${player_color}"></div>', {player_color: '#ff0000'} );<br />
<br />
'''this.format_string_recursive'''<br />
This bga function is similar to this.format_string but is capable of processing recursive argument structures and translations. It is used to format server notifications.<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, but it starts the animation. <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. Its starts the animation.<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 />
Note: This wiki was probably written 10 years ago - now all modern broser support 'transform' so just ignore this and use transform.<br />
<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 and that it is centered.<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, the center of "mobile_obj" will be placed to the specified x,y position relatively to the center of "target_obj".<br />
<br />
''Note: the placement works differently from this.slideToObjectPos'', since coordinates are calculated based on the center of objects.<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(element, event, context, method) ====<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(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(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 />
<br />
====this.disconnect(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(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 />
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(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. Unlike this.checkAction, this function does NOT take interface locking into account (bug or feature?)<br />
<br />
Check if player can do the specified action by taking into account:<br />
* current game state<br />
<br />
<br />
==== this.ajaxcall(url, parameters, obj_callback, callback, callback_error, ajax_method) ====<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 />
* ajax_method: (optional and rarely used) if you need to send large amounts of data (over 2048 bytes), you can set this parameter to 'post' (all lower-case) to send a POST request as opposed to the default GET. This works, but was not officially documented, so only use if you really need to.<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, 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 />
== 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 all notifications arguments with "notif.args".<br />
<br />
Example:<br />
<br />
PHP<br />
<br />
<pre><br />
self::notifyAllPlayers( "myNotification", '', array( "myArgument" => 3 ) );<br />
</pre><br />
<br />
JavaScript<br />
<br />
<pre><br />
//You can access the "myArgument" like this:<br />
notif_myNotification: function( notif )<br />
{<br />
alert( "myArgument = " + notif.args.myArgument );<br />
}<br />
</pre><br />
<br />
=== The notification Object received by client ===<br />
<br />
When sending a notification on your PHP, the client side will receive an Object with the following attributes:<br />
<br />
* args : This is the arguments that you passed on your notification method on php<br />
* bIsTableMsg : Boolean, is true when you use [[Main_game_logic:_yourgamename.game.php#NotifyAllPlayers|NotifyAllPlayers]] method (false otherwise)<br />
* channelorig : information about table ID (formatted as : "/table/t[TABLE_NUMBER]")<br />
* gamenameorig : name of the game<br />
* log: the log information as written in PHP function<br />
* move_id : ID of the move associated with the notification<br />
* table_id : ID of the table<br />
* time : UNIX GMT time<br />
* type : name of the notification<br />
* uid : identifier of the notification<br />
<br />
'' Note that those information were inferred from observation on console log. If an Admin can confirm/correct (and remove this line), you're welcome :)''<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, there 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 />
=== WARNING: combining synchronous and ignored notifications ===<br />
You must be careful when combining dynamic synchronous durations (as described above) with ignored notifications. If you have a conditionally ignored notification like this (see below section):<br />
<br />
<pre>this.notifqueue.setIgnoreNotificationCheck( 'myNotif', (notif) => (notif.args.player_id == this.player_id) /* or any other condition */ )</pre><br />
<br />
then you CANNOT do<br />
<br />
<pre>this.notifqueue.setSynchronous('myNotif');</pre><br />
<br />
as, when the ignored check passes, the notification handler, in which `this.notifqueue.setSychronousDuration` is called, is never called and so the duration is never set and interface locking results.<br />
<br />
The workaround is to set a "dummy" time:<br />
<br />
<pre>this.notifqueue.setSynchronous('myNotif', 5000);</pre><br />
<br />
whose value is irrelevant but must be large enough to cover the time before the notification handler is called. The large value never actually comes into play because the notification is either ignored, or the synchronous duration is reset to a sensible value inside the handler.<br />
<br />
<br />
=== Ignoring notifications ===<br />
Sometimes you need to ignore some notification on client side. You don't want them to be shown in game log and you don't want them to be handled.<br />
<br />
The most common use case is when a player gets private information. They will receive a specific notification (such as "You received Ace of Heart"), while other players would receive more generic notification ("Player received a card").<br />
<br />
In X.game.php<br />
<pre><br />
$this->notifyAllPlayers("dealCard", clienttranslate('${player_name} received a card'), [<br />
'player_id' => $playerId,<br />
'player_name' => $this->getActivePlayerName()<br />
]);<br />
<br />
$this->notifyPlayer($playerId, "dealCardPrivate", clienttranslate('You received ${cardName}'), [<br />
"type" => $card["type"],<br />
"cardName" => $this->getCardName($card["type"])<br />
]);<br />
</pre><br />
<br />
The problem with this approach is that the active player will receive two notifications:<br />
* Player1 received a card<br />
* You received Ace of Hearts<br />
<br />
Hence, notification ignoring. Similar to setting a synchronous notification above, you can set up a check whether a notification should be ignored:<br />
<pre><br />
this.notifqueue.setIgnoreNotificationCheck( 'dealCard', (notif) => (notif.args.player_id == this.player_id) );<br />
</pre><br />
<br />
'''setIgnoreNotificationCheck(notificationId, predicate)'''<br />
This method will set a check whether any of notifications of specific type should be ignored.<br />
<br />
The parameters are:<br />
* notificationId: before dispatching any notification of this type, the framework will call predicate to check whether notification should be ignored<br />
* predicate (notif => boolean): a function that will receive notif object and will return true if this specific notification should be ignored<br />
<br />
NOTE: You can think that it could be possible to send generic notification to all other players with notifyAllPlayers and it will seem to work. The problem is that table spectators would miss this notification and their user interface (and game log) wouldn't be updated.<br />
<br />
IMPORTANT: Remember that this notification is ignored on the client side, but was still received by the client. Therefore it shouldn't contain any private information as cheaters can get it. In other words this is not a way to hide information.<br />
<br />
IMPORTANT: When a game is reloaded with F5 or when opening a turn based game, old notifications are replayed as history notification. They are used just to update the game log and are stripped of all arguments except player_id, i18n and any argument present in message. If you use and other argument in your predicate you should preserve it as explained [[Main_game_logic:_yourgamename.game.php#Notify_players|here]].<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, helpStringTranslated, actionStringTranslated, delay )'''<br />
<br />
Add a simple text tooltip to the DOM node.<br />
<br />
Specify 'helpStringTranslated' to display some information about "what is this game element?".<br />
Specify 'actionStringTranslated' to display some information about "what happens when I click on this element?".<br />
<br />
You must specify both of the strings. You can only use one and specify an empty string ('') for the other one.<br />
<br />
When you pass text directly function _() must be used for the text to be marked for translation! Except for empty string.<br />
<br />
Parameter "delay" is optional. 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 />
Note: this generates static tooltip and attaches to existing dom element, if you need to generate tooltip more dynamically you have to call that method every time information about object is updated or use completely different tehnique<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, helpStringTranslated, actionStringTranslated, delay )'''<br />
<br />
Add a simple text tooltip to all the DOM nodes set with this cssClass. See more details above for this.addTooltip.<br />
this.addTooltipToClass( 'meeple', _('This is A Meeple'), _('Click to tickle') );<br />
<br />
IMPORTANT: all concerned nodes must exist and 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 exist and have IDs to get tooltips.<br />
<br />
'''this.removeTooltip( nodeId )'''<br />
<br />
Remove a tooltip from the DOM node with given id.<br />
<br />
'''force tooltip to open'''<br />
<br />
If you want to force tooltip to open in reaction to some other action, i.e. click you can do this<br />
<br />
this.tooltips[id].open(id)<br />
<br />
where id is the id of the tooltip node where tooltip was installed.<br />
<br />
== Dialogs, warning messages, confirmation dialogs, ... ==<br />
<br />
=== Warning messages ===<br />
<br />
Sometimes, there is something important that is happening in 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 fulfills one of the end of the game conditions, 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 the current player.<br />
<br />
* "msg" is the string to display. It should be translated.<br />
* "type" can be set to "info", "error", or "only_to_log". 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. If set to "only_to_log", the message will be added to the game log but will not popup at the top of the screen.<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 guideline of BGA is to AVOID the use of confirmation dialogs. Confirmation dialogs slow down the game and bother players. The players know that they have to pay attention to each move when they are playing online.<br />
<br />
The situations 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 does 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. Note that the score is centered in the anchor, so the offsets might have to be negative if you calculate the position.<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 />
<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 />
<br />
'''buttons with images'''<br />
<br />
You can use the same method, but add extra class to a button to disable the padding and style it, i.e.<br />
<pre><br />
this.addActionButton( 'button_brick', '<div class="brick"></div>', ()=>{... on brick ...}, null, null, 'gray'); <br />
dojo.addClass('button_brick','bgaimagebutton');<br />
</pre><br />
<br />
where<br />
<br />
<pre><br />
.bgaimagebutton {<br />
padding: 0px 12px;<br />
min-height: 28px;<br />
border: none;<br />
}<br />
</pre><br />
<br />
If you use this a lot, you can define a helper function, i.e.<br />
<pre><br />
/**<br />
* This method can be used instead of addActionButton, to add a button which is an image (i.e. resource). Can be useful when player<br />
* need to make a choice of resources or tokens.<br />
*/<br />
addImageActionButton: function(id, div, handler, bcolor, tooltip) {<br />
if (typeof bcolor == "undefined") {<br />
bcolor = "gray";<br />
}<br />
// this will actually make a transparent button id color = gray<br />
this.addActionButton(id, div, handler, '', false, bcolor);<br />
// remove boarder, for images it better without<br />
dojo.style(id, "border", "none");<br />
// but add shadow style (box-shadow, see css)<br />
dojo.addClass(id, "shadow bgaimagebutton");<br />
// you can also add addition styles, such as background<br />
if (tooltip) {<br />
dojo.attr(id, "title", tooltip);<br />
}<br />
return $(id);<br />
},<br />
</pre><br />
<br />
'''buttons outside of action bar'''<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 />
Note: You can also create button using addActionButton() method, then move anywhere<br />
<pre><br />
this.addActionButton( 'commit_button', _('Confirm'), 'onConfirm', null, true, 'red'); <br />
dojo.place('commit_button','player_board');<br />
</pre><br />
<br />
'''Disabling:'''<br />
You can disable the '''bgabutton''' by adding the css class '''disabled''' in you js. The disabled button is still visible but is grey and not clickable.<br />
For example in the '''onUpdateActionButtons''' : <br />
<pre>this.addActionButton( 'play_button_id', _('Play 1 to 3 cards'), 'playFunctionButton', null, false, 'blue' ); //Create a blue button<br />
if (Condition == true)<br />
{<br />
dojo.addClass( 'play_button_id', 'disabled');//disable the button<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/<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 with the names <code><gamename>_<yoursoundname>[.ogg][.mp3]</code>.<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>
Loenix34
http://en.doc.boardgamearena.com/index.php?title=Troubleshooting&diff=10032
Troubleshooting
2021-11-03T12:13:22Z
<p>Loenix34: Add link to action doc page</p>
<hr />
<div><br />
Describing common errors which is hard to understand and debug <br />
<br />
__TOC__<br />
<br />
<br />
== Game does not start at all ==<br />
<br />
=== Undefined offset: 0 in table/table.game.php on line 830 ===<br />
<br />
Check if you're calling self::getActivePlayerName () during setupNewGame()<br />
<br />
Check if you're NOT calling self::activeNextPlayer() at the end of your game setup. You must always have at least one active player.<br />
<br />
=== Unexpected error: Wrong formatted data from BGA gameserver 1 (method: createGame): ... ===<br />
<br />
This is generic message usually followed by exact position in your source code, and usually its syntax error in one of yours php script<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Not logged ===<br />
<br />
Calling self::getCurrentPlayerId () or using $g_user from 'args' state function, see also below<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Unknow player statistic: ===<br />
<br />
Calling self::incStat() with second parameter which is an empty string<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: Error while processing SQL request: INSERT INTO stats ... ===<br />
<br />
Fatal error during yourgame setup: Error while processing SQL request: INSERT INTO stats (stats_type, stats_player_id, stats_value) VALUES ('10','2300663','0'),('10','2300662','0')<br />
Duplicate entry '10-2300663' for key 'stats_table_id'<br />
<br />
Why? In the stats.inc.php you declared two keys with the same integer "id"<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): Fatal error during yourgame setup: BGA main website do not respond ===<br />
<br />
Check if other games work; if not, it's a problem with BGA Studio; if so, your game likely reaches an end state immediately. Check your states.inc.php and your transitions.<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: createGame): BGA service error ===<br />
<br />
You may have a syntax error in your dbmodel.sql.<br />
<br />
Or, you may be calling self::initStat() for a statistic that does not exist. Check the statistic name, and make sure you clicked "Reload statistics configuration" button in the admin console after making changes to stats.inc.php<br />
<br />
Or you may not have your states.inc.php set up correctly.<br />
<br />
You may have an error in your table.game.php file (such as <code>$result['x'] = y;</code> when $result is not an array).<br />
<br />
=== Unexpected error: Table status of a starting game should be 'setup' ===<br />
<br />
This error message appears when you start a one player game using the "Express Start" button.<br />
<br />
In normal situation, the game starts just after this message and everything should works normally. In this case, you can just ignore this message.<br />
<br />
If the games does not start, it is most probable that there is a syntax error in any of your php code (Check your states.inc.php and X.game.php)<br />
<br />
=== Fatal error during creation of database ebd_quoridor_389 Not logged ===<br />
<br />
Check that you didn't use $g_user or getCurrentPlayerId() in setupNewGame() function or in an 'args' function of your state.<br />
<br />
As these functions are not consequences of a user action, there is no current player defined.<br />
<br />
As a general rule, you should use getActivePlayerId() and not getCurrentPlayerId(). See the [http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine presentation on the game state machine] for more information.<br />
<br />
=== Warning: Invalid argument supplied for foreach() in table.game.php ===<br />
<br />
Warning: Invalid argument supplied for foreach() in /var/tournoi/release/tournoi-151226-1240-gs/www/game/module/table/table.game.php on line 129 <br />
Fatal error: Cannot unset string offsets in /var/tournoi/release/tournoi-151226-1240-gs/www/game/module/table/table.game.php on line 143<br />
<br />
That appears when your arg* function that suppose to return array of state arguments returns a scalar (a non-array) value<br />
<br />
=== The server reported an error ===<br />
<br />
During table creation: "The server reported an error" error shown and nothing else.<br />
<br />
If you cannot even create a table - there is syntax error in gameinfos.php, check it, reload it from management panel.<br />
If still no luck copy clean version from template https://github.com/elaskavaia/bga-sharedcode/blob/master/gameinfos.inc.php<br />
<br />
=== Unexpected Error : Invalid player number for this game : 4 ===<br />
<br />
This error may comes when trying to change the player number configuration in gameinfos.inc.php from a higher to lower number of players (for example from 4 to 2).<br />
<br />
The following steps will help to change a player config from array(4) to array(2) :<br />
<br />
* change in gameinfos.inc.php the entry to 'players' => array( 2,3,4 )<br />
* In game management page (Control Panel > Manage Games > "Your Game"), press the "Reload Game information" button<br />
* create a new table<br />
* reduce the number of players to 2 and start the game<br />
* (express) Stop the game<br />
* change in gameinfos.inc.php the entry to 'players' => array( 2 )<br />
* press again the "Reload Game information" button<br />
<br />
=== Blank page ===<br />
If you see no html at all check <br />
* if you have syntax error in your view.php file <br />
* class name of your view.php file does not match the project name (should be view_foo_foo if project is foo)<br />
* game name return by getGameName() does not match the project name (should be "foo" if project is foo)<br />
<br />
class view_russianrailroads_russianrailroads extends game_view {<br />
function getGameName() {<br />
return "russianrailroads";<br />
}<br />
<br />
== Game starts but I can't make a move ==<br />
<br />
=== When I do a move, I got "Move recorded, waiting for update ..." forever ===<br />
<br />
"Move recorded" means that your ajaxcall request has been sent to the server and returned normally.<br />
<br />
"Waiting for update" means that your client interface is waiting for some notifications from the server that correspond to the move we just did.<br />
<br />
If this message stays forever, it is probably that your PHP code does not send any notification when the move happens, which is abnormal. To fix this: add a notifyAllPlayers or a notifyPlayer call in your PHP code.<br />
<br />
=== When I do a move, I get "Sending move to server..." then nothing and game resets to state before the move ===<br />
<br />
Its possible that server code get into infinite loops or thinks too much, in which case it will timeout and will be aborted without any extra logs (and db transaction you saw in the log won't be committed). You will usually see "Unable to connect to server" message on console in this case. You have to put more logging into server<br />
to trace where it hangs.<br />
<br />
=== When I do a move, I get "Ajaxcall error: empty answer" ===<br />
<br />
You either have missing self::ajaxResponse(); in your [[Players actions: yourgamename.action.php|game.action.php]] method, or there is some problem with getting arguments (check that you are using btoa() on client when using AT_base64 arg type in action.php)<br />
<br />
=== Some player action is triggered randomly when I click somewhere on the game area ===<br />
<br />
You probably used "dojo.connect" on a null object. In this case, dojo.connect associate the event (ex: "onclick") to the whole game area.<br />
<br />
Most of the time it happens in this situation, when my_object element does not exists:<br />
<pre><br />
dojo.connect( $("my_object"), "onclick", this, (event)=>{})<br />
</pre><br />
<br />
To determine if this is the case, place "alert( $("my_object") )" before the dojo.connect to check if the object exists or not.<br />
<br />
=== Error: This is not your turn ===<br />
<br />
This happens when checkAction() is called and current player is NOT the active player.<br />
<br />
You can change the active player in studio by clicking the red arrow next to their name in the player panel (right side of the page).<br />
<br />
[[File:Change_active_player.jpg]]<br />
<br />
=== Error: Please wait, an action is already in progress ===<br />
Its followed by error "(Generated by checkAction / XXX)" on studio.<br />
<br />
This error is triggered in the JS by the checkAction function for XXX action.<br />
<br />
This can happen when the player sent an Ajax request action earlier, and:<br />
* action is not complete yet - i.e. takes some time, and user clicked something again <br />
* if there is bug in interface where MULTIPLE handlers are fired on same click. To debug this add some console.trace() in place this specific checkAction is called.<br />
* did not get any notification from the PHP side. To avoid this, put an empty notifyPlayer in the PHP side of the Ajax handler to ensure the client receives a response. This line of code should do it:<br />
<pre><br />
$this->notifyPlayer($this->getCurrentPlayerId(), 'message', '', []); // sent to current player, who originated the action<br />
</pre><br />
<br />
=== Error: This move is not authorized now ===<br />
This is generic error message generated by this.showMoveUnauthorized() function. If you did not call this function yourself, check the console log, it may have additional information.<br />
If you see "Move not authorized now : XXX" in the log, where XXX is your action name - this means that action XXX is not defined in list of possible actions for your current state. This error generated by checkAction() function.<br />
<br />
== Predefined server errors ==<br />
<br />
=== Unexpected error: Unexpected final game state (XX) ===<br />
<br />
The action function does not transition to any state, i.e.<br />
function selectField($field) {<br />
self::checkAction ( 'selectField' );<br />
if ($field!=0) $this->gamestate->nextState ( 'next' );<br />
}<br />
Here if $field is 0 there is no transition<br />
<br />
=== This game action is impossible right now ===<br />
<br />
Check the game log. Usually your state does not define the action you trying to perform in 'possibleactions' array.<br />
<br />
Also check your onActionName javascript function. Make sure you are calling the right "actionname.html".<br />
<br />
=== Unexpected error: This transition (playerTurn) is impossible at this state (42) ===<br />
<br />
This is pretty self explanatory. Function nextState() takes transition name not a state name, so you probably did not<br />
define this transition that the given state<br />
<br />
== Game interface hangs during reload or on start ==<br />
<br />
Showing "Application Loading..."<br />
<br />
=== Javascript error: During pageload undefined no_stack_avail Script: ===<br />
<br />
This error usually has no useful data, but it means you called somes API that require a callback and did not define callback function, i.e<br />
in dojo.connect, this.connectClass, dojo.subscribe, etc<br />
<br />
this.connectClass('field', 'onclick', 'onField'); // <-- onField is not defined<br />
<br />
=== Other errors with "Application loading..." ===<br />
<br />
You probably have a syntax error in your Javascript code, and the interface refuses to load.<br />
<br />
To find this error, check if there is an error message in the Javascript console (F12).<br />
<br />
If there is really nothing on the log, it's probably that the system was unable to load your Javascript because of an syntax error that affect the structure of the Javascript file, typically a missing "}" or a missing "," after a method definition.<br />
<br />
If you have "Uncaught ReferenceError: bgagame is not defined" you have major syntax error in your js file, you should see some other clues in the log where the errors is.<br />
<br />
If you have "dojo.publish is not defined" or something similar, this could be caused by another error earlier. For example, a syntax error caused by a malformed generated file (such as using newlines in tiebreaker description). Also you might have left some images in the /img directory with special characters in the name which must be removed. If you edited images for the game box and website, ensure that you have not left any unnecessary files there.<br />
<br />
=== Unexpected Syntax Error: ===<br />
<br />
No further details in the log. When log is filling with some social connect errors.<br />
<br />
Possible Reason: Syntax error in of the php script which is loaded before the start, such as gameoptions.inc.php, gameinfos.inc.php and such.<br />
<br />
=== Game interface spins in a loop throwing error ===<br />
<br />
Errors is something like "Cannot read property 'is_ai' of undefined". Cannot restart the game because cannot access UI to stop.<br />
Likely you get in actplayer state with player id == 0. The only way to fix it is to edit database, globals index == 2 set player id to one of your test dudes<br />
(can copy from row 5 for example).<br />
<br />
=== Unable to find table database (1): ebd_yourgame_112222 ===<br />
<br />
Its either temporarily server error especially on studio it timeouts sometimes, just try reloading again.<br />
Or check if you have syntax errors in your php game file or sql.<br />
<br />
== Type conversion / juggling errors ==<br />
<br />
=== On php side I get a number instead of string I expect ===<br />
<br />
$num = 3;<br />
$meeple = "meeple_" + $num; // <-- suppose to be "meeple_3"!<br />
<br />
When you switch between JS and PHP it easy to type this and not notice the +. Plus sign (+) in php does not mean string concatenation (in javascript does!),<br />
in php + means integer arithmetic. So change + to . (dot)<br />
<br />
=== On php side my string comparison does not work ===<br />
<br />
if ($color == '4baae2' || $color == '000000') { <br />
}<br />
<br />
Apparently you should not be using '==' in php to compare strings! You should use '==='. The (==) operator will typecast the strings <br />
to numbers then do comparison!<br />
Its not very apparent because usually you can get away with it, but not when strings resemble numbers like hex 'colors'.<br />
<br />
=== Integer columns in the database are returned as strings ===<br />
<br />
This is normal PHP behavior. All fields are returned as string type, regardless of their actual type in the database. This also applies to global variables accessed via getGameStateValue(), since they are stored in a database table.<br />
<br />
You can use type-casting to convert them to the correct type after they come out of the database.<br />
<br />
$myValueInt = (int)self::getGameStateValue(GLOBAL_ROUND_NUMBER);<br />
<br />
or<br />
<br />
$myValueInt = self::getGameStateValue(GLOBAL_ROUND_NUMBER) + 0; // adding 0 casts to int<br />
<br />
<br />
same with direct qdb ueries<br />
$sql = "SELECT my_int_column FROM my_table WHERE my_condition";<br />
$myResult = self::getUniqueValueFromDB($sql); <-- this is a string<br />
$myResultAsInt = (int)$myResult;<br />
<br />
https://stackoverflow.com/questions/5323146/mysql-integer-field-is-returned-as-string-in-php<br />
<br />
== Zombie mode ==<br />
<br />
=== Unexpected error: Propagating error from GS 1 (method: zombie): Not logged ===<br />
<br />
You are probably calling getCurrentPlayerId() or getCurrentPlayerName() in your zombieTurn method or any of the methods it uses. Instead, use the $active_player_id provided as parameter to zombieTurn().<br />
<br />
If you do not see these commands within "function zombieTurn( $state, $active_player ){}", check all the functions called from zombieTurn, also "function getGameProgression(){}" and all the functions THAT calls.<br />
<br />
=== Unexpected error: Can't manage zombie player in this game state ===<br />
<br />
The state the game was in at the time the error was generated didn't have "transitions" => array( "zombiePass" => 27 )" in states.inc.php (27 was an arbitrary example, but everything else must be spelled exactly that way.)<br />
<br />
To correct this error, either add "zombiePass" => ## to the transitions array or work out why the game is in a state it should not be in.<br />
<br />
=== Unexpected error: Wrong formatted data from BGA gameserver 1 (method: zombie): ===<br />
<br />
This is almost certainly an undefined value in PHP code. Look for a warning or error message in the game replay log on the right side of the table UI.<br />
<br />
=== Unexpected error: BGA gameserver 1 do not respond (method: zombie) (timeout: cluster) ===<br />
<br />
You are trying to end the game from a zombie method. This is not allowed. The zombie logic must continue the game as best it can. See [[Main_game_logic:_yourgamename.game.php#Zombie_mode|Zombie mode]] for more info.<br />
<br />
=== Game unexpectedly ends when one player becomes zombie ===<br />
<br />
There is an error in handling zombie mode, check error log. Example: if the zombie turn is called again, framework detects a infinite loop and cancels the game. On the studio there is a notification called ZombieTurnFailed<br />
<br />
== Other errors ==<br />
<br />
=== Javascript does not know how to sum two numbers ===<br />
<br />
Be careful when you manipulate integers returned by notifications: most of the time, Javascript considers they are Strings and not Integers.<br />
<br />
As a result:<br />
<pre><br />
var i=1;<br />
i += notif.args.increment; // With notif.args.increment='1'<br />
alert( i ); // i=11 instead of 2 !! Javascript concatenate 2 strings !<br />
</pre><br />
<br />
To solve this, you should use the "toint" function:<br />
<pre><br />
var i=1;<br />
i += toint( notif.args.increment ); // With notif.args.increment='1'<br />
alert( i ); // i=2 :)<br />
</pre><br />
<br />
=== Javascript: do not use substr with negative numbers ===<br />
<br />
To get the last characters of a string, use "slice" instead of "substr" which has a bug on IE:<br />
<pre><br />
var three_last_characters = string.substr( -3 ); // Wrong<br />
var three_last_characters = string.slice( -3 ); // Correct<br />
</pre><br />
<br />
=== Game "spontaneously" transition to a new state without user input ===<br />
<br />
Make sure on php side you have no code after $this->gamestate->nextState(...) code.<br />
Because if you do accidentally have code that goes to another state it will cause another state transition without user interaction.<br />
<br />
function selectField($field) {<br />
self::checkAction ( 'selectField' );<br />
if ($field!=0) $this->gamestate->nextState ( 'next' );<br />
$this->gamestate->nextState ( 'last' ); // <-- here is missing else, so it will cause double state transition<br />
}<br />
<br />
=== Message "Invalid or missing substitution argument for log message: ${actplayer} ... " when entering a state ===<br />
<br />
You probably have an args function defined for that state that does '''not''' return an array</div>
Loenix34
http://en.doc.boardgamearena.com/index.php?title=Game_interface_logic:_yourgamename.js&diff=9706
Game interface logic: yourgamename.js
2021-10-14T13:19:55Z
<p>Loenix34: </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 />
;setup(gamedatas) <br />
This method must set up the game user interface according to current game situation specified in parameters.<br />
The method is called each time the game interface is displayed to a player, ie:<br />
<br />
- when the game starts<br />
<br />
- when a player refreshes the game page (F5)<br />
<br />
"gamedatas" argument contains all datas retrieved by your "getAllDatas" PHP method.<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 active 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 (this.isCurrentPlayerActive()) with (!this.isSpectator) <br />
for the main switch in that method.<br />
<br />
See more details in [[Your_game_state_machine:_states.inc.php#Diffrence_between_Single_active_and_Multi_active_states]]<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 (i.e. cleanup).<br />
<br />
;onUpdateActionButtons(stateName, args)<br />
In this method you can manage "action buttons" that are displayed in the action status bar and highlight active UI elements.<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 calls would depends on either you get into that state from transitions OR 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). Note: see remarks above about usage of this function inside onEnteringState method.<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 />
; g_replayFrom<br />
: Global contains reply number in live game, it is set to undefined (i.e. not set) when it is not a reply mode, so consequentially the good check is '''g_replayFrom !== undefined''' which 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 (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 can use '''getElementById''' but a longer to type and less handy as it does not do some checks.<br />
Note²: It is safe to use if you don't know if variable is string (id of element) or element itself, i.e. <br />
<pre><br />
foo: function(card) {<br />
card = $(card); // now its node, no need to write if (typeof card === 'string') ...<br />
// but its good idea to check for null here<br />
...<br />
}<br />
</pre><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 />
'''Vanila JS style'''<br />
$('my_element').style.display='none'; // set<br />
var display = $('my_element').style.display; // get<br />
$('my_element').style.removeProperty('display'); // remove<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 />
'''Vanila JS attr'''<br />
<br />
$(token).id=new_id; // set attr for "id"<br />
var id = $(token).id; // get<br />
<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 />
'''Vanila JS query'''<br />
<br />
var cards=document.querySelectorAll(".hand .card");// all cards in all hands<br />
var cards=$('hand').querySelectorAll(".card");// all cards in specific hand<br />
var card=document.querySelector(".hand .card");// first card or null if none (super handy)<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 />
positive integer: This parameter can be a positive 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 />
But you can also relocate elements like that. Note: it won't animate if you do that.<br />
<br />
<br />
'''dojo.empty'''<br />
<br />
Remove all children of the node element<br />
dojo.empty('my_hand');<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 />
'''this.format_block'''<br />
This bga function that takes global var from template file and substitute variables, typical use would be<br />
<br />
var player = gamedatas.players[player_id];<br />
var div = this.format_block('jstpl_player_board', player ); // var jstpl_player_board = ... is defined in .tpl file <br />
<br />
'''this.format_string'''<br />
This bga function just substitute variables in a string, i.e.<br />
var div = this.format_string('<div color="${player_color}"></div>', {player_color: '#ff0000'} );<br />
<br />
'''this.format_string_recursive'''<br />
This bga function is similar to this.format_string but is capable of processing recursive argument structures and translations. It is used to format server notifications.<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, but it starts the animation. <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. Its starts the animation.<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 />
Note: This wiki was probably written 10 years ago - now all modern broser support 'transform' so just ignore this and use transform.<br />
<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 and that it is centered.<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, ajax_method )'''<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 />
* ajax_method: (optional and rarely used) if you need to send large amounts of data (over 2048 bytes), you can set this parameter to 'post' (all lower-case) to send a POST request as opposed to the default GET. This works, but was not officially documented, so only use if you really need to.<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 />
<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 all notifications arguments with "notif.args".<br />
<br />
Example:<br />
<br />
PHP<br />
<br />
<pre><br />
self::notifyAllPlayers( "myNotification", '', array( "myArgument" => 3 ) );<br />
</pre><br />
<br />
JavaScript<br />
<br />
<pre><br />
//You can access the "myArgument" like this:<br />
notif_myNotification: function( notif )<br />
{<br />
alert( "myArgument = " + notif.args.myArgument );<br />
}<br />
</pre><br />
<br />
=== The notification Object received by client ===<br />
<br />
When sending a notification on your PHP, the client side will receive an Object with the following attributes:<br />
<br />
* args : This is the arguments that you passed on your notification method on php<br />
* bIsTableMsg : Boolean, is true when you use [[Main_game_logic:_yourgamename.game.php#NotifyAllPlayers|NotifyAllPlayers]] method (false otherwise)<br />
* channelorig : information about table ID (formatted as : "/table/t[TABLE_NUMBER]")<br />
* gamenameorig : name of the game<br />
* log: the log information as written in PHP function<br />
* move_id : ID of the move associated with the notification<br />
* table_id : ID of the table<br />
* time : UNIX GMT time<br />
* type : name of the notification<br />
* uid : identifier of the notification<br />
<br />
'' Note that those information were inferred from observation on console log. If an Admin can confirm/correct (and remove this line), you're welcome :)''<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, there 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 />
=== WARNING: combining synchronous and ignored notifications ===<br />
You must be careful when combining dynamic synchronous durations (as described above) with ignored notifications. If you have a conditionally ignored notification like this (see below section):<br />
<br />
<pre>this.notifqueue.setIgnoreNotificationCheck( 'myNotif', (notif) => (notif.args.player_id == this.player_id) /* or any other condition */ )</pre><br />
<br />
then you CANNOT do<br />
<br />
<pre>this.notifqueue.setSynchronous('myNotif');</pre><br />
<br />
as, when the ignored check passes, the notification handler, in which `this.notifqueue.setSychronousDuration` is called, is never called and so the duration is never set and interface locking results.<br />
<br />
The workaround is to set a "dummy" time:<br />
<br />
<pre>this.notifqueue.setSynchronous('myNotif', 5000);</pre><br />
<br />
whose value is irrelevant but must be large enough to cover the time before the notification handler is called. The large value never actually comes into play because the notification is either ignored, or the synchronous duration is reset to a sensible value inside the handler.<br />
<br />
<br />
=== Ignoring notifications ===<br />
Sometimes you need to ignore some notification on client side. You don't want them to be shown in game log and you don't want them to be handled.<br />
<br />
The most common use case is when a player gets private information. They will receive a specific notification (such as "You received Ace of Heart"), while other players would receive more generic notification ("Player received a card").<br />
<br />
In X.game.php<br />
<pre><br />
$this->notifyAllPlayers("dealCard", clienttranslate('${player_name} received a card'), [<br />
'player_id' => $playerId,<br />
'player_name' => $this->getActivePlayerName()<br />
]);<br />
<br />
$this->notifyPlayer($playerId, "dealCardPrivate", clienttranslate('You received ${cardName}'), [<br />
"type" => $card["type"],<br />
"cardName" => $this->getCardName($card["type"])<br />
]);<br />
</pre><br />
<br />
The problem with this approach is that the active player will receive two notifications:<br />
* Player1 received a card<br />
* You received Ace of Hearts<br />
<br />
Hence, notification ignoring. Similar to setting a synchronous notification above, you can set up a check whether a notification should be ignored:<br />
<pre><br />
this.notifqueue.setIgnoreNotificationCheck( 'dealCard', (notif) => (notif.args.player_id == this.player_id) );<br />
</pre><br />
<br />
'''setIgnoreNotificationCheck(notificationId, predicate)'''<br />
This method will set a check whether any of notifications of specific type should be ignored.<br />
<br />
The parameters are:<br />
* notificationId: before dispatching any notification of this type, the framework will call predicate to check whether notification should be ignored<br />
* predicate (notif => boolean): a function that will receive notif object and will return true if this specific notification should be ignored<br />
<br />
NOTE: You can think that it could be possible to send generic notification to all other players with notifyAllPlayers and it will seem to work. The problem is that table spectators would miss this notification and their user interface (and game log) wouldn't be updated.<br />
<br />
IMPORTANT: Remember that this notification is ignored on the client side, but was still received by the client. Therefore it shouldn't contain any private information as cheaters can get it. In other words this is not a way to hide information.<br />
<br />
IMPORTANT: When a game is reloaded with F5 or when opening a turn based game, old notifications are replayed as history notification. They are used just to update the game log and are stripped of all arguments except player_id, i18n and any argument present in message. If you use and other argument in your predicate you should preserve it as explained [[Main_game_logic:_yourgamename.game.php#Notify_players|here]].<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, helpStringTranslated, actionStringTranslated, delay )'''<br />
<br />
Add a simple text tooltip to the DOM node.<br />
<br />
Specify 'helpStringTranslated' to display some information about "what is this game element?".<br />
Specify 'actionStringTranslated' to display some information about "what happens when I click on this element?".<br />
<br />
You must specify both of the strings. You can only use one and specify an empty string ('') for the other one.<br />
<br />
When you pass text directly function _() must be used for the text to be marked for translation! Except for empty string.<br />
<br />
Parameter "delay" is optional. 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 />
Note: this generates static tooltip and attaches to existing dom element, if you need to generate tooltip more dynamically you have to call that method every time information about object is updated or use completely different tehnique<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, helpStringTranslated, actionStringTranslated, delay )'''<br />
<br />
Add a simple text tooltip to all the DOM nodes set with this cssClass. See more details above for this.addTooltip.<br />
this.addTooltipToClass( 'meeple', _('This is A Meeple'), _('Click to tickle') );<br />
<br />
IMPORTANT: all concerned nodes must exist and 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 exist and have IDs to get tooltips.<br />
<br />
'''this.removeTooltip( nodeId )'''<br />
<br />
Remove a tooltip from the DOM node with given id.<br />
<br />
== Dialogs, warning messages, confirmation dialogs, ... ==<br />
<br />
=== Warning messages ===<br />
<br />
Sometimes, there is something important that is happening in 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 fulfills one of the end of the game conditions, 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 the current player.<br />
<br />
* "msg" is the string to display. It should be translated.<br />
* "type" can be set to "info", "error", or "only_to_log". 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. If set to "only_to_log", the message will be added to the game log but will not popup at the top of the screen.<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 guideline of BGA is to AVOID the use of confirmation dialogs. Confirmation dialogs slow down the game and bother players. The players know that they have to pay attention to each move when they are playing online.<br />
<br />
The situations 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 does 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. Note that the score is centered in the anchor, so the offsets might have to be negative if you calculate the position.<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 />
<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 />
<br />
'''buttons with images'''<br />
<br />
You can use the same method, but add extra class to a button to disable the padding and style it, i.e.<br />
<pre><br />
this.addActionButton( 'button_brick', '<div class="brick"></div>', ()=>{... on brick ...}, null, null, 'gray'); <br />
dojo.addClass('button_brick','bgaimagebutton');<br />
</pre><br />
<br />
where<br />
<br />
<pre><br />
.bgaimagebutton {<br />
padding: 0px 12px;<br />
min-height: 28px;<br />
border: none;<br />
}<br />
</pre><br />
<br />
If you use this a lot, you can define a helper function, i.e.<br />
<pre><br />
/**<br />
* This method can be used instead of addActionButton, to add a button which is an image (i.e. resource). Can be useful when player<br />
* need to make a choice of resources or tokens.<br />
*/<br />
addImageActionButton: function(id, div, handler, bcolor, tooltip) {<br />
if (typeof bcolor == "undefined") {<br />
bcolor = "gray";<br />
}<br />
// this will actually make a transparent button id color = gray<br />
this.addActionButton(id, div, handler, '', false, bcolor);<br />
// remove boarder, for images it better without<br />
dojo.style(id, "border", "none");<br />
// but add shadow style (box-shadow, see css)<br />
dojo.addClass(id, "shadow bgaimagebutton");<br />
// you can also add addition styles, such as background<br />
if (tooltip) {<br />
dojo.attr(id, "title", tooltip);<br />
}<br />
return $(id);<br />
},<br />
</pre><br />
<br />
'''buttons outside of action bar'''<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 />
Note: You can also create button using addActionButton() method, then move anywhere<br />
<pre><br />
this.addActionButton( 'commit_button', _('Confirm'), 'onConfirm', null, true, 'red'); <br />
dojo.place('commit_button','player_board');<br />
</pre><br />
<br />
'''Disabling:'''<br />
You can disable the '''bgabutton''' by adding the css class '''disabled''' in you js. The disabled button is still visible but is grey and not clickable.<br />
For example in the '''onUpdateActionButtons''' : <br />
<pre>this.addActionButton( 'play_button_id', _('Play 1 to 3 cards'), 'playFunctionButton', null, false, 'blue' ); //Create a blue button<br />
if (Condition == true)<br />
{<br />
dojo.addClass( 'play_button_id', 'disabled');//disable the button<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/<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>
Loenix34
http://en.doc.boardgamearena.com/index.php?title=Create_a_game_in_BGA_Studio:_Complete_Walkthrough&diff=8950
Create a game in BGA Studio: Complete Walkthrough
2021-07-28T08:57:21Z
<p>Loenix34: /* Hook Input and Animation */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Introduction ==<br />
<br />
This document is not a tutorial, but step by step instructions on how to build your own first game adaptation using BGA Studio framework.<br />
<br />
Before you read this material, you must:<br />
* Read the overall presentations of the BGA [[Studio]].<br />
* Some-what know the languages used by BGA Studio: PHP, SQL, HTML, CSS, Javascript<br />
* Setup your development environment [http://en.doc.boardgamearena.com/First_steps_with_BGA_Studio First Steps with BGA Studio]<br />
* Create a game using one of the available tutorials. Don't bother with a new game if you have not completed at least one of the tutorials.<br />
<br />
<br />
If you are stuck or have questions about this page post on [https://forum.boardgamearena.com/viewforum.php?f=12 BGA Developers forum].<br />
If you're uncomfortable posting on the public forum you can send messages directly to developers who post answers on that forum but NOT the BGA admins.<br />
If you find typos in this wiki - fix it.<br />
<br />
== Select a First Game ==<br />
<br />
For your first '''real''' game you must either<br />
* Select a game from [https://en.studio.boardgamearena.com/licensing Available Licenses]<br />
* Or from the Public Domain<br />
<br />
But what if the game you want is not there? If you are able to successfully publish your first game, you would gain the trust of the BGA admins and they will be happy to assist you in obtaining a license for a game you really want to do or you can request a license yourself. You can read more about game licenses on [[BGA Game licenses]] page.<br />
<br />
Once you selected the game but before creating a new project, please take a few seconds to check that someone is not already developing this game. If it is the case, maybe you can propose to join the project?<br />
<br />
[http://en.studio.boardgamearena.com/#!projects Check the list of current projects]<br />
<br />
Even if you see a few projects with name of the game they may not be active. There are a lot of abandoned game projects. If it's not clear by the status, post to Developers forum asking if anybody actively working on the project or send a message to developers listed for the abandoned projects, and at the same time ask admins on the same forum post to send you graphics for that game if they have them (there a button on [https://en.studio.boardgamearena.com/licensing Available Licenses] page to request graphics, but it will just send email).<br />
<br />
If your goal was to fix bugs in an existing project, first try to locate on studio, projects developed by bga admins are not in the studio. Then get read only access to the project and you can create your own as a copy of the existing one. Contact existing project admin about getting write access to the original project or if they willing to take your patches - apply them.<br />
<br />
If you want to take over an existing project first ask on forum to see if project is abandoned, then get read only access (via project list) and see if this worth using it, if it has no code or graphics just start from the scratch, don't worry about project name it can be renamed later.<br />
<br />
== Create a project ==<br />
<br />
If you have not already, you have to create a project in BGA Studio for this game. If the original game name is taken use gamenameYOURINITIALS<br />
template, i.e."heartsla". Don't worry too much about the name, if game would be good enough to be publish it will be renamed to original name. <br />
<br />
Find and start the game in turn based mode, make sure it works.<br />
<br />
Second, modify the text in .tpl file, reload the page in the browser and make sure your ftp sync works as expected.<br />
Note: if you have not setup [http://en.doc.boardgamearena.com/Tools_and_tips_of_BGA_Studio#File_Sync FTP auto-sync] yet, do it now, manually copying files is a no-starter.<br />
<br />
Update your project status in [http://en.studio.boardgamearena.com/#!studio Control Panel > Manage games] page, you can say "development started" or "waiting for license" or "waiting for graphics" or combination of those.<br />
<br />
<br />
<br />
== Development Tools ==<br />
<br />
At some point you need to setup your development environment which consist of multiple tools, such as<br />
* Editor or IDE<br />
* Browser with dev tools<br />
* File sync tools<br />
* BGA Web tools<br />
* Image manipulation tools<br />
* Version control tools<br />
<br />
Please scan though articles from [[Studio#BGA_Studio_user_guide]] especially related to debugging and tools, there is a lot of useful info there.<br />
<br />
== Hook version control system ==<br />
<br />
If its a real game I would commit the code to version control right at start. You going to find yourself in the situation<br />
when game does not even start anymore and no way of debugging it unless you have a way to revert. That is where version control becomes very handy.<br />
If you don't know what I am talking about then at least back-up your files after each of major steps. Starting now.<br />
You can also create a project on github, but make sure '''you don't commit original publisher graphics files''' and '''you don't include a file with your sftp password''' (github is automatically crawled for passwords by hackers; a hacking attempt occurred on BGA studio for this reason in June 2020).<br />
You can (and should) also commit your modification periodically via studio's control panel.<br />
<br />
== Obtain game graphics ==<br />
<br />
If you developing a game from Available Licenses games, ask the admins to send you graphics by contacting studio@boardgamearena.com. While that request is being processed (it can take time, as it often requires some back and forth between the admins and the publishers) you can proceed to next step - project creation.<br />
<br />
If you don't get original graphics you go to '''Scavenger Hunt'''<br />
<br />
* If you developing a public domain card game you can borrow standard cards graphics from hearts project (see [[Tutorial hearts]]).<br />
* Standard game pieces - meeples, cubes, dice can be found here https://github.com/elaskavaia/bga-sharedcode/tree/master/img<br />
* Go to boardgamegeek.com find your game and obtain 3D game box image, 2D box image, and if you lucky they also sometime have boards and token scans in "Game Pieces" section of Images<br />
* If that fail google "boardgame <name>" and check Images section<br />
* Get the rules PDF as well, there tools that allows you to extract graphics from PDF, which usually good for meeples, cubes and such<br />
<br />
Once you get the graphics one way or another you have to massage it to fit in the BGA criteria, which usually involves<br />
* If publisher sends graphics in one token/card per file mode, you have to stitch them in sprite and scale down<br />
* For non square tiles and game pieces you need transparency<br />
* Usually you chop off scoring "ring" around the board of the game since scoring track not needed for online adaptation<br />
<br />
More details about graphics requirements can be found here [[Game art: img directory]].<br />
<br />
[[File:Rrr_search.png]]<br />
<br />
== Obtain game documentation ==<br />
<br />
Also at this time obtain a electronic copy of rules, such as PDF (English version). <br />
<br />
Also grab any other documents you may find on boardgamegeek such as FAQ, additional Reference books, and user created assistant documents, such<br />
as cheat-sheets (may be easier to get a data from these then trying to scrub pdf). You create and place them in the doc/ folder of the project then<br />
exclude them from version control. There is also a misc/ folder now but it will hold up to 1 Mb of data files which would be checked in, so rules pdf's may not fit there.<br />
<br />
<br />
== Update game infos and box graphics ==<br />
<br />
Even it does not nothing yet start with making sure game looks descent in the game selector, meaning it has nice box graphics and information is correct. <br />
<br />
For that we need to edit [[Game_meta-information: gameinfos.inc.php|gameinfos.inc.php]].<br />
What you would do for real game you would go to http://boardgamegeek.com find the game and use the information from web-site to fill the gameinfos.<br />
<br />
<br />
The next step is to replace game_box.png with proper images, usually you can find all images including publisher logo on boardgamegeek website.<br />
<br />
Details about images can be found here: [[Game art: img directory]].<br />
<br />
Now important step. You have to LOAD these files in studio website through control panel. So go to Control Panel -> Manager Games -> YOURPROJECT<br />
and press Reload for 'Reload game informations' and 'Reload game box image'<br />
<br />
[[File:Gamepanel_sharedcode.png]]<br />
<br />
Now try to start the game again. If you some-how introduced a syntax error in gameinfos file it may not actually work (game won't start).<br />
Always use "Express Start" button to start the game. You should see a standard state prompt from template. You should see X players on the right, testdude0 .. testdudeX-1.<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 />
== Fix source copyright ==<br />
<br />
Now since you have your own project, you want put your name in the copyright header, so replace<br />
<br />
© <Your name here> <Your email address here><br />
with<br />
© John Snow <jsnow@gameofthrones.com><br />
<br />
Well not exactly this but whatever your real name is. For all files in project directory, its about 10 files. Make sure project still starts after that :)<br />
<br />
== Reduce the Rules ==<br />
<br />
Programming a game will take a lot more time than you may think. Most of the projects in the studio are abandoned because of lack of patience or skill.<br />
To keep sane, start the game with *reduced* rules and try to complete that first.<br />
<br />
* If it has any expansions - do not even attempt to deal with them, not even - "I will just add graphics for them now and not use" - waste of time if you don't complete basic<br />
* If it has advanced rules - start with basic rules only, i.e. "beginner game"<br />
* If it has special rules for 2 player vs 4, start with most basic form (i.e. 4), restrict to 4 players <br />
* If it has 50 unique cards of 2 each - start with 2 unique cards with 25 each (just to keep it moving)<br />
* Any sort of rules that you think can be removed and not included in base - set aside for now <br />
* Ignore any sort of cool animations - dice rolling, card flipping, choo-choo sounds of the trains - all this fluff can be added later<br />
<br />
<br />
== Design Game Elements ==<br />
Technically game elements are already designed by board game designer but your job is to map it to program space.<br />
Each physical piece (card, token, cube) will leave footprints all over the code (unfortunatly in multiple disconnected places).<br />
To prepare the game you need to sort out these elements, i.e. categorize. I usually have to the following categorization (in object oriented view):<br />
* Instance - all individual pieces are instances, i.e. two red cubes are two instances of 'red cube' type (class)<br />
* Type - element type which distinctly represents that element in appearence (i.e. red cube is different type than blue cube)<br />
* Super Type - one of more common types that similar properties (i.e. red OR cube)<br />
* Player color - supertype specific for player color (sometimes there is no colors but like player 1 - but is conceptually the same, I use color because its easier to track)<br />
<br />
Personally I like to encode my elements in string using reverse dns notation listing all the properties above, i.e.<br />
meeple_ff0000_7 - this is instance #7 of type meeple_ff0000 (red meeple)<br />
Or<br />
card_yellow_magic_2 - this is instance #2 of yellow card (in this case yellow is color of deck not related to player color) that can do magic<br />
<br />
So every game element would be in the<br />
<br />
1. Database - instances. The db record would be something like <br />
key|location|state<br />
meeple_ff0000_7|slot_action_2|1<br />
meeple_ff0000_2|tableau_ff0000|0<br />
2. Material file - types and supertypes, we never need repearing info here, so never list individual instances but only types or supertypes, in this case we don't really need to define red meeple vs blue meeple<br />
'meeple'=>{'name'=>totranslate('Meeple')}<br />
3. Client (js, css, tpl, etc) - instances and types. For example my meeple will be like <br />
<pre><br />
<div id="meeple_ff0000_7" class="meeple meeple_ff0000"></div><br />
</pre><br />
with .css something like<br />
.meeple { background-image: url(img/tokens.png); width: 2em; height: 2em;}<br />
.meeple_ff0000 {background-position: 20% 0%;}<br />
4. Game php - setup and logic. During setup you have to generate all the pieces and place them in right positions. Also sometimes you need to reference elements to code the logic (I usually try to encode all rules in material file as much as possible)<br />
<br />
For complex card games I think it is the best to keep all these info and rules in spreadsheet and generate other files such as material.inc.php.<br />
See more info below about design of the individual layers.<br />
<br />
== Create Initial Layout and Game Graphics ==<br />
<br />
Mentally it is easier to start with game layout and graphics pieces. Even when nothing is working its give you moral satisfaction!<br />
<br />
There are a few ways how the html could have been generated. You could have started with nothing and generate<br />
it 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 also provides a third way, which is mix of both, plus a template engine to generate HTML using php. The only thing that is really annoying about the template engine is<br />
that you cannot put any translatable strings in the template (which means any visible text at all). If you are using the template approach all strings have to extracted as variables and injected through php (.view.php). This page explains the template engine in great detail:[[Game_layout:_view_and_template:_yourgamename.view.php_and_yourgamename_yourgamename.tpl|Template Engine]].<br />
<br />
The other disadvantage of the template engine is you cannot run and debug it locally, in the beginning of development it's a lot faster run off local pages, <br />
you can do it with some trickery described here [[Tools_and_tips_of_BGA_Studio#Speed_up_CSS_development_and_layout|Tools and Tips for BGA Studio]]<br />
<br />
During this step you have to decide what technical solutions you will be using, such as<br />
* Use inline positioning of all moving pieces, controlled by JS. There are a few classes that already exist in Studio to help with that (see [[Studio#Game_interface_.28Client_side.29|Game Interface - Client Side]]). OR use html/css layout engine to position pieces (my personal choice).<br />
* Use BGA template engine OR create all ui elements by JS OR manually write or generate complete html markup. The game usually contain 200-300 pieces, it seems wrong but actually its faster to type all of this up in html/css when trying write than debug code for page generator.<br />
Static HTML markup also means you have to use players color or abstracted player number (such as red is 1, blue is 2) not player id's anywhere in JS, since player id is dynamic by nature.<br />
<br />
Start by creating and mapping all games assets, best way is probably to open rule book on "boardgame contents" page and go through every piece. Every pieces of boardgame would have its "print" in multiple files in your game:<br />
* Some sort if "div" in html, where id of element match id of element in database (easiest way)<br />
* Css for the element (either unique or for class), usually with background propery refering to part of sprite image<br />
* Entry in material.inc.php referring to static properties of the element, i.e. name, tooltip, rules, etc<br />
* Entry in .tpl file to represent static or initial location on the table OR creation template<br />
<br />
Here are some specific examples:<br />
<br />
'''Game Board'''<br />
<br />
Create entry in .tpl file for the board, it will be static entry as we never need to create this dynamically<br />
<pre><br />
<div id="board" class="board shadow board4p"> ... </div><br />
</pre><br />
Create entry in .css file for this board and other board variants (in example below we have 4 ppl board whcih is diffrent than 2 ppl board)<br />
<pre><br />
.board {<br />
position: relative;<br />
width: 980px;<br />
height: 433px;<br />
margin-bottom: 5px;<br />
}<br />
<br />
.board4p {<br />
background-image: url(img/board4p.jpg);<br />
}<br />
</pre><br />
That wold be pretty much it for the board itself, as it does not really need a tooltip so we don't need entry in material.inc.php<br />
<br />
<br />
'''Game Board Slots'''<br />
<br />
These are interactive areas on the board, usually illustrated as such. In most cases you can get away with rectangular shapes, but sometimes you have to create circle or oval shapes (and in really advanced case would be some svg paths). For slots you can do the following:<br />
<br />
Entry in material.inc.php<br />
<pre><br />
$this->token_types = array(<br />
...<br />
'slot_action_2' => array(<br />
'type' => 'slot_action',<br />
'name' => clienttranslate("2 Gray Track Advancements"),<br />
'tooltip' => clienttranslate("This action gives you two advancements of gray track. You cannot use this action if you cannot complete all advancements."),<br />
'o'=>"1,0,0,gg", // automatic rules<br />
),<br />
...<br />
</pre><br />
<br />
Entry in template inside the "board" div<br />
<pre><br />
<div id="slot_action_2" class="slot_action_2 slot_action slot_w_1 slot"></div><br />
</pre><br />
<br />
Entry in .css with absolute position within the board (its actually better to use percentage - would be easier to scale later)<br />
<pre><br />
.slot_action_2 {<br />
top: 83px;<br />
left: 37px;<br />
}<br />
.slot_action {<br />
position: absolute;<br />
width: 46px;<br />
height: 26px;<br />
padding: 9px 7px 6px 4px;<br />
}<br />
</pre><br />
<br />
'''Meeples''' - also cards, tokens, other mobile stuff<br />
<br />
In css these guys will use "sprite" images with transparency, so it will look like this this<br />
<pre><br />
.meeple {<br />
background-image: url(img/tokens.png);<br />
width: 25px;<br />
height: 25px;<br />
}<br />
.meeple_ff0000 { /* red */<br />
background-position: 14% 0%;<br />
}<br />
</pre><br />
As for creation you can either generate them using template (where whole thing wrapped in template block and {COLOR} replace with all possible colors in .view.php<br />
<pre><br />
<div id="meeple_{COLOR}_1" class="meeple meeple_{COLOR} meepleable"></div><br />
<div id="meeple_{COLOR}_2" class="meeple meeple_{COLOR} meepleable"></div><br />
...<br />
</pre><br />
<br />
Or you can declare a template js var in .tpl file <br />
<pre><br />
var jstpl_mepple = '<div id="meeple_${color}_${num}" class="meeple meeple_${color} meepleable"></div>'; // this is in .tpl file at the bottom<br />
</pre><br />
and create in js, like this<br />
<pre><br />
var tokenDiv = this.format_block('jstpl_mepple', {<br />
"color" : color,<br />
"num" : i<br />
}); // this in js code somewhere before placing it<br />
</pre><br />
If you dealing with cards and decks, there are pre-build components that can generate stuff for you.<br />
<br />
When do you create dom element matching game element?<br />
* If you have static layout you create it in .tpl file and its always there, but during initial setup or during notification it moved in proper spot (including "removed from the game" spot)<br />
* If you dynamically generated pieces you create the element during notification, and sometimes during animation. Also don't forgot to hook event listener to it if its interactive.<br />
<br />
<br />
One of the greatest parts about the web is all client side code can be viewed in your browser, so if you wondering how something is done in another BGA game just load the page and spy on it! In Chrome that would be right click and "Inspect Element". That would immediately show html of the given element alongside with css used for it (on the right). Another great way to learn is you can add yourself to any BGA project as read only from the project page!<br />
<br />
So at the end of this stage you should complete the following (keeping in mind reduced rules/material for first iteration):<br />
* Create a layout of the game, with positioning of main board, player areas, zones, other supporting areas, etc<br />
* Create css and html snippets for all game pieces: boards, tokens, meeples, etc. Place them all in initial template (even if they're not supposed to be visible at start). I.e. create fake player's hand with cards, put meeples on the board<br />
* Hook layout to number of players and colors picked by the game and test with multiple players<br />
* Figure out what you want to display in mini-player boards and hook it up<br />
* Create material.inc.php and populate with initial values (names, tooltips, rules) for all relevant game elements or classes of elements<br />
<br />
If at this time you don't have graphics yet create pieces with just css, you can use shape, background color and object text using css ::after construct to fake the pieces.<br />
<br />
<br />
[[File:Injected_text.png]]<br />
<br />
== Hook Input and Animation ==<br />
<br />
This step can be done before or after some of the server steps, or you go in iterations switching back and forward until you get it done, up to you.<br />
<br />
At this time you want to hook clicking on pieces and buttons and provide some reaction, such of moving a piece. The handler code will be replaced later by the server hook, but at the beginning you want your game to be alive as early as possible. <br />
<br />
Usually all pieces will be hooked to onclick during JS "setup" method, in addition if you create elements during server notification they have to be hooked up at that time.<br />
<br />
You can play with animation effects you want put in place, in general all the pieces that move in real game should be moving, such as meeples, resources tokens/cubes, cards, vp tokens. <br />
Regular piece animation is provided by BGA framework, but if you use html layout positioning not inline positioning you have to remove absolute positions (inline position styling) after each move. The set of functions for relative position token animation can found in https://github.com/elaskavaia/bga-sharedcode/blob/master/sharedcode.js <br />
<br />
Also its a good idea to give player a visual cues on what game elements are clickable now, usually it will be a style, such as "active_slot", with visual effect of white dashed outline (outline is better then border, because border changes will make piece slightly move since it changes the size) or box-shadow (i.e. neon glow)<br />
<br />
If you read [http://www.slideshare.net/boardgamearena/bga-studio-guidelines BGA developers guidelines] you know that you should not get carried away with animation, you are creating a board game not a video game... That also applies to sound effects (in general, you should not use any sounds effects beside already provided by framework).<br />
<br />
See [[Game_interface_logic:_yourgamename.js#Players_input|Player's Input]] and [[Game_interface_logic:_yourgamename.js#Access_and_manipulate_the_DOM|Animation and DOM Manipulation]] for JS reference.<br />
<br />
== Create Database Schema ==<br />
<br />
At some point you have to design your game database. Do it sooner then later since it would be harder to change it later, since some<br />
code decisions would be based on that.<br />
<br />
If you have grid-based abstract game use template from reversi, if you have a card game use template from hearts (the cards one also commented out in generated template for your project). The cards database goes with php class called [[Deck]].<br />
<br />
In general make it as simple as possible. <br />
Think about it, your game has 300 pieces (likely less). Using database to store this amount of data is like shooting a mosquito with a tank.<br />
Anything more complex then one table with 5 columns or two tables will only going to make it harder to develop and not improve performance.<br />
You can forget about normalising and any fancy stuff you learn about databases in school. String field for a primary key would be as fast as integer when we talking about this size of data. So don't over-optimize with trying to have integers field that have state based on bitmask!<br />
<br />
Also remember that static (non dynamic) information about the game does not need to be stored in the database, that all include everything that does not change, i.e<br />
all token/card properties such as name, tooltips, "strength", color, etc. This is stored in material.inc.php and server has access to it from anywhere, as well as client<br />
if you send it with getAllDatas(). The only reason store some of it in database if it can affect your queries (i.e. type of token).<br />
<br />
Usually design process will contain the following steps:<br />
* Design game model - model that represent your game in progress, such as at any given step you can restore the game from that model<br />
* Mapping - now map real game to that model<br />
* Encoding - now represent this model in database and material file with reasonable amount of fields<br />
<br />
Example: '''The card game'''<br />
<br />
* In real word to "save" the game we take a picture a play area, save cards from it, then put away draw deck, discard and hand of each player separately and mark it, also we will record current scoring (if any) and who's turn was it<br />
* Framework handles state machine transition, so you don't have to worry about database design for that (i.e. who's turn it is, what phase of the game we are at, you still have to design it but as part of state machine step)<br />
* Also framework supports basic player information, color, order around the table, basic scoring, etc, so you don't have to worry about it either<br />
* The only thing you need in your database is state of the "board", which is "where each pieces is, and in what state", or (position,rotation) pair.<br />
* The card state is very simple, its usually "face up/face down", "tapped/untapped", "right side up/up side down"<br />
* As position go we never need real x,y,z. We need to know what "zone" card was, and depending on the zone it may sometimes need an extra "z" or "x" as card order. The zone position itself usually static or irrelevant.<br />
* So our model is: we have cards, which have some attributes, at any given point in time they belong to a "zone", and can also have order and state<br />
* Now for mapping we should consider what info changes and what info is static, static info is always candidate for material file or html<br />
* For dynamic stuff we should try to reduce amount of fields we need, i.e. we need a field for card, so its one, we need to know what zone cards belong to, its 2, and we have possible few other fields, but if you look closely at you game you may find out that most of the zone only need one attribute at a time, i.e. draw pile always have cards face down, hand always face up, also for hand and discard order does not matter at all (but for draw it does matter). So in majority of cases we can get away with one single extra integer field representing state or order<br />
* In real database both card and zone will be integers as primary keys referring to additional tables, but in our case its total overkill, so they can be strings as easily<br />
<br />
You can also use cards database schema and [[Deck]] implementation for most purposes (even you not dealing with cards).<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 />
Another Example: '''The euro game'''<br />
<br />
See details on database design for euro game at [[BGA_Studio_Cookbook#Database_for_The_euro_game]]<br />
<br />
<br />
So the piece mapping for non-grid based <br />
games can be in most case represented by (string: token_key, string: location, int: state), example of such database schema can be found here:<br />
[https://github.com/elaskavaia/bga-sharedcode/blob/master/dbmodel.sql dbmodel.sql] and class implementing access to it here [https://github.com/elaskavaia/bga-sharedcode/blob/master/modules/tokens.php tokens.php].<br />
<br />
<pre><br />
CREATE TABLE IF NOT EXISTS `token` (<br />
`token_key` varchar(32) NOT NULL,<br />
`token_location` varchar(32) NOT NULL,<br />
`token_state` int(10),<br />
PRIMARY KEY (`token_key`)<br />
) ENGINE=InnoDB DEFAULT CHARSET=utf8;<br />
</pre><br />
<br />
<br />
See [[Game database model: dbmodel.sql]] for details about editing the file.<br />
<br />
Note: the simpler the database is the less debugging of db issues you have to deal with including database migration. The tokens database above - if you use it you never have to worry about migration because you don't need extra tables in 95% of the games.<br />
Here are some example of how real games are mapped to such database:<br />
<br />
'''Chess''' <br />
<br />
- chess is grid base game and normally you would use positional columns, but just for the sake of argument, the chess game will look like this<br />
{| class="wikitable"<br />
|+token<br />
! token_key<br />
! token_location<br />
! token_state<br />
|-<br />
|Q_white<br />
|f3<br />
|0<br />
|-<br />
|P_black_2<br />
|c6<br />
|0<br />
|-<br />
|K_black<br />
|e8<br />
|1<br />
|}<br />
And the state in this case indicated that kind was moved for example (which means castling cannot be performed)<br />
<br />
<br />
'''Classic card game''' <br />
<br />
Lets pretend we need 2 decks for that game<br />
{| class="wikitable"<br />
|+token<br />
! token_key<br />
! token_location<br />
! token_state<br />
|-<br />
|Q_spades_1<br />
|hand_ff0000<br />
|0 /* state not used for hand */<br />
|-<br />
|10_hearts_2<br />
|tableau_ff0000<br />
|2 /* position */<br />
|-<br />
|10_hearts_1<br />
|tableau_common<br />
|1 /* face down */<br />
|}<br />
<br />
'''Eminent Domain (card game)''' <br />
<br />
{| class="wikitable"<br />
|+token<br />
! token_key<br />
! token_location<br />
! token_state<br />
|-<br />
|card_tech_23<br />
|hand_ff0000<br />
|0 /* state not used for hand */<br />
|-<br />
|card_planet_19<br />
|tableau_ff0000<br />
|1 /* face up */<br />
|-<br />
|reource_s_22 /* silicon */<br />
|card_planet_19<br />
|2 /* production state */<br />
|-<br />
|fighter_F_1<br />
|tableau_ff0000<br />
|0<br />
|}<br />
<br />
<br />
You can also look at other games that use Tokens database and access layer: Nippon, Dungeon Petz, Lewis & Clark, Battleship, Russian Railroads, Khronos<br />
<br />
== Implement Game Setup ==<br />
<br />
Once you have your database schema you can do a proper game setup. Usually you open rulebook on the "Game Setup" page<br />
and implement these step by step populating the database (using db access API).<br />
Game initialization is performed in php method setupNewGame, this method is called once when game table is created.<br />
Game notifications cannot be sent during this time.<br />
<br />
== Implement One time game model synchronisation ==<br />
<br />
<br />
Now at any point in the game we need to make sure that database information can be reflected back in UI, so we fix getAllDatas function<br />
to return all possible data we need to reconstruct the game. The template for getAllDatas already taking care of player info, but you <br />
have to alter it to return all other data from database visible to the "current" player.<br />
<br />
After that on the client side we should display this data, so in your .js file in setup function (which is the receiver of getAllDatas) you add calls that handle data send by server, usually by calling animation function such as "placeToken" or "placeCard".<br />
<br />
== Create State Machine ==<br />
<br />
Now you need to create a game state machine. <br />
<br />
The state handling spread across 4 files, so you have to make sure all the pieces are connected together.<br />
The state machine states.inc.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 />
Please first watch this again [http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine BGA game state machine]<br />
and then please read [[Your game state machine: states.inc.php]].<br />
<br />
Now the state machine should be relatively simple. If you find yourself with machine with more than 20 states its probably not the way to go.<br />
Not all the player interactions need separate states, a lot of things can be implemented directly on client, i.e. if your player need to select<br />
a reward token, which offers choice of resource, instead of two states on server just have one state on server and possible few states on client (client side states)<br />
to collect this info.<br />
<br />
== Handle Turn Order ==<br />
<br />
If your game goes in clockwise order in natural sitting position nothing really needed you just use standard API and you are good. However if position is complecated<br />
it may require some trickery.<br />
<br />
Usually turn order is done by "game state" (see state machine above). Basically it would be two choices:<br />
* Turn order depends on game situation (such as we take player with highest number of red cubes)<br />
* Turn order is custom and assign on previos step - i.e. we not playing in clockwise order anymore. In this case you either need to extend player table with new order info (cannot! use player_no column) or use natural order markers (i.e. marker_ff0000 on position_1). In this we can buid player array in right order and pick next player based on previous player using existing helper function as $this->createNextPlayerTable<br />
<br />
== Implement Notification handling ==<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 onclick js handler right to client animation, in real game its a two<br />
step operation. When user clicks on something, 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. See [[Game_interface_logic:_yourgamename.js#Notifications|JS Notifications]].<br />
<br />
Exception to this is client states, if you need to process two step user interaction such as select meeple, place meeple, you may want <br />
to avoid sending data to server until step is complete (which may involve direct client side animation). See [[BGA_Studio_Cookbook#Multi_Step_Interactions:_Select_Worker.2FPlace_Worker_-_Using_Selection|Multi-Step Interactions]]<br />
<br />
Part of the sending notifications would be to update player's scoring, BGA uses standard control for score (on JS side), see [[Game_interface_logic:_yourgamename.js#Update_players_score|Update Player's Score]].<br />
<br />
In BGA there is only two ways interact with the server (officially)<br />
* Initial data dump - when JS client starts it gets all current data via setup() method<br />
* Game actions - ajaxcall from client, it returns error or ok (not data), then server send butch of notifications to client<br />
<br />
Note current ajaxcall is super vebosy and prone to errors, I suggest to use helper function. It does a lot of stuff you must do anyways.<br />
<pre><br />
ajaxcallwrapper: function(action, args, handler) {<br />
if (!args) args = []; // this allows to skip args parameter for action which do not require them<br />
<br />
args.lock = true; // this allows to avoid rapid action clicking which can cause race condition on server<br />
<br />
if (this.checkAction(action)) { // this does all the proper check that player is active and action is declared<br />
this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", args, // this is mandatory fluff <br />
this, (result) => { }, // success result handler is empty - it is never needed<br />
handler); // this is real result handler - it called both on success and error, its is optional param - you rarely need it<br />
}<br />
},<br />
</pre><br />
<br />
When you insert a single action you have to update multiples files:<br />
* in ggg.js add ajaxcall, i.e. something like <br />
this.addActionButton('pass',_('Pass),()=>this.ajaxcallwrapper('pass'));<br />
* in states.php - add action 'pass' to list of possible actions<br />
'possibleactions' => ['pass','playCard']<br />
* in action.php - add action hander, see https://en.doc.boardgamearena.com/Players_actions:_yourgamename.action.php<br />
* in game.php - add action hander, there you must do the following<br />
** call checkAction to validate the action<br />
** possible do more game specific check to validate what player doing is legal (even its not possible from your js side - player can cheat - not allow that)<br />
** do some database maniplations, using access api<br />
** send notifications - this is the "reply" for action<br />
** transition to new state (it very rare that user will remain in the same state, except for multi-active states)<br />
* back to ggg.js add notification subsciption and notification handler (two separate things)<br />
<br />
== Wrap Up ==<br />
<br />
<br />
* Implement game progression (getGameProgression() in php)<br />
* Implement Zombie turn (zombieTurn() in php)<br />
* Define and implemented some meaningful statistics for your game (i.e. total points, point from source A, B, C...)<br />
* The games logs should explain what happened if player was not looking<br />
* You need to implemented tiebreaking (using aux score field) and updated tiebreaker description in meta-data<br />
* Make sure all UI strings are marked for translation<br />
* UI elements which are images (i.e. tokens, cards) should have tooltips<br />
<br />
<br />
When you think you game is completely working there is still bunch of stuff you have to do/check before telling admin that game is ready, please go though this [[Pre-release checklist]].<br />
<br />
Finally, visit the game page for your alpha game (https://boardgamearena.com/gamepanel?game=…) to add the following information if you can:<br />
* Links to the rules (in multiple languages if available).<br />
* Links to teaching videos.<br />
* In the "On the web" section, links to:<br />
** The official website for the game (if there is one).<br />
** The BoardGameGeek page for the game.<br />
* Consider writing a summary of the rules.</div>
Loenix34
http://en.doc.boardgamearena.com/index.php?title=Game_meta-information:_gameinfos.inc.php&diff=8859
Game meta-information: gameinfos.inc.php
2021-07-17T10:33:53Z
<p>Loenix34: /* Tie breaker description */</p>
<hr />
<div>{{Studio_Framework_Navigation}}<br />
<br />
== Overview ==<br />
<br />
From this file, you can edit the various meta-information of your game.<br />
<br />
Once you modified the file, don't forget to click on "Reload game informations" from the Control Panel in order in can be taken into account.<br />
<br />
Most information provided in this file are self-explainable.<br />
<br />
See sections below for specific cases.<br />
<br />
<br />
== Publisher/Designer fields ==<br />
<br />
These fields should match the publisher/designer for the game. In the case of a public domain name, they should be left empty (empty string '')<br />
<br />
== Beta ==<br />
<br />
You are not allowed to set the "'''is_beta'''" to 0 before the game has been released on BGA and stabilized.<br />
<br />
<br />
== Time Profiles ==<br />
'''fast/medium/slow_additional_time''': please set high values here: after the game has been released, we will lower these value to match the real game duration.<br />
<br />
<br />
== Number of players ==<br />
<br />
'''players'''<br />
'players' => array( 3, 4, 6 ),<br />
<br />
* during the first step of development of a game, it is recommended to have "1 player" configuration: much easy to start/stop a game this way, don't need to switch players.<br />
* if you change the minimum number of players from for example 1 to 2, make sure the new tables you create are not restricted from 1 to 1 player otherwise when you create a new table and this account setting is used, there will be a conflict with the new minimum number of players allowed and you will be blocked from creating the game.<br />
But you can also unblock yourself by changing player number again, launching a game with a larger number, and then getting back to the numbers you want.<br />
<br />
'''suggest_player_number''' / '''not_recommend_player_number'''<br />
don't specify anything here if there is no configuration that is REALLY better/worst than another one. You can check player's poll on BoardGameGeek game page if you have any doubt. '''Important exception:''' in the automatic lobby, if 'suggest_player_number' is not specified, the system will try first the lowest. So if the lowest player number is not compatible with the default options for your game (especially if there is a Solo mode that can only be played in training mode) you have to specify a suggest_player_number of your choice, so that players launching a game in the automatic lobby without checking the option don't get an error with the default configuration.<br />
<br />
<br />
== Colors ==<br />
<br />
'''player_colors'''<br />
'player_colors' => array( "ff0000", "008000", "0000ff", "ffa500", "ffffff" ),<br />
<br />
This array defines the default player colors, theoretically this can be bigger then maximum number of players but you have to support all of the in your game.<br />
Your setupNewGame in php is responsible for attributing these values to players. See section "Player color preferences" in [[Main_game_logic:_yourgamename.game.php]] for details.<br />
<br />
== Losers not ranked between themselves ==<br />
<br />
By default, all player are ranked, but in some games, the rules say that there is only one winner and that all the other players are losers and not ranked between themselves. You can set this option to true so that for your game, there is only one winner and losers, without a full ranking of all players.<br />
<br />
The score in this case should be 1 for the winner and 0 for the losers.<br />
<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' => false,<br />
<br />
== Disable player rotation in case of rematch ==<br />
<br />
By default, in case of a rematch players are rotated so that the first player changes. If for your game it's better to always have a random player order you can change this option.<br />
<br />
// When doing a rematch, the player order is swapped using a "rotation" so the starting player is not the same<br />
// If you want to disable this, set this to false<br />
'disable_player_order_swap_on_rematch' => false,<br />
<br />
== Custom "buy this game" button ==<br />
<br />
By default, the "buy this game" button is a link to an Amazon search with the name of the game.<br />
<br />
You can replace it by a different URL / game button label with :<br />
<br />
'custom_buy_button' => array(<br />
'url' => 'http://yoururl.com',<br />
'label' => 'Name of the website'<br />
),<br />
<br />
Note : button label will be "Buy on <Name of the website>"<br />
<br />
== Tags ==<br />
<br />
Any number of '''Tags''' can be attribute to your game. Tags are useful to place your game in the correct place and to give a good quick overview of what the game is about to players.<br />
<br />
Main game category (you MUST specify one tag AND ONLY ONE from this category):<br />
* 1: Abstract game<br />
* 2: Casual games<br />
* 3: For regular players<br />
* 4: For core gamers<br />
<br />
Other tags (you can specify any numbers of tags):<br />
* 10: Short game (<10 minutes)<br />
* 11: Medium length game (10 minutes to 30 minutes)<br />
* 12: Long game (>30mn)<br />
* 20: Awarded game (Win a prestigious award) (the game must have been at the '''first''' place of one the [http://boardgamegeek.com/wiki/page/Gaming_Industry_Awards# following major awards list]).<br />
* 22: Prototype (This game has not been published yet)<br />
* 23: Classic (This game is a classic from Public Domain)<br />
* 30: 2 players (2p game / best with 2 players)<br />
* 100: Fantasy theme<br />
* 101: Science Fiction them<br />
* 102: Historical theme<br />
* 103: Adventure<br />
* 104: Exploration<br />
* 105: Conquest<br />
* 106: Building<br />
* 200: Card game (Cards plays a central role in this game)<br />
* 201: Dice<br />
* 202: Solo game<br />
* 203: Worker placement<br />
* 204: Hand management<br />
* 205: Bluff<br />
* 206: Tile placement<br />
* 207: Combinations<br />
* 208: Majority<br />
* 209: Race<br />
* 210: Collection<br />
* 211: Cooperative game<br />
<br />
== Game Presentation ==<br />
<br />
Short game presentation text that will appear on the game description page, structured as an array of paragraphs.<br />
<br />
Each paragraph must be wrapped with totranslate() for translation and should not contain html (plain text without formatting). A good length for this text is between 100 and 150 words (about 6 to 9 lines on a standard display)<br />
<br />
Example:<br />
'presentation' => array(<br />
totranslate("This wonderful game is about geometric shapes!"),<br />
totranslate("It was awarded best triangle game of the year in 2005 and nominated for the Spiel des Jahres."),<br />
...<br />
),<br />
<br />
== Tie breaker description ==<br />
Describe tie breaker score calculation (player_score_aux in player table).<br />
<br />
Important: This is used in the javascript too, which is automatically generated. Using newlines in here will cause errors in the javascript, which will cause the game to not load.<br />
<br />
Example:<br />
'tie_breaker_description' => totranslate("Number of remaining cards in hand"),<br />
<br />
== Language dependency ==<br />
<br />
If you have a game that is language dependent, you can use the option described here: [[Main_game_logic:_yourgamename.game.php#Language_dependent_games_API]]<br />
<br />
== Coop Elo Mode ==<br />
<br />
For cooperative games, by default players will earn as many Elo points as the score. <br />
<br />
For games where there is no variable difficulty (such as for example Bandido) we keep that setting, but for all coop games where we have options to change the difficulty level and/or where the score reached indicates a higher level of skill and/or where the number of players has a significant impact on the difficulty, we have to set up a reference scale for Elo points (between 1300 and 2500). Players regularly winning with a specific difficulty setting will have their Elo nearing this value asymptotically over time.<br />
<br />
For this, you have to set up the coop_elo_mode parameter.<br />
<br />
Here is an example using options and win/loss (score 1/0):<br />
<br />
'coop_elo_mode' => [<br />
'type' => 'points_references',<br />
'references' => [<br />
// Difficulty 1<br />
['players_nbr' => 2, 'options' => [100 => 1, 101 => 0], 'elo' => [0 => 1000, 1 => 1400]],<br />
['players_nbr' => 2, 'options' => [100 => 1, 101 => 1], 'elo' => [0 => 1000, 1 => 1470]],<br />
['players_nbr' => 3, 'options' => [100 => 1, 101 => 0], 'elo' => [0 => 1000, 1 => 1350]],<br />
['players_nbr' => 3, 'options' => [100 => 1, 101 => 1], 'elo' => [0 => 1000, 1 => 1400]],<br />
['players_nbr' => 4, 'options' => [100 => 1, 101 => 0], 'elo' => [0 => 1000, 1 => 1400]],<br />
['players_nbr' => 4, 'options' => [100 => 1, 101 => 1], 'elo' => [0 => 1000, 1 => 1470]],<br />
// Difficulty 2<br />
['players_nbr' => 2, 'options' => [100 => 2, 101 => 0], 'elo' => [0 => 1000, 1 => 1540]],<br />
['players_nbr' => 2, 'options' => [100 => 2, 101 => 1], 'elo' => [0 => 1000, 1 => 1690]],<br />
['players_nbr' => 3, 'options' => [100 => 2, 101 => 0], 'elo' => [0 => 1000, 1 => 1450]],<br />
['players_nbr' => 3, 'options' => [100 => 2, 101 => 1], 'elo' => [0 => 1000, 1 => 1540]],<br />
['players_nbr' => 4, 'options' => [100 => 2, 101 => 0], 'elo' => [0 => 1000, 1 => 1540]],<br />
['players_nbr' => 4, 'options' => [100 => 2, 101 => 1], 'elo' => [0 => 1000, 1 => 1690]],<br />
// Difficulty 3<br />
['players_nbr' => 2, 'options' => [100 => 3, 101 => 0], 'elo' => [0 => 1000, 1 => 1690]],<br />
['players_nbr' => 2, 'options' => [100 => 3, 101 => 1], 'elo' => [0 => 1000, 1 => 1910]],<br />
['players_nbr' => 3, 'options' => [100 => 3, 101 => 0], 'elo' => [0 => 1000, 1 => 1540]],<br />
['players_nbr' => 3, 'options' => [100 => 3, 101 => 1], 'elo' => [0 => 1000, 1 => 1690]],<br />
['players_nbr' => 4, 'options' => [100 => 3, 101 => 0], 'elo' => [0 => 1000, 1 => 1690]],<br />
['players_nbr' => 4, 'options' => [100 => 3, 101 => 1], 'elo' => [0 => 1000, 1 => 1910]],<br />
// Difficulty 4<br />
['players_nbr' => 2, 'options' => [100 => 4, 101 => 0], 'elo' => [0 => 1000, 1 => 1830]],<br />
['players_nbr' => 2, 'options' => [100 => 4, 101 => 1], 'elo' => [0 => 1000, 1 => 2120]],<br />
['players_nbr' => 3, 'options' => [100 => 4, 101 => 0], 'elo' => [0 => 1000, 1 => 1640]],<br />
['players_nbr' => 3, 'options' => [100 => 4, 101 => 1], 'elo' => [0 => 1000, 1 => 1830]],<br />
['players_nbr' => 4, 'options' => [100 => 4, 101 => 0], 'elo' => [0 => 1000, 1 => 1830]],<br />
['players_nbr' => 4, 'options' => [100 => 4, 101 => 1], 'elo' => [0 => 1000, 1 => 2120]],<br />
// Difficulty 5<br />
['players_nbr' => 2, 'options' => [100 => 5, 101 => 0], 'elo' => [0 => 1000, 1 => 1980]],<br />
['players_nbr' => 2, 'options' => [100 => 5, 101 => 1], 'elo' => [0 => 1000, 1 => 2340]],<br />
['players_nbr' => 3, 'options' => [100 => 5, 101 => 0], 'elo' => [0 => 1000, 1 => 1740]],<br />
['players_nbr' => 3, 'options' => [100 => 5, 101 => 1], 'elo' => [0 => 1000, 1 => 1980]],<br />
['players_nbr' => 4, 'options' => [100 => 5, 101 => 0], 'elo' => [0 => 1000, 1 => 1980]],<br />
['players_nbr' => 4, 'options' => [100 => 5, 101 => 1], 'elo' => [0 => 1000, 1 => 2340]],<br />
],<br />
],<br />
<br />
Here is an example using different scoring values to indicate the level of difficulty mastered by the players winning the game:<br />
<br />
'coop_elo_mode' => array(<br />
'type' => 'points_references',<br />
'references' => array(<br />
array(<br />
'players_nbr' => 2,<br />
//'options' => array( ),<br />
'elo' => array(<br />
0 => 1000,<br />
1 => 1350,<br />
2 => 1425,<br />
3 => 1500,<br />
4 => 1576,<br />
5 => 1651,<br />
6 => 1727,<br />
7 => 1802,<br />
8 => 1878,<br />
9 => 1953,<br />
10 => 2029,<br />
11 => 2104,<br />
12 => 2180<br />
)<br />
),<br />
array(<br />
'players_nbr' => 3,<br />
//'options' => array( ),<br />
'elo' => array(<br />
0 => 1000,<br />
1 => 1400,<br />
2 => 1470,<br />
3 => 1541,<br />
4 => 1612,<br />
5 => 1683,<br />
6 => 1754,<br />
7 => 1825,<br />
8 => 1895,<br />
9 => 1966,<br />
10 => 2018,<br />
11 => 2095,<br />
12 => 2250<br />
)<br />
),<br />
array(<br />
'players_nbr' => 4,<br />
//'options' => array( ),<br />
'elo' => array(<br />
0 => 1000,<br />
1 => 1430,<br />
2 => 1505,<br />
3 => 1580,<br />
4 => 1655,<br />
5 => 1730,<br />
6 => 1805,<br />
7 => 1880,<br />
8 => 1955,<br />
9 => 2030,<br />
10 => 2105,<br />
11 => 2180,<br />
12 => 2330<br />
)<br />
),<br />
array(<br />
'players_nbr' => 5,<br />
// 'options' => array( ),<br />
'elo' => array(<br />
0 => 1000,<br />
1 => 1480,<br />
2 => 1558,<br />
3 => 1636,<br />
4 => 1714,<br />
5 => 1792,<br />
6 => 1870,<br />
7 => 1949,<br />
8 => 2027,<br />
9 => 2105,<br />
10 => 2183,<br />
11 => 2261,<br />
12 => 2340<br />
)<br />
)<br />
)<br />
),</div>
Loenix34