Overview
I'm continuing to learn how to develop a game within Board Game Arena. In this section I'm working on the next step in the game I'm building out which involves the dealer picking a specific game to play. Ultimately, each game can only be selected once, but this is just going to focus on making the selection possible and ensuring that I can tell that the selection was made.
I realize I haven't named the game I'm working to implement and as I kept typing things like "the game I'm building out" it started to seem silly. The game is Guillotine. A game that my brother taught me years ago and now I play with a group of friends regularly. Here's a link to the rules if you're interested.
For some context, you might want to read the previous post about getting a hand of cards to show.
Create a state for selecting a game
After a hand is dealt the dealer must select the game to play. These directly translate to states to be implemented. So we need to create a new state for handling game selection and update the state for dealing a new hand to transition to the newly created one.
2 => [
"name" => "newHand",
"description" => "",
"type" => "game",
"action" => "stNewHand",
"updateGameProgression" => true,
"transitions" => ["selectGame" => 10]
],
10 => [
"name" => "gameSelection",
"description" => clienttranslate('${actplayer} must select the game to play'),
"descriptionmyturn" => clienttranslate('${you} must select the game to play'),
"type" => "activeplayer",
"args" => "argSelectGame",
"possibleactions" => ["gameSelection"],
"transitions" => []
],
Since the only thing we want to happen is for the game to transition from "newHand" to "gameSelection" we need to invoke the transition in the "newHand" action. So at the end of the
stNewHand
function we do just that with the following that references the name of the transition, "selectGame".$this->gamestate->nextState("selectGame");
Some things to note about the new state (see documentation for complete details):
- "type" => "activeplayer" indicates that a single player is active and must do something to continue the game.
- "description" and "descriptionmyturn" provide the text to display in the main action bar of the game.
- "args" exposes information to the client side from the declared function.
- "possibleactions" defines the possible actions the player can take and allows for them to be enforced so the functions aren't called at unexpected times.
State args
Guillotine has six games a player must select during the game with each one being selected only once. So ultimately we want to provide the list of available games for the current player to select. Though to simplify things, I'm going to ignore how the played games are tracked and just worry about making the six games available to select from.
function argSelectGame() {
$available_games = [];
foreach ($this->games as $game_type => $game) {
$available_games[] = ['type' => $game_type, 'name' => $game['name']];
}
return [
"available_games" => $available_games
];
}
I initially had the names of the games just hard-coded into this function, but I ran into issues keeping track of the current game since game state values can only be integers instead of strings. So I pulled the game details out to the
material.inc.php
file so they can be referenced everywhere.$this->games = [
'parliament' => [
'id' => 1,
'name' => clienttranslate('Parliament')
],
'spades' => [
'id' => 2,
'name' => clienttranslate('Spades')
],
'queens' => [
'id' => 3,
'name' => clienttranslate('Queens')
],
'royalty' => [
'id' => 4,
'name' => clienttranslate('Royalty')
],
'dominoes' => [
'id' => 5,
'name' => clienttranslate('Dominoes')
],
'guillotine' => [
'id' => 6,
'name' => clienttranslate('Guillotine')
],
];
Prompt current user
Now things shift to the client side and we need to allow the user to select the game they want to play. Within the
<game_name>.js
file there should already exist a function onUpdateActionButtons
which I just updated to handle this case.onUpdateActionButtons: function( stateName, args )
{
console.log( 'onUpdateActionButtons: '+stateName );
if( this.isCurrentPlayerActive() )
{
switch( stateName )
{
case 'gameSelection':
args.available_games?.forEach(game => {
this.addActionButton(game.type, _(game.name), () => this.onGameSelection(game.type))
});
break;
}
}
},
The
args.available_games
along with type
and name
are direct references to what was returned from the argSelectGame
function above. Currently it will error out because the onGameSelection
function isn't defined, so let's add that to the "Player's action" section of the file.onGameSelection: function(game_type) {
const action = "gameSelection";
if (!this.checkAction(action)) return;
this.ajaxcall(
"/" + this.game_name + "/" + this.game_name + "/" + action + ".html",
{lock: true, selected_game: game_type}, this, function (result) {}, function (is_error) {}
);
},
The call to
checkAction
is used to protect against the method being called when not expected and the action must match the name in the "possibleactions" in the state definition. The call to ajaxcall
sends the selected game to the back end. (See the documentation for details on the function.)Keep track of selected game
For the back end to be able to accept the call that gives it the selected game an action needs to be defined. This is done the in
<game_name>.action.php
file. The details of the file and the functions used in it are defined in the documentation.public function gameSelection() {
self::setAjaxMode();
$selected_game = self::getArg("selected_game", AT_alphanum, true);
$this->game->gameSelection($selected_game);
self::ajaxResponse();
}
As noted in the documentation for actions, they are just serving as a bridge from the front end to the main game logic. So this just extracts the "selected_game" argument that was passed along and calls a
gameSelection
function in the main <game_name>.game.php
file. The function should go in the "Player actions" section.function gameSelection($selected_game) {
self::checkAction("gameSelection");
$game_id = $this->games[$selected_game]['id'];
self::setGameStateValue(SELECTED_GAME, $game_id);
$game_name = $this->games[$selected_game]['name'];
self::notifyAllPlayers('gameSelection',
clienttranslate('${player_name} selects ${game_name} as the game to play'), [
'i18n' => ['game_name'],
'player_name' => self::getActivePlayerName(),
'game_name' => $game_name,
]);
}
This keeps track of the selected game by calling
setGameStateValue
with the id
defined in the game details defined in the first section. It also notifies all of the players in the game log which game was selected. SELECTED_GAME
is a constant that I defined in a new file models\constants.inc.php
. I like using a constant as I find it helps me avoid typos that I don't notice until testing.<?php
// Game State Variables
define('SELECTED_GAME', 'selected_game');
Additionally, the game state needs to be initialized in a couple of sections of the
<game_name>.game.php
file. In the __construct
function it needs to be defined.self::initGameStateLabels([
SELECTED_GAME => 10,
]);
If I didn't want a default value of 0 for the starting value I'd need to initialize it in the
setupNewGame
function, but since I am I can skip that. However, I know I'm going to have to reset the value for each new hand so I'll update the stNewHand
function that was created for dealing out the cards. This won't be used until we play through a hand, but I feel like adding it in this case. :)self::setGameStateValue(SELECTED_GAME, 0);
Wrap up
I'm still trying to understand how translations work and if everything I've done will be able to be translated appropriately. From what I understand my call to
notifyAllPlayers
should be good because the ${game_name}
variable in the message is specified in the i18n
parameter. I'm less certain about the addActionButton
call in the front end as I'm passing in a variable to it. Something I'll need to dig further into to make sure I'm doing that correctly.
No comments:
Post a Comment