diff --git a/doc/pygambit.rst b/doc/pygambit.rst index b77da9f84..ceec241ec 100644 --- a/doc/pygambit.rst +++ b/doc/pygambit.rst @@ -23,7 +23,7 @@ They are numbered in the order they should be read. tutorials/running_locally tutorials/01_quickstart tutorials/02_extensive_form - tutorials/03_poker + tutorials/03_stripped_down_poker Advanced user tutorials ----------------------- diff --git a/doc/tutorials/03_poker.ipynb b/doc/tutorials/03_stripped_down_poker.ipynb similarity index 85% rename from doc/tutorials/03_poker.ipynb rename to doc/tutorials/03_stripped_down_poker.ipynb index 14292df75..624a1b624 100644 --- a/doc/tutorials/03_poker.ipynb +++ b/doc/tutorials/03_stripped_down_poker.ipynb @@ -5,31 +5,31 @@ "id": "98eb65d8", "metadata": {}, "source": [ - "# 3) A one-card poker game with private information\n", + "# 3) Stripped-down poker\n", "\n", - "In this tutorial, we'll create an extensive form representation of a one-card poker game [[Mye91](#references)] and use it to demonstrate and explain the following with Gambit:\n", + "In this tutorial, we'll create an extensive form representation of a one-card poker game from [Reiley et al (2008)](#references), a classroom game under the name \"stripped-down poker\".\n", + "This is perhaps the simplest interesting game with imperfect information.\n", + "\n", + "We'll use \"stripped-down poker\" to demonstrate and explain the following with Gambit:\n", "\n", "1. Setting up an extensive form game with imperfect information using [information sets](#information-sets)\n", "2. [Computing and interpreting Nash equilibria](#computing-and-interpreting-nash-equilibria) and understanding mixed behaviour and mixed strategy profiles\n", "3. [Acceptance criteria for Nash equilibria](#acceptance-criteria-for-nash-equilibria)\n", "\n", - "A version of this game also appears in [[RUW08](#references)], as a classroom game under the name \"stripped-down poker\".\n", - "This is perhaps the simplest interesting game with imperfect information.\n", - "\n", "In our version of the game, there are two players, **Alice** and **Bob**, and a deck of cards, with equal numbers of **King** and **Queen** cards.\n", "\n", "- The game begins with each player putting \\$1 in the pot.\n", - " - A card is dealt at random to Alice\n", - " - Alice observes her card\n", - " - Bob does not observe the card\n", - "- Alice then chooses either to **Raise** or to **Fold**.\n", + " - A card is dealt at random to Alice.\n", + " - Alice observes her card.\n", + " - Bob does not observe the card.\n", + "- Alice then chooses either to **Bet** or to **Fold**.\n", " - If she chooses to Fold, Bob wins the pot and the game ends.\n", - " - If she chooses to Raise, she adds another \\$1 to the pot.\n", - "- Bob then chooses either to **Meet** or **Pass**.\n", - " - If he chooses to Pass, Alice wins the pot and the game ends.\n", - " - If he chooses to Meet, he adds another $1 to the pot.\n", + " - If she chooses to Bet, she adds another \\$1 to the pot.\n", + "- Bob then chooses either to **Call** or **Fold**.\n", + " - If he chooses to Fold, Alice wins the pot and the game ends.\n", + " - If he chooses to Call, he adds another $1 to the pot.\n", "- There is then a showdown, in which Alice reveals her card.\n", - " - If she has a King, then she wins the pot;\n", + " - If she has a King, then she wins the pot.\n", " - If she has a Queen, then Bob wins the pot." ] }, @@ -48,7 +48,7 @@ "id": "70819881", "metadata": {}, "source": [ - "Create the game with two players." + "Create the game with two players:" ] }, { @@ -60,7 +60,7 @@ "source": [ "g = gbt.Game.new_tree(\n", " players=[\"Alice\", \"Bob\"],\n", - " title=\"One card poker\"\n", + " title=\"Stripped-Down Poker: a simple game of one-card poker from Reiley et al (2008).\"\n", ")" ] }, @@ -82,9 +82,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Player(game=Game(title='One card poker'), label='Alice')\n", - "Player(game=Game(title='One card poker'), label='Bob')\n", - "ChancePlayer(game=Game(title='One card poker'))\n" + "Player(game=Game(title='Stripped-Down Poker: a simple game of one-card poker from Reiley et al (2008).'), label='Alice')\n", + "Player(game=Game(title='Stripped-Down Poker: a simple game of one-card poker from Reiley et al (2008).'), label='Bob')\n", + "ChancePlayer(game=Game(title='Stripped-Down Poker: a simple game of one-card poker from Reiley et al (2008).'))\n" ] } ], @@ -105,7 +105,7 @@ "\n", "The first step in this game is that Alice is dealt a card which could be a King or Queen, each with probability 1/2.\n", "\n", - "To simulate this in Gambit, we create a chance player move at the root node of the game." + "To simulate this in Gambit, we create a chance player move at the root node of the game:" ] }, { @@ -133,7 +133,7 @@ "Alice knows her card, so the two nodes at which she has the move are part of different **information sets**.\n", "\n", "We'll therefore need to append Alice's move separately for each of the root node's children, i.e. the scenarios where she has a King or a Queen.\n", - "Let's now add both of these possible moves." + "Let's now add both of these possible moves:" ] }, { @@ -147,7 +147,7 @@ " g.append_move(\n", " node,\n", " player=\"Alice\",\n", - " actions=[\"Raise\", \"Fold\"]\n", + " actions=[\"Bet\", \"Fold\"]\n", " )" ] }, @@ -160,14 +160,13 @@ "\n", "In contrast, Bob does not know Alice’s card, and therefore cannot distinguish between the two nodes at which he has to make his decision:\n", "\n", - " - Chance player chooses King, then Alice Raises: `g.root.children[\"King\"].children[\"Raise\"]`\n", - " - Chance player chooses Queen, then Alice Raises: `g.root.children[\"Queen\"].children[\"Raise\"]`\n", - "\n", - "In other words, Bob's decision when Alice raises with a Queen should be part of the same information set as Bob's decision when Alice raises with a King.\n", + " - Chance player chooses King, then Alice Bets: `g.root.children[\"King\"].children[\"Bet\"]`\n", + " - Chance player chooses Queen, then Alice Bets: `g.root.children[\"Queen\"].children[\"Bet\"]`\n", "\n", - "To set this scenario up in Gambit, we'll need to use `Game.append_infoset` to add a move as part of an existing information set (represented in Gambit as an `Infoset`).\n", + "In other words, Bob's decision when Alice Bets with a Queen should be part of the same information set as Bob's decision when Alice Bets with a King.\n", "\n", - "First, let's add Bob's move to the node where Alice has raised with a King." + "To set this scenario up in Gambit, we'll need to add both possible moves as part of the same information set (represented in Gambit as an `Infoset`).\n", + "This can be done by passing a list of nodes to the `append_move` method:" ] }, { @@ -178,30 +177,9 @@ "outputs": [], "source": [ "g.append_move(\n", - " g.root.children[\"King\"].children[\"Raise\"],\n", + " [g.root.children[\"King\"].children[\"Bet\"], g.root.children[\"Queen\"].children[\"Bet\"]],\n", " player=\"Bob\",\n", - " actions=[\"Meet\", \"Pass\"]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "689ce12c", - "metadata": {}, - "source": [ - "Now let's add the information set we created at the node where Alice raised with a King, to the node where Alice raised with a Queen." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "655cdae3", - "metadata": {}, - "outputs": [], - "source": [ - "g.append_infoset(\n", - " g.root.children[\"Queen\"].children[\"Raise\"],\n", - " infoset=g.root.children[\"King\"].children[\"Raise\"].infoset\n", + " actions=[\"Call\", \"Fold\"]\n", ")" ] }, @@ -211,24 +189,24 @@ "metadata": {}, "source": [ "In game theory terms, this creates \"imperfect information\".\n", - "Bob cannot distinguish between these two nodes in the game tree, so he must use the same strategy (same probabilities for Meet vs. Pass) in both situations.\n", + "Bob cannot distinguish between these two nodes in the game tree, so he must use the same same probabilities for Call vs. Fold in both situations.\n", "\n", - "This is crucial in games where players must make decisions without complete knowledge of their opponents' private information.\n", + "This is crucial in games where players must make decisions without full knowledge of the state of the game.\n", "\n", - "Let's now set up the four possible payoff outcomes for the game." + "Let's now set up the four possible payoff outcomes for the game. We'll label them according to player 1 (Alice):" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "87c988be", "metadata": {}, "outputs": [], "source": [ - "alice_winsbig = g.add_outcome([2, -2], label=\"Alice wins big\")\n", - "alice_wins = g.add_outcome([1, -1], label=\"Alice wins\")\n", - "bob_winsbig = g.add_outcome([-2, 2], label=\"Bob wins big\")\n", - "bob_wins = g.add_outcome([-1, 1], label=\"Bob wins\")" + "win_big = g.add_outcome([2, -2], label=\"Win Big\")\n", + "win = g.add_outcome([1, -1], label=\"Win\")\n", + "lose_big = g.add_outcome([-2, 2], label=\"Lose Big\")\n", + "lose = g.add_outcome([-1, 1], label=\"Lose\")" ] }, { @@ -236,29 +214,29 @@ "id": "467a2c39", "metadata": {}, "source": [ - "Finally, we should assign an outcome to each of the terminal nodes in the game tree." + "Finally, we should assign an outcome to each of the terminal nodes in the game tree:" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "29aa60a0", "metadata": {}, "outputs": [], "source": [ "# Alice folds, Bob wins small\n", - "g.set_outcome(g.root.children[\"King\"].children[\"Fold\"], bob_wins)\n", - "g.set_outcome(g.root.children[\"Queen\"].children[\"Fold\"], bob_wins)\n", + "g.set_outcome(g.root.children[\"King\"].children[\"Fold\"], lose)\n", + "g.set_outcome(g.root.children[\"Queen\"].children[\"Fold\"], lose)\n", "\n", - "# Bob sees Alice raise and calls, correctly believing she is bluffing, Bob wins big\n", - "g.set_outcome(g.root.children[\"Queen\"].children[\"Raise\"].children[\"Meet\"], bob_winsbig)\n", + "# Bob sees Alice Bet and calls, correctly believing she is bluffing, Bob wins big\n", + "g.set_outcome(g.root.children[\"Queen\"].children[\"Bet\"].children[\"Call\"], lose_big)\n", "\n", - "# Bob sees Alice raise and calls, incorrectly believing she is bluffing, Alice wins big\n", - "g.set_outcome(g.root.children[\"King\"].children[\"Raise\"].children[\"Meet\"], alice_winsbig)\n", + "# Bob sees Alice Bet and calls, incorrectly believing she is bluffing, Alice wins big\n", + "g.set_outcome(g.root.children[\"King\"].children[\"Bet\"].children[\"Call\"], win_big)\n", "\n", - "# Bob does not call Alice's raise, Alice wins small\n", - "g.set_outcome(g.root.children[\"King\"].children[\"Raise\"].children[\"Pass\"], alice_wins)\n", - "g.set_outcome(g.root.children[\"Queen\"].children[\"Raise\"].children[\"Pass\"], alice_wins)" + "# Bob does not call Alice's Bet, Alice wins small\n", + "g.set_outcome(g.root.children[\"King\"].children[\"Bet\"].children[\"Fold\"], win)\n", + "g.set_outcome(g.root.children[\"Queen\"].children[\"Bet\"].children[\"Fold\"], win)" ] }, { @@ -269,12 +247,12 @@ "## Computing and interpreting Nash equilibria\n", "\n", "\n", - "Since our one-card poker game is extensive form and has two players, we can use the `lcp_solve` algorithm in Gambit to compute the Nash equilibria." + "Since our one-card poker game has two players, we can use the `lcp_solve` algorithm in Gambit to compute a Nash equilibrium:" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "4d92c8d9", "metadata": {}, "outputs": [ @@ -284,7 +262,7 @@ "NashComputationResult(method='lcp', rational=True, use_strategic=False, equilibria=[[[[Rational(1, 1), Rational(0, 1)], [Rational(1, 3), Rational(2, 3)]], [[Rational(2, 3), Rational(1, 3)]]]], parameters={'stop_after': 0, 'max_depth': 0})" ] }, - "execution_count": 10, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -308,7 +286,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "9967d6f7", "metadata": {}, "outputs": [ @@ -325,9 +303,17 @@ "eqm = result.equilibria[0]" ] }, + { + "cell_type": "markdown", + "id": "1b1cd21e", + "metadata": {}, + "source": [ + "If we inspect the object type, we can see it's a `MixedBehaviorProfileRational` which is a subclass of `MixedBehaviorProfile` that uses rational numbers for probabilities:" + ] + }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "3293e818", "metadata": {}, "outputs": [ @@ -337,14 +323,12 @@ "pygambit.gambit.MixedBehaviorProfileRational" ] }, - "execution_count": 12, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# MixedBehaviorProfileRational is a subclass of MixedBehaviorProfile that uses\n", - "# rational numbers for probabilities.\n", "type(eqm)" ] }, @@ -360,7 +344,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "id": "4cf38264", "metadata": {}, "outputs": [ @@ -370,7 +354,7 @@ "pygambit.gambit.MixedBehavior" ] }, - "execution_count": 13, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -381,7 +365,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "id": "85e7fdda", "metadata": {}, "outputs": [ @@ -394,7 +378,7 @@ "[[Rational(1, 1), Rational(0, 1)], [Rational(1, 3), Rational(2, 3)]]" ] }, - "execution_count": 14, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -408,7 +392,7 @@ "id": "6615115d", "metadata": {}, "source": [ - "In this case, at Alice's first information set, the one at which she has the King, she always raises.\n", + "In this case, at Alice's first information set, the one at which she has the King, she always Bets.\n", "\n", "At her second information set, where she has the Queen, she sometimes bluffs, raising with probability one-third.\n", "\n", @@ -419,7 +403,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "id": "f45a82b6", "metadata": {}, "outputs": [ @@ -427,8 +411,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "At information set 0, Alice plays Raise with probability: 1 and Fold with probability: 0\n", - "At information set 1, Alice plays Raise with probability: 1/3 and Fold with probability: 2/3\n" + "At information set 0, Alice plays Bet with probability: 1 and Fold with probability: 0\n", + "At information set 1, Alice plays Bet with probability: 1/3 and Fold with probability: 2/3\n" ] } ], @@ -436,7 +420,7 @@ "for infoset, mixed_action in eqm[\"Alice\"].mixed_actions():\n", " print(\n", " f\"At information set {infoset.number}, \"\n", - " f\"Alice plays Raise with probability: {mixed_action['Raise']}\"\n", + " f\"Alice plays Bet with probability: {mixed_action['Bet']}\"\n", " f\" and Fold with probability: {mixed_action['Fold']}\"\n", " )" ] @@ -451,7 +435,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "id": "83bbd3e5", "metadata": {}, "outputs": [ @@ -459,9 +443,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "At information set 0, Alice plays Raise with probability: 1\n", + "At information set 0, Alice plays Bet with probability: 1\n", "At information set 0, Alice plays Fold with probability: 0\n", - "At information set 1, Alice plays Raise with probability: 1/3\n", + "At information set 1, Alice plays Bet with probability: 1/3\n", "At information set 1, Alice plays Fold with probability: 2/3\n" ] } @@ -484,7 +468,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "id": "6bf51b38", "metadata": {}, "outputs": [ @@ -497,7 +481,7 @@ "[[Rational(2, 3), Rational(1, 3)]]" ] }, - "execution_count": 17, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -511,16 +495,16 @@ "id": "e906c4c4", "metadata": {}, "source": [ - "Bob meets Alice’s raise two-thirds of the time.\n", - "The label “Raise” is used in more than one information set for Alice, so in the above we had to specify information sets when indexing.\n", + "Bob Calls Alice’s Bet two-thirds of the time.\n", + "The label “Bet” is used in more than one information set for Alice, so in the above we had to specify information sets when indexing.\n", "\n", "When there is no ambiguity, we can specify action labels directly.\n", - "So for example, because Bob has only one action named “Meet” in the game, we can extract the probability that Bob plays “Meet” by:" + "So for example, because Bob has only one action named “Call” in the game, we can extract the probability that Bob plays “Call” by:" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "id": "2966e700", "metadata": {}, "outputs": [ @@ -533,13 +517,13 @@ "Rational(2, 3)" ] }, - "execution_count": 18, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "eqm[\"Bob\"][\"Meet\"]" + "eqm[\"Bob\"][\"Call\"]" ] }, { @@ -552,7 +536,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "id": "f5a7f110", "metadata": {}, "outputs": [ @@ -565,13 +549,13 @@ "Rational(2, 3)" ] }, - "execution_count": 19, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "eqm[\"Meet\"]" + "eqm[\"Call\"]" ] }, { @@ -579,14 +563,14 @@ "id": "db19411b", "metadata": {}, "source": [ - "Because this is an equilibrium, Bob is indifferent between the two actions at his information set, meaning he has no reason to prefer one action over the other, given Alice's expected strategy.\n", + "Because this is an equilibrium, Bob is indifferent between the two actions at his information set, meaning he has no reason to prefer one action over the other, in expectation, given Alice's expected strategy.\n", "\n", "`MixedBehaviorProfile.action_value` returns the expected payoff of taking an action, conditional on reaching that action's information set:" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "id": "a7d3816d", "metadata": {}, "outputs": [ @@ -594,8 +578,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "When Bob plays Meet his expected payoff is -1\n", - "When Bob plays Pass his expected payoff is -1\n" + "When Bob plays Call his expected payoff is -1\n", + "When Bob plays Fold his expected payoff is -1\n" ] } ], @@ -616,12 +600,12 @@ "\n", "`MixedBehaviorProfile.belief` returns the probability of reaching a node, conditional on its information set being reached.\n", "\n", - "Recall that the two nodes in Bob's only information set are `g.root.children[\"King\"].children[\"Raise\"]` and `g.root.children[\"Queen\"].children[\"Raise\"]`):" + "Recall that the two nodes in Bob's only information set are `g.root.children[\"King\"].children[\"Bet\"]` and `g.root.children[\"Queen\"].children[\"Bet\"]`):" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "id": "4a54b20c", "metadata": {}, "outputs": [ @@ -629,8 +613,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Bob's belief in reaching the King -> Raise node is: 3/4\n", - "Bob's belief in reaching the Queen -> Raise node is: 1/4\n" + "Bob's belief in reaching the King -> Bet node is: 3/4\n", + "Bob's belief in reaching the Queen -> Bet node is: 1/4\n" ] } ], @@ -647,14 +631,14 @@ "id": "351bb3ce", "metadata": {}, "source": [ - "Bob believes that, conditional on Alice raising, there's a 3/4 chance that she has the King; therefore, the expected payoff to meeting is in fact -1 as computed.\n", + "Bob believes that, conditional on Alice raising, there's a 3/4 chance that she has the King; therefore, the expected payoff to Calling is in fact -1 as computed.\n", "\n", "`MixedBehaviorProfile.infoset_prob` returns the probability that an information set is reached:" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "id": "b250c1cd", "metadata": {}, "outputs": [ @@ -667,7 +651,7 @@ "Rational(2, 3)" ] }, - "execution_count": 22, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -681,12 +665,12 @@ "id": "9216ea34", "metadata": {}, "source": [ - "The corresponding probability that a node is reached in the play of the game is given by `MixedBehaviorProfile.realiz_prob`, and the expected payoff to a player conditional on reaching a node is given by `MixedBehaviorProfile.node_value`." + "The corresponding probability that a node is reached in the play of the game is given by `MixedBehaviorProfile.realiz_prob`, and the expected payoff to a player conditional on reaching a node is given by `MixedBehaviorProfile.node_value`:" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "id": "6f01846b", "metadata": {}, "outputs": [ @@ -694,8 +678,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "The probability that the node King -> Raise is reached is: 1/2. Bob's expected payoff conditional on reaching this node is -5/3\n", - "The probability that the node Queen -> Raise is reached is: 1/6. Bob's expected payoff conditional on reaching this node is 1\n" + "The probability that the node King -> Bet is reached is: 1/2. Bob's expected payoff conditional on reaching this node is -5/3\n", + "The probability that the node Queen -> Bet is reached is: 1/6. Bob's expected payoff conditional on reaching this node is 1\n" ] } ], @@ -718,7 +702,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "id": "5079d231", "metadata": {}, "outputs": [ @@ -731,7 +715,7 @@ "Rational(1, 3)" ] }, - "execution_count": 24, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -742,7 +726,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "id": "c55f2c7a", "metadata": {}, "outputs": [ @@ -755,7 +739,7 @@ "Rational(-1, 3)" ] }, - "execution_count": 25, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -777,12 +761,12 @@ "\n", "When a game has an extensive representation, equilibrium finding methods default to computing on that representation.\n", "It is also possible to compute using the strategic representation.\n", - "`pygambit` transparently computes the reduced strategic form representation of an extensive game." + "`pygambit` transparently computes the reduced strategic form representation of an extensive game:" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "id": "d4ecff88", "metadata": {}, "outputs": [ @@ -792,7 +776,7 @@ "['11', '12', '21', '22']" ] }, - "execution_count": 26, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -809,14 +793,14 @@ "In the strategic form of this game, Alice has four strategies.\n", "\n", "The generated strategy labels list the action numbers taken at each information set.\n", - "For example, label '11' refers to the strategy gets dealt the King, then raises.\n", + "For example, label '11' refers to the strategy gets dealt the King, then Bets.\n", "\n", "We can therefore apply a method which operates on a strategic game to any game with an extensive representation." ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 26, "id": "24e4b6e8", "metadata": {}, "outputs": [ @@ -826,7 +810,7 @@ "NashComputationResult(method='gnm', rational=False, use_strategic=True, equilibria=[[[0.33333333333866677, 0.6666666666613335, 0.0, 0.0], [0.6666666666559997, 0.3333333333440004]]], parameters={'perturbation': [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0]], 'end_lambda': -10.0, 'steps': 100, 'local_newton_interval': 3, 'local_newton_maxits': 10})" ] }, - "execution_count": 27, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -843,12 +827,12 @@ "source": [ "`gnm_solve` can be applied to any game with any number of players, and uses a path-following process in floating-point arithmetic, so it returns profiles with probabilities expressed as floating-point numbers.\n", "\n", - "This method operates on the strategic representation of the game, so the returned results are of type `MixedStrategyProfile` (specifically `MixedStrategyProfileDouble`)." + "This method operates on the strategic representation of the game, so the returned results are of type `MixedStrategyProfile` (specifically `MixedStrategyProfileDouble`):" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 27, "id": "d9ffb4b8", "metadata": {}, "outputs": [ @@ -858,7 +842,7 @@ "pygambit.gambit.MixedStrategyProfileDouble" ] }, - "execution_count": 28, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -880,7 +864,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 28, "id": "56e2f847", "metadata": {}, "outputs": [ @@ -933,7 +917,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 29, "id": "d18a91f0", "metadata": {}, "outputs": [ @@ -942,14 +926,14 @@ "output_type": "stream", "text": [ "Alice's expected payoffs:\n", - "At information set 0, when playing Raise - gnm: 1.6667, lcp: 1.6667\n", + "At information set 0, when playing Bet - gnm: 1.6667, lcp: 1.6667\n", "At information set 0, when playing Fold - gnm: -1.0000, lcp: -1.0000\n", - "At information set 1, when playing Raise - gnm: -1.0000, lcp: -1.0000\n", + "At information set 1, when playing Bet - gnm: -1.0000, lcp: -1.0000\n", "At information set 1, when playing Fold - gnm: -1.0000, lcp: -1.0000\n", "\n", "Bob's expected payoffs:\n", - "At information set 0, when playing Meet - gnm: -1.0000, lcp: -1.0000\n", - "At information set 0, when playing Pass - gnm: -1.0000, lcp: -1.0000\n", + "At information set 0, when playing Call - gnm: -1.0000, lcp: -1.0000\n", + "At information set 0, when playing Fold - gnm: -1.0000, lcp: -1.0000\n", "\n" ] } @@ -984,7 +968,7 @@ "\n", "> In game theory, an epsilon-equilibrium, or near-Nash equilibrium, is a strategy profile that approximately satisfies the condition of Nash equilibrium. In a Nash equilibrium, no player has an incentive to change his behavior. In an approximate Nash equilibrium, this requirement is weakened to allow the possibility that a player may have a small incentive to do something different.\n", "\n", - "> Given a game and a real non-negative parameter $\\varepsilon$, a strategy profile is said to be an $\\varepsilon$-equilibrium if it is not possible for any player to gain more than $\\varepsilon$ in expected payoff by unilaterally deviating from his strategy. Every Nash Equilibrium is equivalent to an $\\varepsilon$-equilibrium where $\\varepsilon = 0$.\n", + "> Given a game and a real non-negative parameter $\\varepsilon$, a strategy profile is said to be an $\\varepsilon$-equilibrium if it is not possible for any player to gain more than $\\varepsilon$ in expected payoff by unilaterally deviating from his strategy. Every Nash Equilibrium is an $\\varepsilon$-equilibrium where $\\varepsilon = 0$.\n", "\n", "\n", "To provide a uniform interface across methods, where relevant Gambit provides a parameter\n", @@ -994,12 +978,12 @@ "Any profile returned as an equilibrium is guaranteed to be an $\\varepsilon$-equilibrium, for $\\varepsilon$ no more than `maxregret`\n", "times the difference of the game's maximum and minimum payoffs.\n", "\n", - "As an example, consider solving our one-card poker game using `logit_solve`. The range of the payoffs in this game is 4 (from +2 to -2).\n" + "As an example, consider solving our one-card poker game using `logit_solve`. The range of the payoffs in this game is 4 (from +2 to -2):\n" ] }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 30, "id": "0c55f745", "metadata": {}, "outputs": [ @@ -1009,7 +993,7 @@ "(Rational(2, 1), Rational(-2, 1))" ] }, - "execution_count": 31, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -1031,7 +1015,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 31, "id": "101598c6", "metadata": {}, "outputs": [ @@ -1041,7 +1025,7 @@ "1" ] }, - "execution_count": 32, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -1053,7 +1037,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 32, "id": "9b142728", "metadata": {}, "outputs": [ @@ -1063,7 +1047,7 @@ "3.987411578698641e-08" ] }, - "execution_count": 33, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -1084,7 +1068,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 33, "id": "ff405409", "metadata": {}, "outputs": [ @@ -1094,7 +1078,7 @@ "9.968528946746602e-09" ] }, - "execution_count": 34, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -1115,7 +1099,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 34, "id": "31b0143c", "metadata": {}, "outputs": [ @@ -1125,7 +1109,7 @@ "9.395259956013202e-05" ] }, - "execution_count": 35, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -1139,12 +1123,12 @@ "id": "dc8c8509", "metadata": {}, "source": [ - "The tradeoff comes from some methods being slow to converge on some games, making it useful instead to get a more coarse approximation to an equilibrium (higher `maxregret` value) which is faster to calculate. " + "The tradeoff comes from some methods being slow to converge on some games, making it useful instead to get a more coarse approximation to an equilibrium (higher `maxregret` value) which is faster to calculate:" ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 35, "id": "7cfba34a", "metadata": {}, "outputs": [ @@ -1152,8 +1136,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 2.84 ms, sys: 19 μs, total: 2.86 ms\n", - "Wall time: 2.86 ms\n" + "CPU times: user 9.9 ms, sys: 230 μs, total: 10.1 ms\n", + "Wall time: 10.1 ms\n" ] }, { @@ -1162,7 +1146,7 @@ "NashComputationResult(method='logit', rational=False, use_strategic=False, equilibria=[[[[1.0, 0.0], [0.3338351656285655, 0.666164834417892]], [[0.6670407651644307, 0.3329592348608147]]]], parameters={'first_step': 0.03, 'max_accel': 1.1})" ] }, - "execution_count": 36, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -1174,7 +1158,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 36, "id": "6f1809a7", "metadata": {}, "outputs": [ @@ -1182,8 +1166,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 5.51 ms, sys: 72 μs, total: 5.58 ms\n", - "Wall time: 5.59 ms\n" + "CPU times: user 18.4 ms, sys: 210 μs, total: 18.6 ms\n", + "Wall time: 18.6 ms\n" ] }, { @@ -1192,7 +1176,7 @@ "NashComputationResult(method='logit', rational=False, use_strategic=False, equilibria=[[[[1.0, 0.0], [0.33333338649882943, 0.6666666135011706]], [[0.6666667065407631, 0.3333332934592369]]]], parameters={'first_step': 0.03, 'max_accel': 1.1})" ] }, - "execution_count": 37, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -1209,22 +1193,22 @@ "source": [ "The convention of expressing `maxregret` scaled by the game's payoffs standardises the behavior of methods across games.\n", "\n", - "For example, consider solving the poker game instead using `liap_solve()`." + "For example, consider solving the poker game instead using `liap_solve()`:" ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 37, "id": "414b6f65", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "5.5099518433632255e-05" + "5.509533871672634e-05" ] }, - "execution_count": 38, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -1241,22 +1225,22 @@ "id": "c6853432", "metadata": {}, "source": [ - "If, instead, we double all payoffs, the output of the method is unchanged." + "If, instead, we double all payoffs, the output of the method is unchanged:" ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 38, "id": "a892dc2b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "5.5099518433632255e-05" + "5.509533871672634e-05" ] }, - "execution_count": 39, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -1287,7 +1271,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 39, "id": "2f79695a", "metadata": {}, "outputs": [ @@ -1297,7 +1281,7 @@ "[Rational(1, 3), Rational(1, 3), Rational(1, 3)]" ] }, - "execution_count": 40, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" } @@ -1321,7 +1305,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 40, "id": "5de6acb2", "metadata": {}, "outputs": [ @@ -1331,7 +1315,7 @@ "[Rational(1, 4), Rational(1, 2), Rational(1, 4)]" ] }, - "execution_count": 41, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -1354,7 +1338,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 41, "id": "c47d2ab6", "metadata": {}, "outputs": [ @@ -1364,7 +1348,7 @@ "[Decimal('0.25'), Decimal('0.50'), Decimal('0.25')]" ] }, - "execution_count": 42, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" } @@ -1391,7 +1375,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 42, "id": "04329084", "metadata": {}, "outputs": [ @@ -1401,7 +1385,7 @@ "[Rational(1, 4), Rational(1, 2), Rational(1, 4)]" ] }, - "execution_count": 43, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -1413,7 +1397,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 43, "id": "9015e129", "metadata": {}, "outputs": [ @@ -1423,7 +1407,7 @@ "[Decimal('0.25'), Decimal('0.50'), Decimal('0.25')]" ] }, - "execution_count": 44, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } @@ -1443,12 +1427,12 @@ "\n", "`pygambit` attempts to render `float` values in an appropriate `Decimal` equivalent.\n", "In the majority of cases, this creates no problems.\n", - "For example," + "For example:" ] }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 44, "id": "0a019aa5", "metadata": {}, "outputs": [ @@ -1458,7 +1442,7 @@ "[Decimal('0.25'), Decimal('0.5'), Decimal('0.25')]" ] }, - "execution_count": 45, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -1473,12 +1457,12 @@ "id": "d53adcd4", "metadata": {}, "source": [ - "However, rounding can cause difficulties when attempting to use `float` values to represent values which do not have an exact decimal representation" + "However, rounding can cause difficulties when attempting to use `float` values to represent values which do not have an exact decimal representation:" ] }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 45, "id": "1991d288", "metadata": {}, "outputs": [ @@ -1503,12 +1487,12 @@ "metadata": {}, "source": [ "This behavior can be slightly surprising, especially in light of the fact that\n", - "in Python," + "in Python:" ] }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 46, "id": "b1dc37fd", "metadata": {}, "outputs": [ @@ -1518,7 +1502,7 @@ "1.0" ] }, - "execution_count": 47, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -1532,12 +1516,12 @@ "id": "a06699af", "metadata": {}, "source": [ - "In checking whether these probabilities sum to one, `pygambit` first converts each of the probabilities to a `Decimal` representation, via the following method" + "In checking whether these probabilities sum to one, `pygambit` first converts each of the probabilities to a `Decimal` representation, via the following method:" ] }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 47, "id": "dc1edea2", "metadata": {}, "outputs": [ @@ -1547,7 +1531,7 @@ "Decimal('0.3333333333333333')" ] }, - "execution_count": 48, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } @@ -1561,12 +1545,12 @@ "id": "4bfff415", "metadata": {}, "source": [ - "and the sum-to-one check then fails because" + "...and the sum-to-one check then fails because:" ] }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 48, "id": "1edd90d6", "metadata": {}, "outputs": [ @@ -1576,7 +1560,7 @@ "Decimal('0.9999999999999999')" ] }, - "execution_count": 49, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" } @@ -1604,15 +1588,13 @@ "source": [ "#### References\n", "\n", - "Myerson, Roger B. (1991) *Game Theory: Analysis of Conflict*. Cambridge: Harvard University Press.\n", - "\n", "Reiley, David H., Michael B. Urbancic and Mark Walker. (2008) \"Stripped-down poker: A classroom game with signaling and bluffing.\" *The Journal of Economic Education* 39(4): 323-341." ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "gambitvenv313", "language": "python", "name": "python3" }, diff --git a/tests/test_behav.py b/tests/test_behav.py index dbf096167..638a8baf8 100644 --- a/tests/test_behav.py +++ b/tests/test_behav.py @@ -49,10 +49,10 @@ def test_payoff_reference(game: gbt.Game, player_idx: int, payoff: typing.Union[ (games.create_mixed_behav_game_efg(), "Player 1", "3", True), (games.create_mixed_behav_game_efg(), "Player 2", "3", True), (games.create_mixed_behav_game_efg(), "Player 3", "13/4", True), - (games.create_stripped_down_poker_efg(), "Fred", -0.25, False), - (games.create_stripped_down_poker_efg(), "Alice", 0.25, False), - (games.create_stripped_down_poker_efg(), "Fred", "-1/4", True), - (games.create_stripped_down_poker_efg(), "Alice", "1/4", True), + (games.create_stripped_down_poker_efg(), "Alice", -0.25, False), + (games.create_stripped_down_poker_efg(), "Bob", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice", "-1/4", True), + (games.create_stripped_down_poker_efg(), "Bob", "1/4", True), ] ) def test_payoff_by_label_reference(game: gbt.Game, label: str, payoff: typing.Union[str, float], @@ -85,12 +85,12 @@ def test_is_defined_at(game: gbt.Game, rational_flag: bool): (games.create_mixed_behav_game_efg(), "Infoset 1:1", True), (games.create_mixed_behav_game_efg(), "Infoset 2:1", True), (games.create_mixed_behav_game_efg(), "Infoset 3:1", True), - (games.create_stripped_down_poker_efg(), "(1,1)", False), - (games.create_stripped_down_poker_efg(), "(1,2)", False), - (games.create_stripped_down_poker_efg(), "(2,1)", False), - (games.create_stripped_down_poker_efg(), "(1,1)", True), - (games.create_stripped_down_poker_efg(), "(1,2)", True), - (games.create_stripped_down_poker_efg(), "(2,1)", True), + (games.create_stripped_down_poker_efg(), "Alice has King", False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", False), + (games.create_stripped_down_poker_efg(), "Bob's response", False), + (games.create_stripped_down_poker_efg(), "Alice has King", True), + (games.create_stripped_down_poker_efg(), "Alice has Queen", True), + (games.create_stripped_down_poker_efg(), "Bob's response", True), ] ) def test_is_defined_at_by_label(game: gbt.Game, label: str, rational_flag: bool): @@ -212,14 +212,14 @@ def test_profile_indexing_by_invalid_infoset_label(rational_flag: bool): (games.create_mixed_behav_game_efg(), "Infoset 1:1", "D1", 0.5, False), (games.create_mixed_behav_game_efg(), "Infoset 1:1", "U1", "1/2", True), (games.create_mixed_behav_game_efg(), "Infoset 1:1", "D1", "1/2", True), - (games.create_stripped_down_poker_efg(), "(1,1)", "Bet", 0.5, False), - (games.create_stripped_down_poker_efg(), "(1,1)", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "(1,2)", "Bet", 0.5, False), - (games.create_stripped_down_poker_efg(), "(1,2)", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "(2,1)", "Call", 0.5, False), - (games.create_stripped_down_poker_efg(), "(2,1)", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "(2,1)", "Call", "1/2", True), - (games.create_stripped_down_poker_efg(), "(2,1)", "Fold", "1/2", True), + (games.create_stripped_down_poker_efg(), "Alice has King", "Bet", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has King", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", "Bet", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob's response", "Call", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob's response", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob's response", "Call", "1/2", True), + (games.create_stripped_down_poker_efg(), "Bob's response", "Fold", "1/2", True), ] ) def test_profile_indexing_by_infoset_and_action_labels_reference(game: gbt.Game, @@ -241,14 +241,14 @@ def test_profile_indexing_by_infoset_and_action_labels_reference(game: gbt.Game, (games.create_mixed_behav_game_efg(), "Player 1", "Infoset 1:1", "D1", 0.5, False), (games.create_mixed_behav_game_efg(), "Player 1", "Infoset 1:1", "U1", "1/2", True), (games.create_mixed_behav_game_efg(), "Player 1", "Infoset 1:1", "D1", "1/2", True), - (games.create_stripped_down_poker_efg(), "Fred", "(1,1)", "Bet", 0.5, False), - (games.create_stripped_down_poker_efg(), "Fred", "(1,1)", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "Fred", "(1,2)", "Bet", 0.5, False), - (games.create_stripped_down_poker_efg(), "Fred", "(1,2)", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice", "(2,1)", "Call", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice", "(2,1)", "Fold", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice", "(2,1)", "Call", "1/2", True), - (games.create_stripped_down_poker_efg(), "Alice", "(2,1)", "Fold", "1/2", True), + (games.create_stripped_down_poker_efg(), "Alice", "Alice has King", "Bet", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice", "Alice has King", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice", "Alice has Queen", "Bet", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice", "Alice has Queen", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Call", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Fold", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Call", "1/2", True), + (games.create_stripped_down_poker_efg(), "Bob", "Bob's response", "Fold", "1/2", True), ] ) def test_profile_indexing_by_player_infoset_action_labels_reference(game: gbt.Game, @@ -271,8 +271,8 @@ def test_profile_indexing_by_player_infoset_action_labels_reference(game: gbt.Ga (games.create_mixed_behav_game_efg(), "1:1", "U2", False), (games.create_mixed_behav_game_efg(), "1:1", "U4", True), # U4 isn't in the game (games.create_mixed_behav_game_efg(), "1:1", "U4", False), - (games.create_stripped_down_poker_efg(), "(1,1)", "MEET", True), # MEET at different iset - (games.create_stripped_down_poker_efg(), "(1,1)", "MEET", False), + (games.create_stripped_down_poker_efg(), "Alice has King", "MEET", True), + (games.create_stripped_down_poker_efg(), "Alice has King", "MEET", False), ] ) def test_profile_indexing_by_invalid_infoset_or_action_label(game: gbt.Game, infoset_label: str, @@ -316,12 +316,12 @@ def test_profile_indexing_by_player_and_infoset_idx_reference(game: gbt.Game, (games.create_mixed_behav_game_efg(), 0, "Infoset 1:1", ["1/2", "1/2"], True), (games.create_mixed_behav_game_efg(), 1, "Infoset 2:1", ["1/2", "1/2"], True), (games.create_mixed_behav_game_efg(), 2, "Infoset 3:1", ["1/2", "1/2"], True), - (games.create_stripped_down_poker_efg(), 0, "(1,1)", [0.5, 0.5], False), - (games.create_stripped_down_poker_efg(), 0, "(1,2)", [0.5, 0.5], False), - (games.create_stripped_down_poker_efg(), 1, "(2,1)", [0.5, 0.5], False), - (games.create_stripped_down_poker_efg(), 0, "(1,1)", ["1/2", "1/2"], True), - (games.create_stripped_down_poker_efg(), 0, "(1,2)", ["1/2", "1/2"], True), - (games.create_stripped_down_poker_efg(), 1, "(2,1)", ["1/2", "1/2"], True), + (games.create_stripped_down_poker_efg(), 0, "Alice has King", [0.5, 0.5], False), + (games.create_stripped_down_poker_efg(), 0, "Alice has Queen", [0.5, 0.5], False), + (games.create_stripped_down_poker_efg(), 1, "Bob's response", [0.5, 0.5], False), + (games.create_stripped_down_poker_efg(), 0, "Alice has King", ["1/2", "1/2"], True), + (games.create_stripped_down_poker_efg(), 0, "Alice has Queen", ["1/2", "1/2"], True), + (games.create_stripped_down_poker_efg(), 1, "Bob's response", ["1/2", "1/2"], True), ] ) def test_profile_indexing_by_player_idx_infoset_label_reference(game: gbt.Game, player_idx: int, @@ -400,11 +400,11 @@ def test_profile_indexing_by_player_idx_reference(game: gbt.Game, player_idx: in (games.create_mixed_behav_game_efg(), "Player 1", [["1/2", "1/2"]], True), (games.create_mixed_behav_game_efg(), "Player 2", [["1/2", "1/2"]], True), (games.create_mixed_behav_game_efg(), "Player 3", [["1/2", "1/2"]], True), - (games.create_stripped_down_poker_efg(), "Fred", [[0.5, 0.5], [0.5, 0.5]], False), - (games.create_stripped_down_poker_efg(), "Alice", [[0.5, 0.5]], False), - (games.create_stripped_down_poker_efg(), "Fred", [["1/2", "1/2"], ["1/2", "1/2"]], + (games.create_stripped_down_poker_efg(), "Alice", [[0.5, 0.5], [0.5, 0.5]], False), + (games.create_stripped_down_poker_efg(), "Bob", [[0.5, 0.5]], False), + (games.create_stripped_down_poker_efg(), "Alice", [["1/2", "1/2"], ["1/2", "1/2"]], True), - (games.create_stripped_down_poker_efg(), "Alice", [["1/2", "1/2"]], True), + (games.create_stripped_down_poker_efg(), "Bob", [["1/2", "1/2"]], True), ] ) def test_profile_indexing_by_player_label_reference(game: gbt.Game, player_label: str, @@ -513,12 +513,12 @@ def test_set_probabilities_infoset(game: gbt.Game, player_idx: int, infoset_idx: (games.create_mixed_behav_game_efg(), "Infoset 1:1", ["7/9", "2/9"], True), (games.create_mixed_behav_game_efg(), "Infoset 2:1", ["4/13", "9/13"], True), (games.create_mixed_behav_game_efg(), "Infoset 3:1", ["1/98", "97/98"], True), - (games.create_stripped_down_poker_efg(), "(1,1)", [0.1, 0.9], False), - (games.create_stripped_down_poker_efg(), "(1,2)", [0.2, 0.8], False), - (games.create_stripped_down_poker_efg(), "(2,1)", [0.3, 0.7], False), - (games.create_stripped_down_poker_efg(), "(1,1)", ["1/10", "9/10"], True), - (games.create_stripped_down_poker_efg(), "(1,2)", ["2/10", "8/10"], True), - (games.create_stripped_down_poker_efg(), "(2,1)", ["3/10", "7/10"], True), + (games.create_stripped_down_poker_efg(), "Alice has King", [0.1, 0.9], False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", [0.2, 0.8], False), + (games.create_stripped_down_poker_efg(), "Bob's response", [0.3, 0.7], False), + (games.create_stripped_down_poker_efg(), "Alice has King", ["1/10", "9/10"], True), + (games.create_stripped_down_poker_efg(), "Alice has Queen", ["2/10", "8/10"], True), + (games.create_stripped_down_poker_efg(), "Bob's response", ["3/10", "7/10"], True), ] ) def test_set_probabilities_infoset_by_label(game: gbt.Game, infoset_label: str, probs: list, @@ -562,11 +562,11 @@ def test_set_probabilities_player(game: gbt.Game, player_idx: int, behav_data: l (games.create_mixed_behav_game_efg(), "Player 1", [["7/9", "2/9"]], True), (games.create_mixed_behav_game_efg(), "Player 2", [["4/13", "9/13"]], True), (games.create_mixed_behav_game_efg(), "Player 3", [["1/98", "97/98"]], True), - (games.create_stripped_down_poker_efg(), "Fred", [[0.1, 0.9], [0.5, 0.5]], False), - (games.create_stripped_down_poker_efg(), "Alice", [[0.6, 0.4]], False), - (games.create_stripped_down_poker_efg(), "Fred", [["1/3", "2/3"], ["1/2", "1/2"]], + (games.create_stripped_down_poker_efg(), "Alice", [[0.1, 0.9], [0.5, 0.5]], False), + (games.create_stripped_down_poker_efg(), "Bob", [[0.6, 0.4]], False), + (games.create_stripped_down_poker_efg(), "Alice", [["1/3", "2/3"], ["1/2", "1/2"]], True), - (games.create_stripped_down_poker_efg(), "Alice", [["2/3", "1/3"]], True), + (games.create_stripped_down_poker_efg(), "Bob", [["2/3", "1/3"]], True), ] ) def test_set_probabilities_player_by_label(game: gbt.Game, player_label: str, behav_data: list, @@ -672,12 +672,12 @@ def test_infoset_prob_reference(game: gbt.Game, player_idx: int, infoset_idx: in (games.create_mixed_behav_game_efg(), "Infoset 1:1", "1", True), (games.create_mixed_behav_game_efg(), "Infoset 2:1", "1", True), (games.create_mixed_behav_game_efg(), "Infoset 3:1", "1", True), - (games.create_stripped_down_poker_efg(), "(1,1)", 0.5, False), - (games.create_stripped_down_poker_efg(), "(1,2)", 0.5, False), - (games.create_stripped_down_poker_efg(), "(2,1)", 0.5, False), - (games.create_stripped_down_poker_efg(), "(1,1)", "1/2", True), - (games.create_stripped_down_poker_efg(), "(1,2)", "1/2", True), - (games.create_stripped_down_poker_efg(), "(2,1)", "1/2", True), + (games.create_stripped_down_poker_efg(), "Alice has King", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob's response", 0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has King", "1/2", True), + (games.create_stripped_down_poker_efg(), "Alice has Queen", "1/2", True), + (games.create_stripped_down_poker_efg(), "Bob's response", "1/2", True), ] ) def test_infoset_prob_by_label_reference(game: gbt.Game, label: str, @@ -717,12 +717,12 @@ def test_infoset_payoff_reference(game: gbt.Game, player_idx: int, infoset_idx: (games.create_mixed_behav_game_efg(), "Infoset 1:1", "3", True), (games.create_mixed_behav_game_efg(), "Infoset 2:1", "3", True), (games.create_mixed_behav_game_efg(), "Infoset 3:1", "13/4", True), - (games.create_stripped_down_poker_efg(), "(1,1)", 0.25, False), - (games.create_stripped_down_poker_efg(), "(1,2)", -0.75, False), - (games.create_stripped_down_poker_efg(), "(2,1)", -0.5, False), - (games.create_stripped_down_poker_efg(), "(1,1)", "1/4", True), - (games.create_stripped_down_poker_efg(), "(1,2)", "-3/4", True), - (games.create_stripped_down_poker_efg(), "(2,1)", "-1/2", True), + (games.create_stripped_down_poker_efg(), "Alice has King", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice has Queen", -0.75, False), + (games.create_stripped_down_poker_efg(), "Bob's response", -0.5, False), + (games.create_stripped_down_poker_efg(), "Alice has King", "1/4", True), + (games.create_stripped_down_poker_efg(), "Alice has Queen", "-3/4", True), + (games.create_stripped_down_poker_efg(), "Bob's response", "-1/2", True), ] ) def test_infoset_payoff_by_label_reference(game: gbt.Game, label: str, diff --git a/tests/test_games/stripped_down_poker.efg b/tests/test_games/stripped_down_poker.efg index 9c7eb66ec..8a21af1ff 100644 --- a/tests/test_games/stripped_down_poker.efg +++ b/tests/test_games/stripped_down_poker.efg @@ -1,14 +1,14 @@ -EFG 2 R "A simple Poker game" { "Fred" "Alice" } +EFG 2 R "A simple Poker game" { "Alice" "Bob" } "Stripped-Down Poker: a simple game of one-card poker from Reiley et al (2008)." -c "" 1 "(0,1)" { "King" 1/2 "Queen" 1/2 } 0 -p "" 1 1 "(1,1)" { "Bet" "Fold" } 0 -p "" 2 1 "(2,1)" { "Call" "Fold" } 0 +c "" 1 "Deal" { "King" 1/2 "Queen" 1/2 } 0 +p "" 1 1 "Alice has King" { "Bet" "Fold" } 0 +p "" 2 1 "Bob's response" { "Call" "Fold" } 0 t "" 1 "Win Big" { 2, -2 } t "" 2 "Win" { 1, -1 } t "" 4 "Lose" { -1, 1 } -p "" 1 2 "(1,2)" { "Bet" "Fold" } 0 -p "" 2 1 "(2,1)" { "Call" "Fold" } 0 +p "" 1 2 "Alice has Queen" { "Bet" "Fold" } 0 +p "" 2 1 "Bob's response" { "Call" "Fold" } 0 t "" 3 "Lose Big" { -2, 2 } t "" 2 "Win" { 1, -1 } t "" 4 "Lose" { -1, 1 } diff --git a/tests/test_mixed.py b/tests/test_mixed.py index b37763d74..82658bfd1 100644 --- a/tests/test_mixed.py +++ b/tests/test_mixed.py @@ -138,10 +138,10 @@ def test_set_and_get_probability_by_strategy_label(game: gbt.Game, strategy_labe (games.create_coord_4x4_nfg(), P1, True, ["1/4", 0, 0, "3/4"]), ############################################################################## # stripped-down poker efg - (games.create_stripped_down_poker_efg(), "Fred", False, [0.25, 0.75, 0, 0]), - (games.create_stripped_down_poker_efg(), "Alice", False, [1, 0]), - (games.create_stripped_down_poker_efg(), "Fred", True, ["1/4", "3/4", 0, 0]), - (games.create_stripped_down_poker_efg(), "Alice", True, [1, 0]), + (games.create_stripped_down_poker_efg(), "Alice", False, [0.25, 0.75, 0, 0]), + (games.create_stripped_down_poker_efg(), "Bob", False, [1, 0]), + (games.create_stripped_down_poker_efg(), "Alice", True, ["1/4", "3/4", 0, 0]), + (games.create_stripped_down_poker_efg(), "Bob", True, [1, 0]), ], ) def test_set_and_get_probabilities_by_player_label(game: gbt.Game, player_label: str, @@ -158,19 +158,19 @@ def test_set_and_get_probabilities_by_player_label(game: gbt.Game, player_label: ############################################################################## # stripped-down poker efg # Player 1 - (games.create_stripped_down_poker_efg(), "Fred", "11", 0.25, False), - (games.create_stripped_down_poker_efg(), "Fred", "12", 0.25, False), - (games.create_stripped_down_poker_efg(), "Fred", "21", 0.25, False), - (games.create_stripped_down_poker_efg(), "Fred", "22", 0.25, False), - (games.create_stripped_down_poker_efg(), "Fred", "11", "1/4", True), - (games.create_stripped_down_poker_efg(), "Fred", "12", "1/4", True), - (games.create_stripped_down_poker_efg(), "Fred", "21", "1/4", True), - (games.create_stripped_down_poker_efg(), "Fred", "22", "1/4", True), + (games.create_stripped_down_poker_efg(), "Alice", "11", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice", "12", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice", "21", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice", "22", 0.25, False), + (games.create_stripped_down_poker_efg(), "Alice", "11", "1/4", True), + (games.create_stripped_down_poker_efg(), "Alice", "12", "1/4", True), + (games.create_stripped_down_poker_efg(), "Alice", "21", "1/4", True), + (games.create_stripped_down_poker_efg(), "Alice", "22", "1/4", True), # Player 2 - (games.create_stripped_down_poker_efg(), "Alice", "1", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice", "2", 0.5, False), - (games.create_stripped_down_poker_efg(), "Alice", "1", "1/2", True), - (games.create_stripped_down_poker_efg(), "Alice", "2", "1/2", True), + (games.create_stripped_down_poker_efg(), "Bob", "1", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob", "2", 0.5, False), + (games.create_stripped_down_poker_efg(), "Bob", "1", "1/2", True), + (games.create_stripped_down_poker_efg(), "Bob", "2", "1/2", True), ############################################################################## # coordination 4x4 nfg outcome version with strategy labels (games.create_coord_4x4_nfg(outcome_version=True), P1, "1-1", "1/4", True), @@ -191,12 +191,12 @@ def test_profile_indexing_by_player_and_strategy_label_reference(game: gbt.Game, [ ############################################################################## # stripped-down poker efg - (games.create_stripped_down_poker_efg(), "Alice", "11", True), - (games.create_stripped_down_poker_efg(), "Alice", "11", False), - (games.create_stripped_down_poker_efg(), "Fred", "1", True), - (games.create_stripped_down_poker_efg(), "Fred", "1", False), - (games.create_stripped_down_poker_efg(), "Fred", "2", True), - (games.create_stripped_down_poker_efg(), "Fred", "2", False), + (games.create_stripped_down_poker_efg(), "Bob", "11", True), + (games.create_stripped_down_poker_efg(), "Bob", "11", False), + (games.create_stripped_down_poker_efg(), "Alice", "1", True), + (games.create_stripped_down_poker_efg(), "Alice", "1", False), + (games.create_stripped_down_poker_efg(), "Alice", "2", True), + (games.create_stripped_down_poker_efg(), "Alice", "2", False), ############################################################################## # coordination 4x4 nfg outcome version with strategy labels (games.create_coord_4x4_nfg(outcome_version=True), P1, "2-1", True), @@ -295,10 +295,10 @@ def test_profile_indexing_by_strategy_label_reference(game: gbt.Game, strategy_l (games.create_mixed_behav_game_efg(), P3, ["1/2", "1/2"], True), ############################################################################ # stripped-down poker efg - (games.create_stripped_down_poker_efg(), "Fred", [0.25, 0.25, 0.25, 0.25], False), - (games.create_stripped_down_poker_efg(), "Alice", [0.5, 0.5], False), - (games.create_stripped_down_poker_efg(), "Fred", ["1/4", "1/4", "1/4", "1/4"], True), - (games.create_stripped_down_poker_efg(), "Alice", ["1/2", "1/2"], True), + (games.create_stripped_down_poker_efg(), "Alice", [0.25, 0.25, 0.25, 0.25], False), + (games.create_stripped_down_poker_efg(), "Bob", [0.5, 0.5], False), + (games.create_stripped_down_poker_efg(), "Alice", ["1/4", "1/4", "1/4", "1/4"], True), + (games.create_stripped_down_poker_efg(), "Bob", ["1/2", "1/2"], True), ############################################################################ # coordination 4x4 nfg (games.create_coord_4x4_nfg(), P1, [0.25, 0.25, 0.25, 0.25], False), @@ -338,24 +338,24 @@ def test_profile_indexing_by_player_label_reference(game: gbt.Game, player_label (games.create_coord_4x4_nfg(), True, [[1, 0, 0, 0], [0, 1, 0, 0]], P2, 0), ######################################################################### # stripped-down poker efg - (games.create_stripped_down_poker_efg(), False, None, "Fred", -0.25), - (games.create_stripped_down_poker_efg(), False, None, "Alice", 0.25), - (games.create_stripped_down_poker_efg(), True, None, "Fred", "-1/4"), - (games.create_stripped_down_poker_efg(), True, None, "Alice", "1/4"), - # Raise/Raise for player 1 - (games.create_stripped_down_poker_efg(), False, [[1, 0, 0, 0], [1, 0]], "Fred", 0), - (games.create_stripped_down_poker_efg(), False, [[1, 0, 0, 0], [1, 0]], "Alice", 0), - (games.create_stripped_down_poker_efg(), True, [[1, 0, 0, 0], [1, 0]], "Fred", 0), - (games.create_stripped_down_poker_efg(), True, [[1, 0, 0, 0], [1, 0]], "Alice", 0), + (games.create_stripped_down_poker_efg(), False, None, "Alice", -0.25), + (games.create_stripped_down_poker_efg(), False, None, "Bob", 0.25), + (games.create_stripped_down_poker_efg(), True, None, "Alice", "-1/4"), + (games.create_stripped_down_poker_efg(), True, None, "Bob", "1/4"), + # Bet/Call + (games.create_stripped_down_poker_efg(), False, [[1, 0, 0, 0], [1, 0]], "Alice", 0), + (games.create_stripped_down_poker_efg(), False, [[1, 0, 0, 0], [1, 0]], "Bob", 0), + (games.create_stripped_down_poker_efg(), True, [[1, 0, 0, 0], [1, 0]], "Alice", 0), + (games.create_stripped_down_poker_efg(), True, [[1, 0, 0, 0], [1, 0]], "Bob", 0), # Fold/Fold for player 1 (player 2's strategy is payoff-irrelevant) - (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [1, 0]], "Fred", -1), - (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [1, 0]], "Alice", 1), - (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], [1, 0]], "Fred", -1), - (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], [1, 0]], "Alice", 1), - (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [0.5, 0.5]], "Fred", -1), - (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [0.5, 0.5]], "Alice", 1), - (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], ["1/2", "1/2"]], "Fred", -1), - (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], ["1/2", "1/2"]], "Alice", 1), + (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [1, 0]], "Alice", -1), + (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [1, 0]], "Bob", 1), + (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], [1, 0]], "Alice", -1), + (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], [1, 0]], "Bob", 1), + (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [0.5, 0.5]], "Alice", -1), + (games.create_stripped_down_poker_efg(), False, [[0, 0, 0, 1], [0.5, 0.5]], "Bob", 1), + (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], ["1/2", "1/2"]], "Alice", -1), + (games.create_stripped_down_poker_efg(), True, [[0, 0, 0, 1], ["1/2", "1/2"]], "Bob", 1), ######################################################################### (games.create_mixed_behav_game_efg(), False, None, P1, 3.0), (games.create_mixed_behav_game_efg(), False, None, P2, 3.0),