Code for an oTree server to play the MAD Chairs game (a.k.a. the Lifeboat Problem) and Kokata Paise Restaurant Problem. Such games are a metaphor for real-world division of scarce resources including division of jobs, hospital beds, and spaces in traffic. One important application is the division of opportunity to have voice in a conversation. Representative democracies, for example, explicitly establish a limited set of channels through which citizens can have voice in their government, and any of those channels can be overcrowded.
The "Advice" column in the bottom section of the screenshot above is optional and adjustable. Each previous selection is followed by what was advised (in parentheses) to that player for that round and is underlined if no other player clicked the same button in that round. The "Bonus" column displays each player's accumulated winnings thus far.
This is the complete code for an app which could be hosted on Heroku (that's how we used it). You can establish OTREE_ADMIN_PASSWORD in Heroku Dashboard via Settings > Config Vars, then use that password to create a room when you access the app via its URL. Connecting this app to Prolific required establishing OTREE_COMPLETION_URL in the same way (copied from your Prolific study) and copying the room URL into your Prolific study.
To test locally, install otree locally, download this project to a local folder, navigate your command prompt to that folder, and run otree devserver. When testing, it can be helpful to set SKIP_PREGAME = True in Instructions/__init__.py to skip straight to the game. A local installation is sufficient to run AI tournaments to establish grandmaster strategies as here.
In GroupPlayers, it can be useful to adjust these constants:
PLAYERS_PER_GROUP(default5): The number of players (should be at least 5 for four buttons)WAIT_LIMIT(default1200): Maximum seconds in the wait room before a player is automatically advanced to the alternate endingROBOTS(defaultNone): Specifies which players to replace with robots of which kinds. For example{2: "A"}would replace Player2 with a robot that always selects "A"."default"refers to all players not otherwise named, so{"default": "A", 2: None, 3: "B"}would replace Player3 with a robot that always selects "B", would leave Player2 human, and replace all other players with robots that always select "A". If ROBOTS is not specified as a dictionary, then it is assumed to be specifying a default, so"A"is equivalent to{"default": "A"}. If all players are replaced with robots, then a simulation will be triggered as soon as any player hits the waiting room (so it can be handy to combine such settings withSKIP_PREGAME = True).{random}will be replaced by a random valid selection. If an integer N > 0 is appended (e.g.{random1}), and the player lost the last round, then it will have 1/N probability of shifting to a random button not selected in the previous round (or selected only once, if all buttons were selected); otherwise the selection will repeat.{random3}may be an idealized version of initial observed human play.{caste}will be replaced by selections that reserve a unique button for each player with highest debt as described here.{turntaking}will be replaced by selections that reserve a unique button for each player with lowest debt as described here.- rotate is the kind of turn-taking that is unresponsive to deviation.
{rotate}will be replaced by the initial caste selections rotated 1 position per round. If a positive integer N is appended (e.g.{rotate2}), then it will rotate N positions per round. {radicaleq}will be replaced by selections that reserve a unique button for each of the poorest players (i.e., whichever players have the highest bonus thus far are assigned to lose a given round).{equalize}is like radicaleq except that deviants are (temporarily) excluded from the list of poorest players. Any history of deviance is gradually forgotten, so any player can rejoin the list by repeatedly following the equalize strategy, but keeping track of who to punish (like keeping track of debt used in caste and turntaking strategies) may require the help of a computer.{obey}will be replaced by whatever advice was given to that robot (see ADVICE below).- Invalid selections will be relaced with random valid selections.
To specify different selections for different rounds, specify a sequence of selections through which to cycle like ("A", "B", "C") or use an inner-dictionary to specify the round number in which to switch to a selection like {4: {4: "B"}}, which would be equivalent to {4: ("{obey}", "{obey}", "{obey}", "B", "B", B", "B",... When using an inner-dictionary, there must be an outer dictionary (which may require specifying "default"), and "{obey}" will be assumed for round 1 if no other selection is specified. As examples, values like {'default':'{obey}', 2: {2:'A', 3:'{obey}'}} were used to run experiments measuring the consequences of a single deviation when following the advice of "{rotate}", "{radicaleq}", "{equalize}", or "{turntaking}":
All four of these strategies have been called "turn taking" because they all yield optimal results when no player ever deviates, but the differences between these kinds of turn taking become apparent under deviation. "{rotate}" is a very simple strategy, but it does nothing to penalize deviance, and any player assigned to lose would lose if they don't deviate, so the costs of their deviance are born entirely by the victims with whom they collide. If carelessness would be problematic, "{rotate}" fails to discourage it. Even though "{equalize}" immediately punishes deviance, it aims to equalize total outcomes, so all players share equally in the costs of deviance (ending-up the same as "{radicaleq}" in the long-run). They discourage carelessness to some extent (but to negligible degree when there are enough other players). Only the strategy we have officially named "{turntaking}" disproportionately penalizes deviance, bringing justice and stability when conflict is inevitable.
The following tables were computed by running pairwise tournaments with five buttons (skip not hidden), 20 rounds, and three players from each of the two competing strategies. Each pair played 20 matches (10 in odd positions, 10 in even positions):
For this analysis, we use the term "edge" to refer to the minimum bonus achieved by those playing the strategy less the minimum achieved by those playing the opposing strategy. Strategies with negative edge are unstable because it would be rational for their lowest-achieving practitioner to defect to the opposing strategy. "{turntaking}" is the only strategy in this group that never has negative edge. While it has no edge over "{equalize}", it has edge over "{rotate}" with has edge over "{equalize}".
"{turntaking}", like most of the strategies, achieve 83% of the maximum possible bonus when no other strategy is played. While waiting for other players to defect to their strategy, the average turn-taker achieves as good or higher bonus than non-turn-takers. However, the implementation of "{turntaking}", "{caste}", and "{equalize}" require accounting that may be impractical without the help of a computer.
In MADChairs/__init__.py, it can be useful to adjust the following constants:
KOLKATA_PAISE(defaultFalse): To address concern that some real-world examples of scarce-resource division have external forces that limit waste (e.g., an employer resolves competition over a job by picking one winner), you can setHIDE_SKIP = FalseandKOLKATA_PAISE = True. That switches the game to the Kolkata Paise Restaurant Problem, confirming that"{turntaking}"remains the grandmaster strategy even if each collision gets one winner.
NUM_ROUNDS(default20): How many rounds to repeat the gameBUTTONS(default('A', 'B', 'C', 'D'): The button labels. This also determines the number and order of buttonsSORT_HISTORY(defaultFalse): Sorts the history like a leaderboard with the highest Bonus on top.HIDE_SKIP(defaultTrue): Hides the ability to skip the round. Many real-world situations permit players to skip. Players can raise their average payout by coordinating about skipping, so the option to skip makes ADVICE and CHAT more compelling.ADVICE(defaultNone): What to display in the advice column of the history table (if anything). As with ROBOTS, a cycle or dictionary can be used to specify different advice in different rounds. For example,{8: "Turn={turntaking}; Caste={caste}", 17: None}would display the turn-taking and caste selections in rounds 8-16 formatted like "Turn=B; Caste=A".HIDE_CHAT(defaultTrue): Hides the ability to chat with other players. Chat logs can be found on the main Data tab of oTreePRIZE(defaultcu(0.25)): How many British pounds (or server currency) to award players who click a button no other player clicksMAX_HISTORY_DISPLAY(default8): How many rounds of previous history to display. This can be specified by round as a dictionary, but history is lost each time the max is reset, so{1:8, 16:8}would show eight previous rounds in rounds 9-15, but zero in round 16, 1 in round 17, and so forth.PLAYER_LABEL(default'Player'): The prefix for player IDs (e.g. "Player1")ROBOT_LABEL(default'Bot'): The prefix when players are replaced with robots (e.g. "Bot1")MAX_TIME(default120): The maximum number of seconds per roundBUFFER_INIT(default60): How many seconds to remove from MAX_TIME if players do not request extra timeTIMER_DISPLAY_AT(default30): Allow players to request extra time when this many seconds remainTIMER_INCREMENT(default30): How many seconds to add when players request extra timeQUESTION_ROUNDS(default(2,)): The round(s) after which to ask users to describe their strategyDEVIANCE_ROUNDS(default()): The round(s) after which to ask users to explain their deviance from adviceQUESTION_TIMER(default120): The time limit (in seconds) for describing one's strategy or deviance
Data can be exported in Excel format. Some special data columns of note:
participant.skill_rating: The participant's trueskill rating at the end of the gameparticipant.disconnected:1if the participant was disconnectedparticipant.overwaited:1if the participant advanced t0 the alternate ending because they were in the waiting room too longparticipant.payoff: The participant's total winnings across all roundsparticipant.robot: The type of robot replacing this player (if any)MADChairs.{round}.player.selection: What the player selected in that roundMADChairs.{round}.player.advice: What advice was displayed to the player (if any)MADChairs.{round}.player.secondsElapsed: How many seconds the player took to make their selection in that roundMADChairs.{round}.player.payoff: How much the player won in that roundMADChairs.{round}.player.timedOut:1if the player timed-out (so their selection was randomized in that round)MADChairs.{round}.player.debt: Cumulative debt of favors owed to other players from the first round until that roundMADChairs.{round}.player.skill_estimate: The player's estimated skill based on performance in that and previous roundsMADChairs.{round}.player.strategy: The player's description of their strategy
You may want to use
- oTree HR
- Heroku dashboard
- oTree depository
- oTree documentation
- To include LLMs: Browser-use (Botex, Alter ego would require significant alterations)



