What is the Zombie mode
When a player leaves a game for any reason (being expelled out of time or quitting the game), he becomes a "zombie player". In this case, the results of the game won't count for statistics, but we must let the other players finish the game anyway.
That's why zombie mode exists: allow the other player to finish the game, against the Zombie (a bot) that replaces the leaver player.
The different Zombie levels
- Level 0: the passing zombie
- Just pass to the next step without doing any action
- For example, in 7 wonders, take a random card and discard it (we cannot just pass in this game as it would mess with the next player hand)
- For example, in Catan, roll the dice and pass
- Level 1: the random zombie
- Code a random possible action
- For example, in 7 wonders, take a random card and play it
- For example, in Catan, roll the dice and take a random possible action
- Level 2: the greedy zombie
- Take the action that will bring the most value given the visible information. Isn't planning for future move, nor remember previously known information. Does not try to take a lower value in order to block the opponent.
- For example, in 7 wonders, take a card that will bring the most points to the players. If there is a tie, the one that will cost less. If there is still a tie, randomly in the subset
- For example, in Catan, roll the dice and play the action that will bring the most points to the players
- Level 3: the smart zombie
- Can plan and/or remember previously revealed information.
- For example, in 7 wonders, it could remember which cards could be back in the next turn to make the best move.
- For example, in Catan, instead of declining all trades for zombie, trade only if it's in the benefit of the zombie player
Ideally, aim for level 1 or 2 for your implementation.
While developing your zombie mode, keep in mind that:
- Do not refer to the rules, because this situation is not planned by the rules.
- Try to figure that you are playing with your friends and one of them has to leave: how can we finish the game without killing the spirit of the game?
- The idea is NOT to develop an artificial intelligence for the game.
- Do not try to end the game early, even in a two-player game. The zombie is there to allow the game to continue, not to end it.
Implementing the Zombie mode
Each time a zombie player must play, your "zombieTurn" method is called.
Parameters:
- $state: the current game state.
- $active_player: the id of the active player.
Most of the time, your zombieTurn method looks like this:
protected function zombieTurn(array $state, int $active_player): void { $state_name = $state["name"]; if ($state["type"] === "activeplayer") { switch ($state_name) { case 'state1': $this->zombieTurn_state1($active_player); break; case 'state2': $this->zombieTurn_state2($active_player); break; case 'state3': $this->zombieTurn_state3($active_player); break; } // ... handle all your states in the same fashion } return; } // Make sure player is in a non-blocking status for role turn. if ($state["type"] === "multipleactiveplayer") { // this example is a Zombie level 0, for level 1 or more, it would be similar to the examples you can see for activeplayer states. $this->gamestate->setPlayerNonMultiactive($active_player, ''); return; } throw new \feException("Zombie mode not supported at this game state: \"{$state_name}\"."); }
Example of Zombie level 0
protected function zombieTurn_state1(int $active_player): void { $this->gamestate->nextState("zombiePass"); // you need to declare this transition that will bring the player/bot to the next state. }
Example of Zombie level 1
protected function zombieTurn_state2(int $active_player): void { $args = $this->argState1(); $possibleMoves = $args['possibleMoves']; $zombieChoice = $possibleMoves[bga_rand(0, count($possibleMoves) - 1)]; // random choice over possible moves return $this->actPlayState1Action($zombieChoice, $active_player); // this function will do the transition to the next state, as it does for a regular player action }
Example of Zombie level 2
protected function zombieTurn_state3(int $active_player): void { $args = $this->argState2(); $possibleSpaces = $args['possibleSpaces']; $playerCards = $this->getPlayerCards($active_player); $possibleAnswerPoints = []; foreach ($possibleSpaces as $choice) { $possibleAnswerPoints[$choice] = $this->getPointsFromState3Choice($playerCards, $choice); // for each possible choice, compute the points the player/Bot would immediately gain } $maxPoints = max($possibleAnswerPoints); $maxPointsAnswers = array_keys($possibleAnswerPoints, $maxPoints); // filter to get only the choices offering the most points $zombieChoice = $maxPointsAnswers[bga_rand(0, count($maxPointsAnswers) - 1)]; // random choice over top points possible moves, in case there is more than one $this->applyPlayCard($playerId, $zombieChoice); // this function will do the transition to the next state, as it does for a regular player action }
Very important: your zombie code will be called when the player leaves the game. The server will handle the Zombie turn, so there is no current player associated to this action. In your zombieTurn function, you must never use getCurrentPlayerId()
or getCurrentPlayerName()
, but use $active_player
or $this->getPlayerNameById($active_player)
instead.
Testing the Zombie mode
Create a table in Friendly mode (the infinite time symbol if you're starting from the game page) and Express Start.
Open one of the other players, and in the menu, select "Quit this game" and confirm.
As it's friendly mode, you won't get karma penalty. If it's not friendly mode, you would lose karma, and testing repeatedly, your player might not be allowed to join games anymore!
You can also use the debug tools to make the game play automatically up to a certain point
function debug_playToEndRound() { $currentRound = $this->globals->get(ROUND); $security = 0; // make sure the condition will not create an infinite loop! while ($this->globals->get(ROUND) == $currentRound && $security < 100) { $security++; $state = intval($this->gamestate->state_id()); $playerId = intval($this->getActivePlayerId()); switch ($state) { case ST_PLAYER_CHOOSE_CARD: $this->zombieTurn_chooseCard($playerId); break; case ST_PLAYER_PLAY_CARD: $this->zombieTurn_playCard($playerId); break; case ST_PLAYER_KEEP_CARD: $this->zombieTurn_keepCard($playerId); break; } } }
Click on the debug icon on the top right, select "playToEndRound", and see the bot playing automatically until it reaches the ST_MULTIPLAYER_BEFORE_END_ROUND state!