Introducing "Undo" features in games can lead to bugs and/or bad interface design.
Please read the following carefully before implementing an Undo feature for your game.
What you must NEVER do
When you are restoring a game situation A from a game situation B:
- The Undo action must never change the active player. In other words, if player X clicks on "Undo", it must NOT make player Y active.
- If several players did game actions between A and B, you must NEVER provide an undo. In other words, an "Undo" action must NEVER force another player to redo some moves.
- No hidden (or private) information must have been revealed between A and B.
- No random event with a visible effect must have been triggered between A and B. This includes cards shuffling, dice roll, elements picking, ...
When to propose to Undo?
As a rule of thumb, on BGA we advise you to not undo moves.
Undo are painful for opponents, in real world and online. Some players may also use Undo to "test" situations while they should just think instead.
In many cases, proposing an "Undo" means also adding an extra step, which is not good for the game interface.
Undo may be useful in 2 main situations:
- To allow players to take back a move following a "misclick" that may ruin their game.
- To allow players to take back a very complex series of actions.
About Undo to avoid "misclick"
In most cases, you don't need this: players must pay attention :)
However, if several of the following cases correspond to your situation, you may consider adding an Undo for "misclicks":
- If the zone to click is very small, or close to other zones, misclicks may happened frequently. Note that if this only happens on mobile, you can propose the Undo to mobile users only.
- If there is already another step after the step you want to undo. In this case, adding an "Undo" does not add an extra step.
- If you are in the middle of a real "game action". For example, on Chess, it is normal to allow to undo the selection of a piece in order to select another one, as the move hasn't been done already.
- The action done by the player is obviously a mistake (in this case, please also consider adding a confirmation before the action).
On the contrary, you must be reluctant to add an Undo if:
- This adds an extra step.
- The game action is clear and the zone to click is large.
To conclude: we may allow a player to undo a move caused by a "misclick", but we shouldn't allow a player to undo a bad move he plays consciously.
Note: not all of these situations require server undo, in chess example above Undo of selection - you don't need to send piece selection to the server you can store it locally (this is "client side undo"), there is no Undo button in this case - user just selects another piece. Similary if you add prompt for clicking on critical buttons like "Pass" you can add extra prompt, but no Undo
About Undo to take back moves from a complex series of actions
Games with a complex serie of actions are an "exception" to BGA Undo policy.
In most games, you shouldn't provide an undo because players are not allowed to "explore" the different possibilities by playing and undoing their moves.
However, in some games, it is commonly admitted that players are allowed to take back a move. The typical example is a complex strategy game with a long serie of actions during your turn. "Through the Ages" or "Tzolk'in" are 2 good examples.
To know if this is a good idea to propose an undo, ask yourself the question: "when playing in real life, will my opponents authorize me to take back this move?". Depending on your answer, you should choose to provide an Undo, to not provide an Undo, or maybe to propose an Undo as an option for the game.
Note: in this situation it is also possible to use client side undo, instead of server side, see "Russian Railroads" for example (implemented using setClientState method).
How to implement Undo
There are 2 ways to implement Undo:
- The framework undoSavePoint/undoRestorepoint methods, documented here.
- Your own method.
As a rule of thumb, it is way better to use your own method to provide an Undo. The reasons are:
- The framework method is quite heavy to use (it saves the WHOLE situation). To cancel a simple move, it is way more efficient for you to execute the opposite move.
- The framework method is doing a full "reset" of the game interface. It is way more smooth and clear to play the opposite move from a player point of view.
- The framework Undo provides only ONE save point. If you want to provide a complex Undo (i.e.: the possibility to undo all the previous steps one by one), you must implement it by yourself.
However, the framework is very handy to use (only 2 methods), so this should be your first choice if some moves are very difficult to reverse on client side.
How does undoSavePoint/undoRestorepoint works?
The undoSavePoint method makes a full copy of the game database state.
The undoRestorePoint method:
- restores the game database
- removes all your game HTML + the player panels
- restores the game HTML + the player panels according to their state at the initial game loading
- calls your "setup" method
Important: because undoRestorePoint is calling your "setup" method, you must ensure that your whole game state can be restored from there. In particular, all your member variables must be initialized inside the "setup" method (and not only in the constructor !).
Adding simple Undo action to your game
Step 1: Declare in gameinfos
Add 'db_undo_support' => true in gamesinfo.inc.php
Note: if you have game in studio you must reload gameinfos and restart all these games
Step 2: Insert savepoint
Determine where you want to save the undo state. Must be single active player state
- Begging of new user turn
- Every time new information revealed such as
- dice roll
- card is drawn
- anything else hidden is shown
Insert
$this->undoSavepoint();
Step 3: Create undo action in server
Add players undo action in game.action.php:
public function undo() { self::setAjaxMode(); $this->game->action_undo(); self::ajaxResponse(); }
in game.game.php
function action_undo() { $this->undoRestorePoint(); // do not do checkAction - its on purpose - do not need to add undo as possible action to every state }
Step 4: Create undo action in client
in game.js add this to onUpdateActionButton regardless of state but inside isCurrenPlayerAction
this.addActionButton('button_undo', _('Undo'), 'onUndo', undefined, undefined, 'red');
and onUndo would be like this
onUndo: function (event) { const action = "undo"; this.ajaxcall( "/" + this.game_name + "/" + this.game_name + "/" + action + ".html", [], this, (result) => {}, (err) => { if (err) return; this.setMainTitle(_("Undo requested...")); dojo.empty("generalactions"); }, true ); },
Step 5: Make it fancy
If you want you can add extra styling to the Undo button such as it will appeat at right of all buttons in game.css
#button_undo { float: right; background-color: red; background-image: none; }