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

Practical debugging: Difference between revisions

From Board Game Arena
Jump to navigation Jump to search
 
(38 intermediate revisions by 9 users not shown)
Line 1: Line 1:
{{Studio_Framework_Navigation}}
This page gives you practical tips to debug your game during development. Don't hesitate to share your difficulties with us so that we can improve this section.


This page gives you practical tips to debug your game during development. Don't hesitate to share your difficulties with us so that we can improve this section.
__TOC__


== Tools ==
== Tools ==
Line 36: Line 36:
* There is a SQL error in your dbmodel.sql file.
* There is a SQL error in your dbmodel.sql file.
* You have a syntax error in your PHP file.
* You have a syntax error in your PHP file.
* Your PHP "setup" - or any method used during the game initial states - generates an exception.
* Your PHP "setupNewGame" generates exception
* You transition to new game state from the initial state which generates exception (or state is misconfigured)
* You transition to player state from the initial state which has excpetion in arg* or action* method (or state is misconfigured)
 


If the error is not explicitly displayed when you click on "Express start", you should check the "Gameserver error log" as per [[Studio logs]].
If the error is not explicitly displayed when you click on "Express start", you should check the "Gameserver error log" as per [[Studio logs]].
However errors and logs generated by setupNewGame not always (almost never) there, so see Debug setupNewGame session.
More cases of why game can't start are described on the [[Troubleshooting]] page.
More cases of why game can't start are described on the [[Troubleshooting]] page.


Line 59: Line 63:
You can use the following functions in your game to add server side logging:
You can use the following functions in your game to add server side logging:


'''self::dump( 'name_of_variable', $variable );'''  // dump variable, like var_dump but in the log debug level logging, goes to [[Studio_logs|BGA request&SQL logs]]
'''$this->dump( 'name_of_variable', $variable );'''  // dump variable, like var_dump but in the log debug level logging, goes to [[Studio_logs|BGA request&SQL logs]]


'''self::debug( $message );'''  // debug level logging, goes to [[Studio_logs|BGA request&SQL logs]]
'''$this->debug( $message );'''  // debug level logging, goes to [[Studio_logs|BGA request&SQL logs]]


'''self::trace( $message );'''  // info level logging, goes to [[Studio_logs|BGA request&SQL logs]]
'''$this->trace( $message );'''  // info level logging, goes to [[Studio_logs|BGA request&SQL logs]]


'''self::warn( $message );'''  // warning level logging, goes to [[Studio_logs#BGA_unexpected_exceptions_logs|BGA unexpected exceptions log]]
'''$this->warn( $message );'''  // warning level logging, goes to [[Studio_logs#BGA_unexpected_exceptions_logs|BGA unexpected exceptions log]]


'''self::error( $message );'''  // error level logging, goes to [[Studio_logs#BGA_unexpected_exceptions_logs|BGA unexpected exceptions log]]
'''$this->error( $message );'''  // error level logging, goes to [[Studio_logs#BGA_unexpected_exceptions_logs|BGA unexpected exceptions log]]


Check [[Studio logs]] for more details on how to access your logs.
Check [[Studio logs]] for more details on how to access your logs.
Line 76: Line 80:
Other levels will show only in the development environment and can be used as you see fit.
Other levels will show only in the development environment and can be used as you see fit.


'''WARNING''': tracing does not work in constructor, setupNewGame and game states immediately following starting state (not sure why). Use other method below in this case.
'''WARNING''': tracing does not work in constructor, setupNewGame and game states immediately following starting state (not sure why). Use other method described below in this case (seciton "Debugging setupNewGame").
 
To access logs on production, use button "Display recent errors from production (unexpected errors)" on control panel of your game


=== Call php functions from chat ===
=== Call php functions from chat ===
Line 85: Line 91:


=== Dump data and die ===
=== Dump data and die ===
First, make sure you can reproduce the needed game situation with one click.  Use Save ("[[Tools_and_tips_of_BGA_Studio#Save_.26_restore_state|save & restore]]") function before doing some "test actions".


Most of the time, debugging PHP is quite easy. Here's what I do when I want to develop/debug some game logic that is triggered by some game action:
If you want to see what data looks like on some path you can simply throw this in exception, i.e.
 
* At first, I make sure that I can reproduce the needed game situation with one click. To do this, I use the "[[Tools_and_tips_of_BGA_Studio#Save_.26_restore_state|save & restore]]" function.
* Another possibility for this is to place a '''die('ok');''' PHP statement right after the PHP I am developing/debugging. This way, I make sure that every request will fail and then nothing will be committed to the database.
* Then, I use the '''var_dump''' function to dump PHP variables and check what's wrong, until it works.
 
Example:
<pre>
<pre>
$value = var_export($my_var, true);
throw new BgaUserException('I am here: '.$value);
</pre>


// (...my code to debug)
Alternately use json instead of php formaing:
 
    $value = json_encode($my_var, JSON_PRETTY_PRINT);
var_dump( $my_variable );
die('ok');


// (...my code to debug)


</pre>


Now since this text will be returned back to client it will usually choke on this, but you can actually see it better if you wrap it some html, i.e.
Another possibility for this is to place a '''die('ok')''' and '''var_dump(...)''' functions
<pre>
<pre>
  echo "<pre>";
  echo "<pre>";
  var_dump( $my_variable );
  var_dump( $my_variable );
  echo "&lt;/pre>";
  echo "&lt;/pre>";
die('ok');
</pre>
</pre>
Note: since this text will be returned back to client it will usually choke on this, but you can actually see it better if you wrap it some html (hence the &lt;pre> tag)


=== Debug at home ===
=== Debug at home ===
Line 123: Line 127:
Recommended way to debug it  
Recommended way to debug it  
* leave  setupNewGame exactly as in template (inlcuding setting up player table and activating first player), and add a single call at the end
* leave  setupNewGame exactly as in template (inlcuding setting up player table and activating first player), and add a single call at the end
   $this->initTables();
   $this->initMyTables();
This will be your own function which you will be changing, so you don't touch setupNewGame anymore.
This will be your own function which you will be changing, so you don't touch setupNewGame anymore.


Remember that this function cannot send notification or query active or current player. If you need to get player data use $this->loadPlayersBasicInfos().
Remember that this function cannot send notification or query active or current player. If you need to get player data use $this->loadPlayersBasicInfos().


Create a code for this function, and if it does not work run it from the chat window of the just created game (i.e. 'initTables()' without quotes). That way you can see debug output!
    function initMyTables() {
        try {
          $players = $this->loadPlayersBasicInfos();
          // ... code the function
        } catch ( Exception $e ) {
            // logging does not actually work in game init :(
            // but if you calling from php chat it will work
            $this->error("Fatal error while creating game");
            $this->dump('err', $e);
        }
    }
 
Create a code for this function, and if it does not work run it from the chat window of the just created game (i.e. 'initMyTables()' without quotes). That way you can see debug output!
The only trick is you have to clear all your tables in the begging so when you run it multiple times your db does not puke.
The only trick is you have to clear all your tables in the begging so when you run it multiple times your db does not puke.
If your game does not start at all - do not even call it from setupNewGame, comment it out and only call from chat window until it works.
If your game does not start at all - do not even call it from setupNewGame, comment it out and only call from chat window until it works.
Line 135: Line 151:
The same problem exists if you game state immediately follow starting state (problem of debugging it without the logs), in this case create a dummy player state in which player just press OK that will transition
The same problem exists if you game state immediately follow starting state (problem of debugging it without the logs), in this case create a dummy player state in which player just press OK that will transition
to you game state, after you done debugging it remove the state and shortcut the transition to your game state.
to you game state, after you done debugging it remove the state and shortcut the transition to your game state.
Instead of calling initMyTables directly you can call helper function instead from chat window which will also reset the client, i.e.
<pre>
    function debug_initMyTables() {
        $this->deleteAllTables(); // this suppose to delete/reset all data - you have to implement this function
        $this->initMyTables();
        $newGameDatas = $this->getAllTableDatas(); // this is framework function
        $this->notifyPlayer($this->getActivePlayerId(), 'resetInterfaceWithAllDatas', '', $newGameDatas); // this is notification to reset all data
        $this->notifyAllPlayers("message",'setup called',[]);
    }
</pre>HINT: This is a great suggestion. If you make any simple mistake in any code in setupNewGame, you may see "BGA Service Exception" instead of anything very useful, since setupNewGame failed. For example, even sending a bad SQL string to $this->dbQuery() will generate this error. Very hard to debug!
=== Debugging endOfGame ===
Debugging end of game is hard if game ends! Make sure it does not end, then you can restore to previous state using saved states.
In php file:
    $this->processEndOfGame();
    if ($this->getBgaEnvironment() == 'studio')
      $this->gamestate->nextState('debuglast'); // debug end
    else
      $this->gamestate->nextState('last'); // real end
You have to add a special user state in states.inc.php file (note: this snippet uses php constants instead of state numbers)
<pre>
        STATE_GAME_TURN_NEXT_PLAYER => array( // normal state
                "name" => "gameTurnNextPlayer",
                "description" => '',
                "type" => "game",
                "action" => "st_gameTurnNextPlayer",
                "updateGameProgression" => true,
                "transitions" => array(
                        "next" => STATE_PLAYER_TURN_ACTION,
                        "loopback" =>  STATE_GAME_TURN_NEXT_PLAYER,
                        "last" => STATE_END_GAME,
                        "debuglast" => STATE_PLAYER_GAME_END // for debugging on studio
                        ),
        ),
        // for debugging end of game
        STATE_PLAYER_GAME_END => array(
                "name" => "playerGameEnd",
                "description" => clienttranslate('Game Over'),
                "descriptionmyturn" => clienttranslate('Game Over'),
                "type" => "activeplayer",
                "args"=>"arg_playerTurnAction",
                "possibleactions" => array( "end"),
                "transitions" =>
                array(
                        "next" => STATE_END_GAME,
                        "loopback"=> STATE_PLAYER_GAME_END)
        ),
</pre>
=== Writing custom debug functions ===
Sometimes you want to set up a situation to debug a special case, and you need tu update the game situation to fit your test. To do this quickly without needing to play a full game and hope to get the card you want to test, you can write custom debug functions.
For example, if I need to change the type of a card to your need, you can write a function like this :
<pre>
    function debug_setCardType(int $id, int $type, int $typeArg = 0) {
      $this->DbQuery("UPDATE card SET card_type = $type, card_type_arg = $typeArg WHERE card_id = $id" );
    }
</pre>Then you can call this function by writing in the chat (on studio only) :<pre>
debug_setCardType(46,2,3)
</pre>
If you want this debug function to be quickly accessible, you need to prefix it with '''debug_''' to make it show in the debug menu of the Studio :
[[File:Custom-debug-function-example.png]]
BGA will detect methods with this prefix and the associated parameters.
Functions with parameters will show with "..." in the debug menu and will show a popin to set the parameters. Functions without parameters will be called directly. The result in the end is the same as calling the function from the chat.


== Debugging my HTML/CSS layout ==
== Debugging my HTML/CSS layout ==
Line 161: Line 249:
* You can even modify directly some CSS properties and see how it looks immediately in the game interface.
* You can even modify directly some CSS properties and see how it looks immediately in the game interface.


When changing css you have to force reload (Ctrl+R). But it also reloads all images which is long, if you want it faster you can define this function in js file, and call it from Browser js console
When changing css you have to force reload (Ctrl+R). But it also reloads all images which is long, if you want it faster you can click the Reload CSS button.
<pre>
function reloadCss() {
var links = document.getElementsByTagName("link");
for (var cl in links) {
var link = links[cl];
if (link.rel === "stylesheet")
link.href += "?";
}
}
</pre>
=== Debugging Toolips CSS/Layout ===
=== Debugging Toolips CSS/Layout ===


Line 188: Line 266:


Modern browsers also allow you to put breakpoints in your js code. It is in source tab, but its a bit difficult to find (your code that is), see picture below on how to insert breakpoints.
Modern browsers also allow you to put breakpoints in your js code. It is in source tab, but its a bit difficult to find (your code that is), see picture below on how to insert breakpoints.
You can do this by pressing Ctrl + B when your cursor is on the line where you want to set the breakpoint, or by clicking just to the left of the line number. One thing to be aware of is that refreshing via F5 removes set breakpoints, due to some cache busting values added to the URL. This can be remedied by adding this line to the end of your javascript source file, which enables the proper source mapping:
<pre>//# sourceURL=myjsfile.js</pre>


[[File:Bga-debug.jpg]]
[[File:Bga-debug.jpg]]
Line 241: Line 323:
When a player creates a bug report in production, a snapshot of the game database is taken. You can get access to this snapshot from the studio by following the steps below:
When a player creates a bug report in production, a snapshot of the game database is taken. You can get access to this snapshot from the studio by following the steps below:
* Create a table in the studio with the same game and number of players as the table for which the report has been written. Launch this table.
* Create a table in the studio with the same game and number of players as the table for which the report has been written. Launch this table.
* Open another tab on the studio and go to "Manage game" page for your project (you have to be admin for this project)
* Load the bug dump by entering the bug report ID this debug section
* In the "Errors in production" section, fill up the fields "Bug report ID" (this is the ID of the bug report in production) and "Studio table ID" (this is the ID of the table you created above) then click the "⇨ Load bug report state into this table save slot #1" button.
[[File:LoadBugReportInput.png]]
* If the snapshot is correctly retrieved, you see a "Done!" message.
 
* Go back to the tab with your studio table and click "Load 1".
=== Customizing the player id replacement ===
* The page refreshes automatically and is broken. This is normal, as the player ids from the snapshot are the player ids of the production, not those of the studio. We'll need to update them.
By default, the tool will replace any production player ids in the dump by the studio player ids of the created table.
** '''Important note:''' if you see a "Done!" message but clicking "Load 1" doesn't bring any change, it's that the snapshot is unfortunately not available (most likely because the bug report was declared too long after the game ended and the database had already been garbage collected to reclaim space).
 
* Click on the "Go to game database" button
In case you want more control, setup a `loadBugReportSQL` method in your game php file (in the DebugTrait if you have one).
* For each table using player_ids, you'll need to update the player_ids from the production to use the player_ids from the studio. You can see the player_ids from the table page before entering the game by hovering over the player names.
If a function with this name is detected on your game, the framework will not replace the ids, and it's up to you to change them.
* Tables to update:
** player
** global (value with ID 2 is the active player)
** stats
** tables specific to your schema that use player_ids
* If your changes to player_ids are not taken into account, it may be a cache problem: use the "Clear PHP cache" button on your "Manage game" page.
* Then you should be able to play with the same state of the game as when the report was created in production.
* If the game has ended, you can place it again in the game state you want to debug by setting the value with ID 1 in the global table to the appropriate state value, and the value with ID 2 to the player you want active).
*
* Below is an example php function you may want to make. You can call this function from the chat window: LoadDebug() 
* change instances of 2308257, and 2308258 to you own BGA Studio logins YourLogin0 and YourLogin1
* change $id0 and $id1 to the player_ids from the table you want to debug, and have recently imported.
* Before you load Slot1, open a second tab with the table, because after loading the slot, that tab will be unusable. In the second tab you can call LoadDebug() in the chat window


Example of `loadBugReportSQL` function:
<pre>
<pre>
public function LoadDebug()
    public function loadBugReportSQL(int $reportId, array $studioPlayers): void
{
    {
// These are the id's from the BGAtable I need to debug.
        $prodPlayers = $this->getObjectListFromDb("SELECT `player_id` FROM `player`", true);
$ids = [
        $prodCount = count($prodPlayers);
86107517,
        $studioCount = count($studioPlayers);
86872172,
        if ($prodCount != $studioCount) {
85086097,
            throw new BgaVisibleSystemException("Incorrect player count (bug report has $prodCount players, studio table has $studioCount players)");
84380410,
        }
11000525
];


// Id of the first player in BGA Studio
        // SQL specific to your game
$sid = 2296755;
        // For example, reset the current state if it's already game over
        $sql = [
foreach ($ids as $id) {
            "UPDATE `global` SET `global_value` = 10 WHERE `global_id` = 1 AND `global_value` = 99"
// basic tables
        ];
self::DbQuery("UPDATE player SET player_id=$sid WHERE player_id = $id" );
        foreach ($prodPlayers as $index => $prodId) {
self::DbQuery("UPDATE global SET global_value=$sid WHERE global_value = $id" );
            $studioId = $studioPlayers[$index];
self::DbQuery("UPDATE stats SET stats_player_id=$sid WHERE stats_player_id = $id" );
            // SQL common to all games
            $sql[] = "UPDATE `player` SET `player_id` = $studioId WHERE `player_id` = $prodId";
            $sql[] = "UPDATE `global` SET `global_value` = $studioId WHERE `global_value` = $prodId";
            $sql[] = "UPDATE `stats` SET `stats_player_id` = $studioId WHERE `stats_player_id` = $prodId";
            $sql[] = "UPDATE `bga_globals` set `name` = REPLACE(`name`, '$prodId', '$studioId'), `value` = REPLACE(`value`, '$prodId', '$studioId')";


// 'other' game specific tables. example:
            // SQL specific to your game
// tables specific to your schema that use player_ids
            $sql[] = "UPDATE `card` SET `card_location_arg` = $studioId WHERE `card_location_arg` = $prodId";
self::DbQuery("UPDATE card SET card_location_arg=$sid WHERE card_location_arg = $id" );
            $sql[] = "UPDATE `my_table` SET `my_column` = REPLACE(`my_column`, '$prodId', '$studioId')";
        }
++$sid;
        foreach ($sql as $q) {
}
            $this->DbQuery($q);
}
        }
        $this->reloadPlayersBasicInfos();
    }
</pre>
</pre>
If you used quietmint's `loadBugSQL` function, there are slight changes: the method name changed, and the studio players ids are plassed as method's second param, instead of computing them.
All other parts added in PHP/JS files for loadBug method can be removed.
''Note: If you get a "Not logged" error showing, you probably left a getCurrentPlayerId call in the `loadBugReportSQL` method.''
== Studio environment ==
Some developers have requested a method allowing to know which environment the module is running on. While in general, your code should be the same on all environments so that the validation process makes sense, this may allow for example to display some helpers for testing during development while being sure that they'll never show up outside of the studio.
From your <gamename>.game.php file you can use:
<pre>if ($this->getBgaEnvironment() == 'studio') { ... }</pre>


=== Fully automated version ===
Or if needed from your <gamename>.view.php file:
Thanks to quietmint for [https://boardgamearena.com/forum/viewtopic.php?f=12&t=16454#p63167 coming up] with that!
 
<pre>if ($this->game->getBgaEnvironment() == 'studio') { ... }</pre>


; In ggg.php
<pre>
  /*
  * loadBug: in studio, type loadBug(20762) into the table chat to load a bug report from production
  * client side JavaScript will fetch each URL below in sequence, then refresh the page
  */
  public function loadBug($reportId)
  {
    $db = explode('_', self::getUniqueValueFromDB("SELECT SUBSTRING_INDEX(DATABASE(), '_', -2)"));
    $game = $db[0];
    $tableId = $db[1];
    self::notifyAllPlayers('loadBug', "Trying to load <a href='https://boardgamearena.com/bug?id=$reportId' target='_blank'>bug report $reportId</a>", [
      'urls' => [
        // Emulates "load bug report" in control panel
        "https://studio.boardgamearena.com/admin/studio/getSavedGameStateFromProduction.html?game=$game&report_id=$reportId&table_id=$tableId",
       
        // Emulates "load 1" at this table
        "https://studio.boardgamearena.com/table/table/loadSaveState.html?table=$tableId&state=1",
       
        // Calls the function below to update SQL
        "https://studio.boardgamearena.com/1/$game/$game/loadBugSQL.html?table=$tableId&report_id=$reportId",
       
        // Emulates "clear PHP cache" in control panel
        // Needed at the end because BGA is caching player info
        "https://studio.boardgamearena.com/admin/studio/clearGameserverPhpCache.html?game=$game",
      ]
    ]);
  }
 
  /*
  * loadBugSQL: in studio, this is one of the URLs triggered by loadBug() above
  */
  public function loadBugSQL($reportId)
  {
    $studioPlayer = self::getCurrentPlayerId();
    $players = self::getObjectListFromDb("SELECT player_id FROM player", true);
 
    // Change for your game
    // We are setting the current state to match the start of a player's turn if it's already game over
    $sql = [
      "UPDATE global SET global_value=2 WHERE global_id=1 AND global_value=99"
    ];
    foreach ($players as $pId) {
      // All games can keep this SQL
      $sql[] = "UPDATE player SET player_id=$studioPlayer WHERE player_id=$pId";
      $sql[] = "UPDATE global SET global_value=$studioPlayer WHERE global_value=$pId";
      $sql[] = "UPDATE stats SET stats_player_id=$studioPlayer WHERE stats_player_id=$pId";
 
      // Add game-specific SQL update the tables for your game
      $sql[] = "UPDATE card SET card_location_arg=$studioPlayer WHERE card_location_arg=$pId";
      $sql[] = "UPDATE piece SET player_id=$studioPlayer WHERE player_id=$pId";
      $sql[] = "UPDATE log SET player_id=$studioPlayer WHERE player_id=$pId";
      $sql[] = "UPDATE log SET action_arg=REPLACE(action_arg, $pId, $studioPlayer)";
 
      // This could be improved, it assumes you had sequential studio accounts before loading
      // e.g., quietmint0, quietmint1, quietmint2, etc. are at the table
      $studioPlayer++;
    }
    $msg = <nowiki>"<b>Loaded <a href='https://boardgamearena.com/bug?id=$reportId' target='_blank'>bug report $reportId</a></b><hr><ul><li>" . implode(';</li><li>', $sql) . ';</li></ul>';</nowiki>
    self::warn($msg);
    self::notifyAllPlayers('message', $msg, []);
 
    foreach ($sql as $q) {
      self::DbQuery($q);
    }
    self::reloadPlayersBasicInfos();
  }
</pre>


; In ggg.action.php
<pre>
  public function loadBugSQL() {
    self::setAjaxMode();
    $reportId = (int) self::getArg('report_id', AT_int, true);
    $this->game->loadBugSQL($reportId);
    self::ajaxResponse();
  }
</pre>


; In ggg.js
[[Category:Studio]]
<pre>
  // Load production bug report handler
  dojo.subscribe("loadBug", this, function loadBug(n) {
    function fetchNextUrl() {
      var url = n.args.urls.shift();
      console.log("Fetching URL", url);
      dojo.xhrGet({
        url: url,
        load: function (success) {
          console.log("Success for URL", url, success);
          if (n.args.urls.length > 0) {
            fetchNextUrl();
          } else {
            console.log("Done, reloading page");
            window.location.reload();
          }
        },
      });
    }
    console.log("Notif: load bug", n.args);
    fetchNextUrl();
  });
</pre>

Latest revision as of 16:10, 11 September 2024

This page gives you practical tips to debug your game during development. Don't hesitate to share your difficulties with us so that we can improve this section.

Tools

To work on BGA Studio, we recommend that you use Google Chrome as it's currently the fastest browser for the BGA platform, and it's available for all OSes.

Another reason to use Chrome is that it embeds all the tools you need to work on BGA Studio. You can see them by pressing "F12" or from the menu ("Tools > Development tools").

A good practice is to use a second browser to develop the game, in order to verify that your game is working fine on this browser too.

To debug with Firefox browser, we advise you to use these 2 extensions:

To debug with other browsers (IE, Edge, Opera), we advise you to use one of the most recent versions. Latest versions of the browser will likely have better development tools than the previous ones...

General tip for debugging

Save & Restore

In general for debugging, think of using the 'save & restore state' functionality. It enables you to save the state of your game just before the issue you are investigating, then come back to that point with one click as many times as needed to understand what is going wrong.

You can save up to 3 different states.

Refresh

If something does not seems to work properly even you "fixed it", it may be refresh issue. Not all files born equal. Some require a lot more steps to get into the game.

See https://en.doc.boardgamearena.com/Studio_file_reference for information on how to "refresh" each files.

Debugging my game when it cannot start

If your game won't start because of an error, you are probably in one of these situations:

  • There is a SQL error in your dbmodel.sql file.
  • You have a syntax error in your PHP file.
  • Your PHP "setupNewGame" generates exception
  • You transition to new game state from the initial state which generates exception (or state is misconfigured)
  • You transition to player state from the initial state which has excpetion in arg* or action* method (or state is misconfigured)


If the error is not explicitly displayed when you click on "Express start", you should check the "Gameserver error log" as per Studio logs. However errors and logs generated by setupNewGame not always (almost never) there, so see Debug setupNewGame session. More cases of why game can't start are described on the Troubleshooting page.

Debugging an issue with the waiting screen

BGA displays a waiting screen during a few seconds inviting free players to become premium when they are playing a lot.

This waiting screen should not in principle impact your game, but in some cases it might.

To test the waiting screen on the studio, you can set the desired duration by adding the get parameter studioLockingDuration with the number of seconds for the lock screen to be displayed. For example to have a 10 seconds waiting screen when you launch a game on the studio: https://studio.boardgamearena.com?studioLockingDuration=10

Then you can just set it back to zero in the same way with https://studio.boardgamearena.com?studioLockingDuration=0 (or restart your browser to clear the session value).

Debugging my PHP game logic (or my view)

Add traces to your code

You can use the following functions in your game to add server side logging:

$this->dump( 'name_of_variable', $variable ); // dump variable, like var_dump but in the log debug level logging, goes to BGA request&SQL logs

$this->debug( $message ); // debug level logging, goes to BGA request&SQL logs

$this->trace( $message ); // info level logging, goes to BGA request&SQL logs

$this->warn( $message ); // warning level logging, goes to BGA unexpected exceptions log

$this->error( $message ); // error level logging, goes to BGA unexpected exceptions log

Check Studio logs for more details on how to access your logs.

This can be useful when you need to follow the flow of your code and not just stop it to see how it goes at some point.

Only the error log level will appear in production. This level should be used only for critical problems. Other levels will show only in the development environment and can be used as you see fit.

WARNING: tracing does not work in constructor, setupNewGame and game states immediately following starting state (not sure why). Use other method described below in this case (seciton "Debugging setupNewGame").

To access logs on production, use button "Display recent errors from production (unexpected errors)" on control panel of your game

Call php functions from chat

This is best feature ever, you can call individual function in your game.php from chat window, see details at https://en.doc.boardgamearena.com/Tools_and_tips_of_BGA_Studio#Run_PHP_functions_from_the_chat


Dump data and die

First, make sure you can reproduce the needed game situation with one click. Use Save ("save & restore") function before doing some "test actions".

If you want to see what data looks like on some path you can simply throw this in exception, i.e.

$value = var_export($my_var, true);
throw new BgaUserException('I am here: '.$value);

Alternately use json instead of php formaing:

   $value = json_encode($my_var, JSON_PRETTY_PRINT);


Another possibility for this is to place a die('ok') and var_dump(...) functions

 echo "<pre>";
 var_dump( $my_variable );
 echo "</pre>";
 die('ok');


Note: since this text will be returned back to client it will usually choke on this, but you can actually see it better if you wrap it some html (hence the <pre> tag)

Debug at home

If you develop some complex logic it is better sometimes to stub all external calls and debug it locally using command line or IDE debugger for php. You can get some basic stubs here https://github.com/elaskavaia/bga-sharedcode/raw/master/misc/module/table/table.game.php

To run php locally you need to wrap it into test or something, see example https://en.doc.boardgamearena.com/BGA_Studio_Cookbook#Creating_a_test_class_to_run_PHP_locally

Debugging setupNewGame

This is separate section because tracing method do not work in this function. Recommended way to debug it

  • leave setupNewGame exactly as in template (inlcuding setting up player table and activating first player), and add a single call at the end
 $this->initMyTables();

This will be your own function which you will be changing, so you don't touch setupNewGame anymore.

Remember that this function cannot send notification or query active or current player. If you need to get player data use $this->loadPlayersBasicInfos().

   function initMyTables() {
       try {
          $players = $this->loadPlayersBasicInfos();
         // ... code the function
       } catch ( Exception $e ) {
           // logging does not actually work in game init :(
           // but if you calling from php chat it will work
           $this->error("Fatal error while creating game");
           $this->dump('err', $e);
       }
   }

Create a code for this function, and if it does not work run it from the chat window of the just created game (i.e. 'initMyTables()' without quotes). That way you can see debug output! The only trick is you have to clear all your tables in the begging so when you run it multiple times your db does not puke. If your game does not start at all - do not even call it from setupNewGame, comment it out and only call from chat window until it works. Don't forget you can also look at tables in db using php-admin tool (link at the bottom of the game table in studio).

The same problem exists if you game state immediately follow starting state (problem of debugging it without the logs), in this case create a dummy player state in which player just press OK that will transition to you game state, after you done debugging it remove the state and shortcut the transition to your game state.

Instead of calling initMyTables directly you can call helper function instead from chat window which will also reset the client, i.e.

    function debug_initMyTables() {
        $this->deleteAllTables(); // this suppose to delete/reset all data - you have to implement this function
        $this->initMyTables();
        $newGameDatas = $this->getAllTableDatas(); // this is framework function
        $this->notifyPlayer($this->getActivePlayerId(), 'resetInterfaceWithAllDatas', '', $newGameDatas); // this is notification to reset all data 
        $this->notifyAllPlayers("message",'setup called',[]);
    }

HINT: This is a great suggestion. If you make any simple mistake in any code in setupNewGame, you may see "BGA Service Exception" instead of anything very useful, since setupNewGame failed. For example, even sending a bad SQL string to $this->dbQuery() will generate this error. Very hard to debug!

Debugging endOfGame

Debugging end of game is hard if game ends! Make sure it does not end, then you can restore to previous state using saved states. In php file:

   $this->processEndOfGame();
   if ($this->getBgaEnvironment() == 'studio')
      $this->gamestate->nextState('debuglast'); // debug end
   else
      $this->gamestate->nextState('last'); // real end

You have to add a special user state in states.inc.php file (note: this snippet uses php constants instead of state numbers)

        STATE_GAME_TURN_NEXT_PLAYER => array( // normal state
                "name" => "gameTurnNextPlayer",
                "description" => '',
                "type" => "game",
                "action" => "st_gameTurnNextPlayer",
                "updateGameProgression" => true,
                "transitions" => array(
                        "next" => STATE_PLAYER_TURN_ACTION,
                        "loopback" =>  STATE_GAME_TURN_NEXT_PLAYER,
                        "last" => STATE_END_GAME,
                        "debuglast" => STATE_PLAYER_GAME_END // for debugging on studio
                        ), 
        ),
        // for debugging end of game
        STATE_PLAYER_GAME_END => array(
                "name" => "playerGameEnd",
                "description" => clienttranslate('Game Over'),
                "descriptionmyturn" => clienttranslate('Game Over'),
                "type" => "activeplayer",
                "args"=>"arg_playerTurnAction",
                "possibleactions" => array( "end"),
                "transitions" =>
                array(
                        "next" => STATE_END_GAME,
                        "loopback"=> STATE_PLAYER_GAME_END)
        ),

Writing custom debug functions

Sometimes you want to set up a situation to debug a special case, and you need tu update the game situation to fit your test. To do this quickly without needing to play a full game and hope to get the card you want to test, you can write custom debug functions.

For example, if I need to change the type of a card to your need, you can write a function like this :

    function debug_setCardType(int $id, int $type, int $typeArg = 0) {
      $this->DbQuery("UPDATE card SET card_type = $type, card_type_arg = $typeArg WHERE card_id = $id" );
    }

Then you can call this function by writing in the chat (on studio only) :

debug_setCardType(46,2,3)

If you want this debug function to be quickly accessible, you need to prefix it with debug_ to make it show in the debug menu of the Studio : Custom-debug-function-example.png

BGA will detect methods with this prefix and the associated parameters.

Functions with parameters will show with "..." in the debug menu and will show a popin to set the parameters. Functions without parameters will be called directly. The result in the end is the same as calling the function from the chat.

Debugging my HTML/CSS layout

Example situations

  • Why doesn't my game element show up in the interface?
  • Why hasn't my CSS property been applied to this element?
  • Why is this game element displayed at this position?

A useful tip when an element does not show up in the interface is to give it a red background:

#my_element {
  ... some CSS definitions ...
  background-color: red;
}

This way, you know if the element is not visible because of some CSS property or because of something else.

Another tip: sometimes, changing a CSS property has no visible effect on your interface. In that case, add a "display:none" property. If your element does not disappear, the bug probably comes from your CSS selector and not from your CSS property.

Using Chrome "Elements" tab (the first one), you can:

  • See the CURRENT HTML of your page. Remember that the classical "show page source" is inefficient with BGA as you are modifying the page source with your Javascript code.
  • Using the "magnifying glass", you can click on any part of your game interface and check its HTML code and associated CSS styles.
  • You can even modify directly some CSS properties and see how it looks immediately in the game interface.

When changing css you have to force reload (Ctrl+R). But it also reloads all images which is long, if you want it faster you can click the Reload CSS button.

Debugging Toolips CSS/Layout

To inspect tooltip you need to pin it so it does not disappear.

  • Chrome tools:
    • Open dev tools and switch to Source tab to engage with debugger
    • Hover over you element until tooltip showing
    • Press F8 - that stops the JS, so tooltip should freeze
    • Now you can go to Elements tab and find your tooltip at the botton in dijitTooltip div
    • Don't forget to resume it again to continue (F8 again or resume button)
  • Other method: Open the console (dev tools) large enough window, hover to make the tooltip appear and alt + tab to focus the console that will cover the tooltip. That way the onmouseout will never be triggered.

Debugging my Javascript game interface logic

To debug javascript you just use embedded browser debugger, usually found in Source tab of dev tools (F12)

Modern browsers also allow you to put breakpoints in your js code. It is in source tab, but its a bit difficult to find (your code that is), see picture below on how to insert breakpoints.

You can do this by pressing Ctrl + B when your cursor is on the line where you want to set the breakpoint, or by clicking just to the left of the line number. One thing to be aware of is that refreshing via F5 removes set breakpoints, due to some cache busting values added to the URL. This can be remedied by adding this line to the end of your javascript source file, which enables the proper source mapping:

//# sourceURL=myjsfile.js

Bga-debug.jpg

The easier method is to add breakpoint directly into your code and reload.

In Chrome, to add a breakpoint: add a line to your .js file

debugger; 

Refresh the page F5, and make sure you have the Developer tools window open, press F12. When the break-point is hit you can then step through your code and visualise variables, etc.

Do complex things on the PHP side

The most frequent case is the following: you want to compute possible moves in a game situation. Doing it in Javascript is a nightmare. Do it in PHP, and transfer the results to your client interface using the "args" game state property.

Note: See the Reversi tutorial for an example.

Add traces in your code

You can use the following:

console.log( variable_to_inspect )

It will give you the object structure of the variable in the Javascript console, without blocking the execution.

It's often a good idea to precede this call with a console.log( '### HERE ###' ); to find more easily the appropriate line in the console log.

alert( variable_to_inspect )

It will popup what you wish and pause the execution until you click ok.

This won't be useful for complex structures; only native types will be plainly displayed. But this is sometimes useful just with messages to make sure which way the execution goes.

Online format checkers

Copy and paste code for a quick code sanity check like the right number of brackets.

PHP: https://phpcodechecker.com/

JS: http://esprima.org/demo/validate.html

Checks for proper attributes values and browser's compatibility

CSS: http://jigsaw.w3.org/css-validator/

Some frequent errors

See Troubleshooting.

Get the database matching a bug report

When a player creates a bug report in production, a snapshot of the game database is taken. You can get access to this snapshot from the studio by following the steps below:

  • Create a table in the studio with the same game and number of players as the table for which the report has been written. Launch this table.
  • Load the bug dump by entering the bug report ID this debug section

LoadBugReportInput.png

Customizing the player id replacement

By default, the tool will replace any production player ids in the dump by the studio player ids of the created table.

In case you want more control, setup a `loadBugReportSQL` method in your game php file (in the DebugTrait if you have one). If a function with this name is detected on your game, the framework will not replace the ids, and it's up to you to change them.

Example of `loadBugReportSQL` function:

    public function loadBugReportSQL(int $reportId, array $studioPlayers): void
    {
        $prodPlayers = $this->getObjectListFromDb("SELECT `player_id` FROM `player`", true);
        $prodCount = count($prodPlayers);
        $studioCount = count($studioPlayers);
        if ($prodCount != $studioCount) {
            throw new BgaVisibleSystemException("Incorrect player count (bug report has $prodCount players, studio table has $studioCount players)");
        }

        // SQL specific to your game
        // For example, reset the current state if it's already game over
        $sql = [
            "UPDATE `global` SET `global_value` = 10 WHERE `global_id` = 1 AND `global_value` = 99"
        ];
        foreach ($prodPlayers as $index => $prodId) {
            $studioId = $studioPlayers[$index];
            // SQL common to all games
            $sql[] = "UPDATE `player` SET `player_id` = $studioId WHERE `player_id` = $prodId";
            $sql[] = "UPDATE `global` SET `global_value` = $studioId WHERE `global_value` = $prodId";
            $sql[] = "UPDATE `stats` SET `stats_player_id` = $studioId WHERE `stats_player_id` = $prodId";
            $sql[] = "UPDATE `bga_globals` set `name` = REPLACE(`name`, '$prodId', '$studioId'), `value` = REPLACE(`value`, '$prodId', '$studioId')";

            // SQL specific to your game
            $sql[] = "UPDATE `card` SET `card_location_arg` = $studioId WHERE `card_location_arg` = $prodId";
            $sql[] = "UPDATE `my_table` SET `my_column` = REPLACE(`my_column`, '$prodId', '$studioId')";
        }
        foreach ($sql as $q) {
            $this->DbQuery($q);
        }
        $this->reloadPlayersBasicInfos();
    }

If you used quietmint's `loadBugSQL` function, there are slight changes: the method name changed, and the studio players ids are plassed as method's second param, instead of computing them. All other parts added in PHP/JS files for loadBug method can be removed.

Note: If you get a "Not logged" error showing, you probably left a getCurrentPlayerId call in the `loadBugReportSQL` method.

Studio environment

Some developers have requested a method allowing to know which environment the module is running on. While in general, your code should be the same on all environments so that the validation process makes sense, this may allow for example to display some helpers for testing during development while being sure that they'll never show up outside of the studio.

From your <gamename>.game.php file you can use:

if ($this->getBgaEnvironment() == 'studio') { ... }

Or if needed from your <gamename>.view.php file:

if ($this->game->getBgaEnvironment() == 'studio') { ... }