buttonmen-dev / buttonmen

Buttonmen - an online dice game
Other
16 stars 24 forks source link

internal error caused by morphing attacks on twin swing dice #1453

Closed cgolubi1 closed 9 years ago

cgolubi1 commented 9 years ago

Well... rats. The situation seems to be that a morphing die can capture a twin swing die and become a morphing twin swing die itself, but once that's happened, the next move (regardless of whether or not it involves the morphing twin swing die) renders the game unloadable.

cgolubi1 commented 9 years ago

It'll take me a little time to write up a usable test for this --- RandomAI found it for me, and RandomAI's output format isn't very human-edit-friendly.

Anyway, in this screencap, responder003 has just captured responder004's s(R,R)?. The game is still loadable at this point: after_morphing_before_breakage

However, the next move RandomAI tried was:

responder004 performed Shadow attack using [vs(15):10] against [Hwz(12):12]; Defender Hwz(12) recipe changed to Hwzv(12), was captured; Attacker vs(15) rerolled 10 => 12.

At that point, any further attempts to load the game fail, and the apache error log gets:

[Tue Dec 02 13:12:40 2014] [error] [client 10.0.2.2] Caught exception in BMInterface::load_game: Invalid die value: 18 is not between  and  for die m(R,R), referer: http://localhost:8080/ui/game.html?game=173

TheFool's twin die is also shadow and mood, so i went ahead and replicated this with Skomp vs. The James Beast, and was able to do so easily. So the issue is simply morphing vs. twin swing.

I don't know whether the regression was introduced by the fix for zero-sided swing dice or not. I'd be suspicious of that, though.

I am pretty sure the regression post-dates the last push to prod, though, because on prod, morphing dice which capture swing dice don't become swing, so this issue can't exist in this form on prod. Hence the prod-blocker label, and apologies.

cgolubi1 commented 9 years ago

Until i have time to make a test, here's RandomAI's transcript of the game. You can plug this into responderTest, and it does run, but it's brittle and is quite a pain to read or edit. However, if you're in a hurry to get a game state you can look at in the DB, plugging the function into responderTest will accomplish that.

    public function test_interface_game_00171() {

        // responder003 is the POV player, so if you need to fake
        // login as a different player e.g. to submit an attack, always
        // return to responder003 as soon as you've done so
        $this->game_number = 171;
        $_SESSION = $this->mock_test_user_login('responder003');

        $gameId = $this->verify_api_createGame(
            array(409, 5, 1, 6, 4, 5, 10, 7, 10),
            'responder003', 'responder004', 'kleric', '__random', 3
        );

        $expData = $this->squash_game_data_timestamps(json_decode('{"previousGameId": null, "gameChatEditable": false, "gameId": 173, "description": "", "timestamp": 1417525085, "roundNumber": 1, "playerWithInitiativeIdx": null, "gameChatLog": [], "gameState": "SPECIFY_DICE", "validAttackTypeArray": [], "activePlayerIdx": null, "gameSkillsInfo": {"Slow": {"code": "w", "interacts": [], "description": "These dice are not counted for the purposes of initiative."}, "Mood": {"code": "?", "interacts": {"Ornery": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "These are a subcategory of Swing dice, whose size changes randomly when rerolled. At the very start of the game (and again after any round they lose, just as with normal Swing dice) the player sets the initial size of Mood Swing dice, but from then on whenever they are rolled their size is set randomly to that of a \"real-world\" die (i.e. 1, 2, 4, 6, 8, 10, 12, 20, or 30 sides) within the range allowable for that Swing type."}, "Morphing": {"code": "m", "interacts": [], "description": "When a Morphing Die is used in any attack, it changes size, becoming the same size as the die that was captured. It is then re-rolled. Morphing Dice change size every time they capture another die. If a Morphing die is captured, its scoring value is based on its size at the time of capture; likewise, if it is not captured during a round, its scoring value is based on its size at the end of the round"}, "Stealth": {"code": "d", "interacts": [], "description": "These dice cannot perform any type of attack other than Multi-die Skill Attacks, meaning two or more dice participating in a Skill Attack. In addition, Stealth Dice cannot be captured by any attack other than a Multi-die Skill Attack."}, "Weak": {"code": "h", "interacts": [], "description": "When a Weak Die rerolls for any reason, it first shrinks from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Ornery": {"code": "o", "interacts": {"Mood": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "Ornery dice reroll every time the player makes any attack - whether the Ornery dice participated in it or not. The only time they don\'t reroll is if the player passes, making no attack whatsoever."}, "Value": {"code": "v", "interacts": [], "description": "These dice are not scored like normal dice. Instead, a Value Die is scored as if the number of sides it has is equal to the value that it is currently showing. If a Value Die is ever part of an attack, all dice that are captured become Value Dice (i.e. They are scored by the current value they are showing when they are captured, not by their size)."}, "Chance": {"code": "c", "interacts": [], "description": "If you do not have the initiative at the start of a round you may re-roll one of your Chance Dice. If this results in you gaining the initiative, your opponent may re-roll one of their Chance Dice. This can continue with each player re-rolling Chance Dice, even re-rolling the same die, until one person fails to gain the initiative or lets their opponent go first. Re-rolling Chance Dice is not only a way to gain the initiative; it can also be useful in protecting your larger dice, or otherwise improving your starting roll. Unlike Focus Dice, Chance Dice can be immediately re-used in an attack even if you do gain the initiative with them."}, "Mighty": {"code": "H", "interacts": [], "description": "When a Mighty Die rerolls for any reason, it first grows from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Queer": {"code": "q", "interacts": [], "description": "These dice behave like normal dice when they show an even number, and like Shadow Dice when they show an odd number."}, "Shadow": {"code": "s", "interacts": [], "description": "These dice are normal in all respects, except that they cannot make Power Attacks. Instead, they make inverted Power Attacks, called \"Shadow Attacks.\" To make a Shadow Attack, Use one of your Shadow Dice to capture one of your opponent\'s dice. The number showing on the die you capture must be greater than or equal to the number showing on your die, but within its range. For example, a shadow 10-sided die showing a 2 can capture a die showing any number from 2 to 10."}, "Speed": {"code": "z", "interacts": [], "description": "These dice can also make Speed Attacks, which are the equivalent of inverted Skill Attacks. In a Speed Attack, one Speed Die can capture any number of dice which add up exactly to its value."}}, "gameActionLog": [], "maxWins": 3, "playerDataArray": [{"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#dd99dd", "roundScore": null, "waitingOnAction": true, "playerId": 4, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "BMdefaultRound.png", "recipe": "Hwz(6) dc(6) m(7) oh(10) (X)?", "name": "kleric"}, "capturedDieArray": [], "activeDieArray": [{"description": "Mighty Slow Speed 6-sided die", "skills": ["Mighty", "Slow", "Speed"], "recipe": "Hwz(6)", "value": null, "properties": [], "sides": 6}, {"description": "Stealth Chance 6-sided die", "skills": ["Stealth", "Chance"], "recipe": "dc(6)", "value": null, "properties": [], "sides": 6}, {"description": "Morphing 7-sided die", "skills": ["Morphing"], "recipe": "m(7)", "value": null, "properties": [], "sides": 7}, {"description": "Ornery Weak 10-sided die", "skills": ["Ornery", "Weak"], "recipe": "oh(10)", "value": null, "properties": [], "sides": 10}, {"description": "X Mood Swing Die", "skills": ["Mood"], "recipe": "(X)?", "value": null, "properties": [], "sides": null}], "canStillWin": null, "swingRequestArray": {"X": [4, 20]}, "hasDismissedGame": false, "sideScore": null, "playerName": "responder003", "prevSwingValueArray": [], "lastActionTime": 1417525085}, {"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#ddffdd", "roundScore": null, "waitingOnAction": true, "playerId": 5, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "thefool.png", "recipe": "v(5) v(10) vq(10) vs(15) s(R,R)?", "name": "TheFool"}, "capturedDieArray": [], "activeDieArray": [{"description": "Value 5-sided die", "skills": ["Value"], "recipe": "v(5)", "value": null, "properties": ["ValueRelevantToScore"], "sides": 5}, {"description": "Value 10-sided die", "skills": ["Value"], "recipe": "v(10)", "value": null, "properties": ["ValueRelevantToScore"], "sides": 10}, {"description": "Value Queer 10-sided die", "skills": ["Value", "Queer"], "recipe": "vq(10)", "value": null, "properties": ["ValueRelevantToScore"], "sides": 10}, {"description": "Value Shadow 15-sided die", "skills": ["Value", "Shadow"], "recipe": "vs(15)", "value": null, "properties": ["ValueRelevantToScore"], "sides": 15}, {"description": "Shadow Twin R Mood Swing Die", "skills": ["Shadow", "Mood"], "recipe": "s(R,R)?", "value": null, "properties": [], "sides": null}], "canStillWin": null, "swingRequestArray": {"R": [2, 16]}, "hasDismissedGame": false, "sideScore": null, "playerName": "responder004", "prevSwingValueArray": [], "lastActionTime": 1417525085}], "currentPlayerIdx": 0}', TRUE));
        $expData['gameId'] = $gameId;
        $expData['playerDataArray'][0]['playerId'] = $this->user_ids['responder003'];
        $expData['playerDataArray'][1]['playerId'] = $this->user_ids['responder004'];
        $retval = $this->verify_api_loadGameData($expData, $gameId, 10);

        $this->verify_api_submitDieValues(
            array(1),
            $gameId, 1, array('X' => 19), NULL);

        $expData = $this->squash_game_data_timestamps(json_decode('{"previousGameId": null, "gameChatEditable": false, "gameId": 173, "description": "", "timestamp": 1417525085, "roundNumber": 1, "playerWithInitiativeIdx": null, "gameChatLog": [], "gameState": "SPECIFY_DICE", "validAttackTypeArray": [], "activePlayerIdx": null, "gameSkillsInfo": {"Slow": {"code": "w", "interacts": [], "description": "These dice are not counted for the purposes of initiative."}, "Mood": {"code": "?", "interacts": {"Ornery": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "These are a subcategory of Swing dice, whose size changes randomly when rerolled. At the very start of the game (and again after any round they lose, just as with normal Swing dice) the player sets the initial size of Mood Swing dice, but from then on whenever they are rolled their size is set randomly to that of a \"real-world\" die (i.e. 1, 2, 4, 6, 8, 10, 12, 20, or 30 sides) within the range allowable for that Swing type."}, "Morphing": {"code": "m", "interacts": [], "description": "When a Morphing Die is used in any attack, it changes size, becoming the same size as the die that was captured. It is then re-rolled. Morphing Dice change size every time they capture another die. If a Morphing die is captured, its scoring value is based on its size at the time of capture; likewise, if it is not captured during a round, its scoring value is based on its size at the end of the round"}, "Stealth": {"code": "d", "interacts": [], "description": "These dice cannot perform any type of attack other than Multi-die Skill Attacks, meaning two or more dice participating in a Skill Attack. In addition, Stealth Dice cannot be captured by any attack other than a Multi-die Skill Attack."}, "Weak": {"code": "h", "interacts": [], "description": "When a Weak Die rerolls for any reason, it first shrinks from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Ornery": {"code": "o", "interacts": {"Mood": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "Ornery dice reroll every time the player makes any attack - whether the Ornery dice participated in it or not. The only time they don\'t reroll is if the player passes, making no attack whatsoever."}, "Value": {"code": "v", "interacts": [], "description": "These dice are not scored like normal dice. Instead, a Value Die is scored as if the number of sides it has is equal to the value that it is currently showing. If a Value Die is ever part of an attack, all dice that are captured become Value Dice (i.e. They are scored by the current value they are showing when they are captured, not by their size)."}, "Chance": {"code": "c", "interacts": [], "description": "If you do not have the initiative at the start of a round you may re-roll one of your Chance Dice. If this results in you gaining the initiative, your opponent may re-roll one of their Chance Dice. This can continue with each player re-rolling Chance Dice, even re-rolling the same die, until one person fails to gain the initiative or lets their opponent go first. Re-rolling Chance Dice is not only a way to gain the initiative; it can also be useful in protecting your larger dice, or otherwise improving your starting roll. Unlike Focus Dice, Chance Dice can be immediately re-used in an attack even if you do gain the initiative with them."}, "Mighty": {"code": "H", "interacts": [], "description": "When a Mighty Die rerolls for any reason, it first grows from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Queer": {"code": "q", "interacts": [], "description": "These dice behave like normal dice when they show an even number, and like Shadow Dice when they show an odd number."}, "Shadow": {"code": "s", "interacts": [], "description": "These dice are normal in all respects, except that they cannot make Power Attacks. Instead, they make inverted Power Attacks, called \"Shadow Attacks.\" To make a Shadow Attack, Use one of your Shadow Dice to capture one of your opponent\'s dice. The number showing on the die you capture must be greater than or equal to the number showing on your die, but within its range. For example, a shadow 10-sided die showing a 2 can capture a die showing any number from 2 to 10."}, "Speed": {"code": "z", "interacts": [], "description": "These dice can also make Speed Attacks, which are the equivalent of inverted Skill Attacks. In a Speed Attack, one Speed Die can capture any number of dice which add up exactly to its value."}}, "gameActionLog": [{"timestamp": 1417525085, "message": "responder003 set die sizes", "player": "responder003"}], "maxWins": 3, "playerDataArray": [{"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#dd99dd", "roundScore": null, "waitingOnAction": false, "playerId": 4, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "BMdefaultRound.png", "recipe": "Hwz(6) dc(6) m(7) oh(10) (X)?", "name": "kleric"}, "capturedDieArray": [], "activeDieArray": [{"description": "Mighty Slow Speed 6-sided die", "skills": ["Mighty", "Slow", "Speed"], "recipe": "Hwz(6)", "value": null, "properties": [], "sides": 6}, {"description": "Stealth Chance 6-sided die", "skills": ["Stealth", "Chance"], "recipe": "dc(6)", "value": null, "properties": [], "sides": 6}, {"description": "Morphing 7-sided die", "skills": ["Morphing"], "recipe": "m(7)", "value": null, "properties": [], "sides": 7}, {"description": "Ornery Weak 10-sided die", "skills": ["Ornery", "Weak"], "recipe": "oh(10)", "value": null, "properties": [], "sides": 10}, {"description": "X Mood Swing Die (with 19 sides)", "skills": ["Mood"], "recipe": "(X)?", "value": null, "properties": [], "sides": 19}], "canStillWin": null, "swingRequestArray": {"X": [4, 20]}, "hasDismissedGame": false, "sideScore": null, "playerName": "responder003", "prevSwingValueArray": [], "lastActionTime": 1417525085}, {"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#ddffdd", "roundScore": null, "waitingOnAction": true, "playerId": 5, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "thefool.png", "recipe": "v(5) v(10) vq(10) vs(15) s(R,R)?", "name": "TheFool"}, "capturedDieArray": [], "activeDieArray": [{"description": "Value 5-sided die", "skills": ["Value"], "recipe": "v(5)", "value": null, "properties": ["ValueRelevantToScore"], "sides": 5}, {"description": "Value 10-sided die", "skills": ["Value"], "recipe": "v(10)", "value": null, "properties": ["ValueRelevantToScore"], "sides": 10}, {"description": "Value Queer 10-sided die", "skills": ["Value", "Queer"], "recipe": "vq(10)", "value": null, "properties": ["ValueRelevantToScore"], "sides": 10}, {"description": "Value Shadow 15-sided die", "skills": ["Value", "Shadow"], "recipe": "vs(15)", "value": null, "properties": ["ValueRelevantToScore"], "sides": 15}, {"description": "Shadow Twin R Mood Swing Die", "skills": ["Shadow", "Mood"], "recipe": "s(R,R)?", "value": null, "properties": [], "sides": null}], "canStillWin": null, "swingRequestArray": {"R": [2, 16]}, "hasDismissedGame": false, "sideScore": null, "playerName": "responder004", "prevSwingValueArray": [], "lastActionTime": 1417525085}], "currentPlayerIdx": 0}', TRUE));
        $expData['gameId'] = $gameId;
        $expData['playerDataArray'][0]['playerId'] = $this->user_ids['responder003'];
        $expData['playerDataArray'][1]['playerId'] = $this->user_ids['responder004'];
        $retval = $this->verify_api_loadGameData($expData, $gameId, 10);

        $_SESSION = $this->mock_test_user_login('responder004');
        $this->verify_api_submitDieValues(
            array(11, 7),
            $gameId, 1, array('R' => 15), NULL);
        $_SESSION = $this->mock_test_user_login('responder003');

        $expData = $this->squash_game_data_timestamps(json_decode('{"previousGameId": null, "gameChatEditable": false, "gameId": 173, "description": "", "timestamp": 1417525086, "roundNumber": 1, "playerWithInitiativeIdx": 0, "gameChatLog": [], "gameState": "START_TURN", "validAttackTypeArray": ["Power", "Skill", "Speed"], "activePlayerIdx": 0, "gameSkillsInfo": {"Slow": {"code": "w", "interacts": [], "description": "These dice are not counted for the purposes of initiative."}, "Mood": {"code": "?", "interacts": {"Ornery": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "These are a subcategory of Swing dice, whose size changes randomly when rerolled. At the very start of the game (and again after any round they lose, just as with normal Swing dice) the player sets the initial size of Mood Swing dice, but from then on whenever they are rolled their size is set randomly to that of a \"real-world\" die (i.e. 1, 2, 4, 6, 8, 10, 12, 20, or 30 sides) within the range allowable for that Swing type."}, "Morphing": {"code": "m", "interacts": [], "description": "When a Morphing Die is used in any attack, it changes size, becoming the same size as the die that was captured. It is then re-rolled. Morphing Dice change size every time they capture another die. If a Morphing die is captured, its scoring value is based on its size at the time of capture; likewise, if it is not captured during a round, its scoring value is based on its size at the end of the round"}, "Stealth": {"code": "d", "interacts": [], "description": "These dice cannot perform any type of attack other than Multi-die Skill Attacks, meaning two or more dice participating in a Skill Attack. In addition, Stealth Dice cannot be captured by any attack other than a Multi-die Skill Attack."}, "Weak": {"code": "h", "interacts": [], "description": "When a Weak Die rerolls for any reason, it first shrinks from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Ornery": {"code": "o", "interacts": {"Mood": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "Ornery dice reroll every time the player makes any attack - whether the Ornery dice participated in it or not. The only time they don\'t reroll is if the player passes, making no attack whatsoever."}, "Value": {"code": "v", "interacts": [], "description": "These dice are not scored like normal dice. Instead, a Value Die is scored as if the number of sides it has is equal to the value that it is currently showing. If a Value Die is ever part of an attack, all dice that are captured become Value Dice (i.e. They are scored by the current value they are showing when they are captured, not by their size)."}, "Chance": {"code": "c", "interacts": [], "description": "If you do not have the initiative at the start of a round you may re-roll one of your Chance Dice. If this results in you gaining the initiative, your opponent may re-roll one of their Chance Dice. This can continue with each player re-rolling Chance Dice, even re-rolling the same die, until one person fails to gain the initiative or lets their opponent go first. Re-rolling Chance Dice is not only a way to gain the initiative; it can also be useful in protecting your larger dice, or otherwise improving your starting roll. Unlike Focus Dice, Chance Dice can be immediately re-used in an attack even if you do gain the initiative with them."}, "Mighty": {"code": "H", "interacts": [], "description": "When a Mighty Die rerolls for any reason, it first grows from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Queer": {"code": "q", "interacts": [], "description": "These dice behave like normal dice when they show an even number, and like Shadow Dice when they show an odd number."}, "Shadow": {"code": "s", "interacts": [], "description": "These dice are normal in all respects, except that they cannot make Power Attacks. Instead, they make inverted Power Attacks, called \"Shadow Attacks.\" To make a Shadow Attack, Use one of your Shadow Dice to capture one of your opponent\'s dice. The number showing on the die you capture must be greater than or equal to the number showing on your die, but within its range. For example, a shadow 10-sided die showing a 2 can capture a die showing any number from 2 to 10."}, "Speed": {"code": "z", "interacts": [], "description": "These dice can also make Speed Attacks, which are the equivalent of inverted Skill Attacks. In a Speed Attack, one Speed Die can capture any number of dice which add up exactly to its value."}}, "gameActionLog": [{"timestamp": 1417525086, "message": "responder003 won initiative for round 1. Initial die values: responder003 rolled [Hwz(6):5, dc(6):1, m(7):6, oh(10):4, (X=19)?:1], responder004 rolled [v(5):5, v(10):10, vq(10):7, vs(15):10, s(R=15,R=15)?:18]. responder003 has dice which are not counted for initiative due to die skills: [Hwz(6)].", "player": ""}, {"timestamp": 1417525086, "message": "responder004 set swing values: R=15", "player": "responder004"}, {"timestamp": 1417525085, "message": "responder003 set swing values: X=19", "player": "responder003"}], "maxWins": 3, "playerDataArray": [{"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#dd99dd", "roundScore": 24, "waitingOnAction": true, "playerId": 4, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "BMdefaultRound.png", "recipe": "Hwz(6) dc(6) m(7) oh(10) (X)?", "name": "kleric"}, "capturedDieArray": [], "activeDieArray": [{"description": "Mighty Slow Speed 6-sided die", "skills": ["Mighty", "Slow", "Speed"], "recipe": "Hwz(6)", "value": 5, "properties": [], "sides": 6}, {"description": "Stealth Chance 6-sided die", "skills": ["Stealth", "Chance"], "recipe": "dc(6)", "value": 1, "properties": [], "sides": 6}, {"description": "Morphing 7-sided die", "skills": ["Morphing"], "recipe": "m(7)", "value": 6, "properties": [], "sides": 7}, {"description": "Ornery Weak 10-sided die", "skills": ["Ornery", "Weak"], "recipe": "oh(10)", "value": 4, "properties": [], "sides": 10}, {"description": "X Mood Swing Die (with 19 sides)", "skills": ["Mood"], "recipe": "(X)?", "value": 1, "properties": [], "sides": 19}], "canStillWin": null, "swingRequestArray": {"X": [4, 20]}, "hasDismissedGame": false, "sideScore": -4.7, "playerName": "responder003", "prevSwingValueArray": [], "lastActionTime": 1417525085}, {"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#ddffdd", "roundScore": 31, "waitingOnAction": false, "playerId": 5, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "thefool.png", "recipe": "v(5) v(10) vq(10) vs(15) s(R,R)?", "name": "TheFool"}, "capturedDieArray": [], "activeDieArray": [{"description": "Value 5-sided die", "skills": ["Value"], "recipe": "v(5)", "value": 5, "properties": ["ValueRelevantToScore"], "sides": 5}, {"description": "Value 10-sided die", "skills": ["Value"], "recipe": "v(10)", "value": 10, "properties": ["ValueRelevantToScore"], "sides": 10}, {"description": "Value Queer 10-sided die", "skills": ["Value", "Queer"], "recipe": "vq(10)", "value": 7, "properties": ["ValueRelevantToScore"], "sides": 10}, {"description": "Value Shadow 15-sided die", "skills": ["Value", "Shadow"], "recipe": "vs(15)", "value": 10, "properties": ["ValueRelevantToScore"], "sides": 15}, {"description": "Shadow Twin R Mood Swing Die (both with 15 sides)", "skills": ["Shadow", "Mood"], "recipe": "s(R,R)?", "value": 18, "properties": [], "sides": 30}], "canStillWin": null, "swingRequestArray": {"R": [2, 16]}, "hasDismissedGame": false, "sideScore": 4.7, "playerName": "responder004", "prevSwingValueArray": [], "lastActionTime": 1417525086}], "currentPlayerIdx": 0}', TRUE));
        $expData['gameId'] = $gameId;
        $expData['playerDataArray'][0]['playerId'] = $this->user_ids['responder003'];
        $expData['playerDataArray'][1]['playerId'] = $this->user_ids['responder004'];
        $retval = $this->verify_api_loadGameData($expData, $gameId, 10);

        $this->verify_api_submitTurn(
            array(2, 5),
            'responder003 performed Speed attack using [Hwz(6):5] against [v(5):5]; Defender v(5) was captured; Attacker Hwz(6) changed size from 6 to 8 sides, recipe changed from Hwz(6) to Hwz(8), rerolled 5 => 2. responder003\'s idle ornery dice rerolled at end of turn: oh(10) changed size from 10 to 8 sides, recipe changed from oh(10) to oh(8), rerolled 4 => 5. ',
            $retval, array(array(0, 0), array(1, 0)),
            $gameId, 1, 'Speed', 0, 1, '');

        $expData = $this->squash_game_data_timestamps(json_decode('{"previousGameId": null, "gameChatEditable": false, "gameId": 173, "description": "", "timestamp": 1417525086, "roundNumber": 1, "playerWithInitiativeIdx": 0, "gameChatLog": [], "gameState": "START_TURN", "validAttackTypeArray": ["Power"], "activePlayerIdx": 1, "gameSkillsInfo": {"Slow": {"code": "w", "interacts": [], "description": "These dice are not counted for the purposes of initiative."}, "Mood": {"code": "?", "interacts": {"Ornery": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "These are a subcategory of Swing dice, whose size changes randomly when rerolled. At the very start of the game (and again after any round they lose, just as with normal Swing dice) the player sets the initial size of Mood Swing dice, but from then on whenever they are rolled their size is set randomly to that of a \"real-world\" die (i.e. 1, 2, 4, 6, 8, 10, 12, 20, or 30 sides) within the range allowable for that Swing type."}, "Morphing": {"code": "m", "interacts": [], "description": "When a Morphing Die is used in any attack, it changes size, becoming the same size as the die that was captured. It is then re-rolled. Morphing Dice change size every time they capture another die. If a Morphing die is captured, its scoring value is based on its size at the time of capture; likewise, if it is not captured during a round, its scoring value is based on its size at the end of the round"}, "Stealth": {"code": "d", "interacts": [], "description": "These dice cannot perform any type of attack other than Multi-die Skill Attacks, meaning two or more dice participating in a Skill Attack. In addition, Stealth Dice cannot be captured by any attack other than a Multi-die Skill Attack."}, "Weak": {"code": "h", "interacts": [], "description": "When a Weak Die rerolls for any reason, it first shrinks from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Ornery": {"code": "o", "interacts": {"Mood": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "Ornery dice reroll every time the player makes any attack - whether the Ornery dice participated in it or not. The only time they don\'t reroll is if the player passes, making no attack whatsoever."}, "Value": {"code": "v", "interacts": [], "description": "These dice are not scored like normal dice. Instead, a Value Die is scored as if the number of sides it has is equal to the value that it is currently showing. If a Value Die is ever part of an attack, all dice that are captured become Value Dice (i.e. They are scored by the current value they are showing when they are captured, not by their size)."}, "Chance": {"code": "c", "interacts": [], "description": "If you do not have the initiative at the start of a round you may re-roll one of your Chance Dice. If this results in you gaining the initiative, your opponent may re-roll one of their Chance Dice. This can continue with each player re-rolling Chance Dice, even re-rolling the same die, until one person fails to gain the initiative or lets their opponent go first. Re-rolling Chance Dice is not only a way to gain the initiative; it can also be useful in protecting your larger dice, or otherwise improving your starting roll. Unlike Focus Dice, Chance Dice can be immediately re-used in an attack even if you do gain the initiative with them."}, "Mighty": {"code": "H", "interacts": [], "description": "When a Mighty Die rerolls for any reason, it first grows from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Queer": {"code": "q", "interacts": [], "description": "These dice behave like normal dice when they show an even number, and like Shadow Dice when they show an odd number."}, "Shadow": {"code": "s", "interacts": [], "description": "These dice are normal in all respects, except that they cannot make Power Attacks. Instead, they make inverted Power Attacks, called \"Shadow Attacks.\" To make a Shadow Attack, Use one of your Shadow Dice to capture one of your opponent\'s dice. The number showing on the die you capture must be greater than or equal to the number showing on your die, but within its range. For example, a shadow 10-sided die showing a 2 can capture a die showing any number from 2 to 10."}, "Speed": {"code": "z", "interacts": [], "description": "These dice can also make Speed Attacks, which are the equivalent of inverted Skill Attacks. In a Speed Attack, one Speed Die can capture any number of dice which add up exactly to its value."}}, "gameActionLog": [{"timestamp": 1417525086, "message": "responder003\'s idle ornery dice rerolled at end of turn: oh(10) changed size from 10 to 8 sides, recipe changed from oh(10) to oh(8), rerolled 4 => 5", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder003 performed Speed attack using [Hwz(6):5] against [v(5):5]; Defender v(5) was captured; Attacker Hwz(6) changed size from 6 to 8 sides, recipe changed from Hwz(6) to Hwz(8), rerolled 5 => 2", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder003 won initiative for round 1. Initial die values: responder003 rolled [Hwz(6):5, dc(6):1, m(7):6, oh(10):4, (X=19)?:1], responder004 rolled [v(5):5, v(10):10, vq(10):7, vs(15):10, s(R=15,R=15)?:18]. responder003 has dice which are not counted for initiative due to die skills: [Hwz(6)].", "player": ""}, {"timestamp": 1417525086, "message": "responder004 set swing values: R=15", "player": "responder004"}, {"timestamp": 1417525085, "message": "responder003 set swing values: X=19", "player": "responder003"}], "maxWins": 3, "playerDataArray": [{"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#dd99dd", "roundScore": 29, "waitingOnAction": false, "playerId": 4, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "BMdefaultRound.png", "recipe": "Hwz(6) dc(6) m(7) oh(10) (X)?", "name": "kleric"}, "capturedDieArray": [{"recipe": "v(5)", "sides": 5, "value": 5, "properties": ["ValueRelevantToScore", "WasJustCaptured"]}], "activeDieArray": [{"description": "Mighty Slow Speed 8-sided die", "skills": ["Mighty", "Slow", "Speed"], "recipe": "Hwz(8)", "value": 2, "properties": ["HasJustGrown"], "sides": 8}, {"description": "Stealth Chance 6-sided die", "skills": ["Stealth", "Chance"], "recipe": "dc(6)", "value": 1, "properties": [], "sides": 6}, {"description": "Morphing 7-sided die", "skills": ["Morphing"], "recipe": "m(7)", "value": 6, "properties": [], "sides": 7}, {"description": "Ornery Weak 8-sided die", "skills": ["Ornery", "Weak"], "recipe": "oh(8)", "value": 5, "properties": ["HasJustShrunk", "HasJustRerolledOrnery"], "sides": 8}, {"description": "X Mood Swing Die (with 19 sides)", "skills": ["Mood"], "recipe": "(X)?", "value": 1, "properties": [], "sides": 19}], "canStillWin": null, "swingRequestArray": {"X": [4, 20]}, "hasDismissedGame": false, "sideScore": 0.3, "playerName": "responder003", "prevSwingValueArray": [], "lastActionTime": 1417525086}, {"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#ddffdd", "roundScore": 28.5, "waitingOnAction": true, "playerId": 5, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "thefool.png", "recipe": "v(5) v(10) vq(10) vs(15) s(R,R)?", "name": "TheFool"}, "capturedDieArray": [], "activeDieArray": [{"description": "Value 10-sided die", "skills": ["Value"], "recipe": "v(10)", "value": 10, "properties": ["ValueRelevantToScore"], "sides": 10}, {"description": "Value Queer 10-sided die", "skills": ["Value", "Queer"], "recipe": "vq(10)", "value": 7, "properties": ["ValueRelevantToScore"], "sides": 10}, {"description": "Value Shadow 15-sided die", "skills": ["Value", "Shadow"], "recipe": "vs(15)", "value": 10, "properties": ["ValueRelevantToScore"], "sides": 15}, {"description": "Shadow Twin R Mood Swing Die (both with 15 sides)", "skills": ["Shadow", "Mood"], "recipe": "s(R,R)?", "value": 18, "properties": [], "sides": 30}], "canStillWin": null, "swingRequestArray": {"R": [2, 16]}, "hasDismissedGame": false, "sideScore": -0.3, "playerName": "responder004", "prevSwingValueArray": [], "lastActionTime": 1417525086}], "currentPlayerIdx": 0}', TRUE));
        $expData['gameId'] = $gameId;
        $expData['playerDataArray'][0]['playerId'] = $this->user_ids['responder003'];
        $expData['playerDataArray'][1]['playerId'] = $this->user_ids['responder004'];
        $retval = $this->verify_api_loadGameData($expData, $gameId, 10);

        $_SESSION = $this->mock_test_user_login('responder004');
        $this->verify_api_submitTurn(
            array(5),
            'responder004 performed Power attack using [v(10):10] against [oh(8):5]; Defender oh(8) recipe changed to ohv(8), was captured; Attacker v(10) rerolled 10 => 5. ',
            $retval, array(array(1, 0), array(0, 3)),
            $gameId, 1, 'Power', 1, 0, '');
        $_SESSION = $this->mock_test_user_login('responder003');

        $expData = $this->squash_game_data_timestamps(json_decode('{"previousGameId": null, "gameChatEditable": false, "gameId": 173, "description": "", "timestamp": 1417525086, "roundNumber": 1, "playerWithInitiativeIdx": 0, "gameChatLog": [], "gameState": "START_TURN", "validAttackTypeArray": ["Power", "Skill"], "activePlayerIdx": 0, "gameSkillsInfo": {"Slow": {"code": "w", "interacts": [], "description": "These dice are not counted for the purposes of initiative."}, "Mood": {"code": "?", "interacts": {"Ornery": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "These are a subcategory of Swing dice, whose size changes randomly when rerolled. At the very start of the game (and again after any round they lose, just as with normal Swing dice) the player sets the initial size of Mood Swing dice, but from then on whenever they are rolled their size is set randomly to that of a \"real-world\" die (i.e. 1, 2, 4, 6, 8, 10, 12, 20, or 30 sides) within the range allowable for that Swing type."}, "Morphing": {"code": "m", "interacts": [], "description": "When a Morphing Die is used in any attack, it changes size, becoming the same size as the die that was captured. It is then re-rolled. Morphing Dice change size every time they capture another die. If a Morphing die is captured, its scoring value is based on its size at the time of capture; likewise, if it is not captured during a round, its scoring value is based on its size at the end of the round"}, "Stealth": {"code": "d", "interacts": [], "description": "These dice cannot perform any type of attack other than Multi-die Skill Attacks, meaning two or more dice participating in a Skill Attack. In addition, Stealth Dice cannot be captured by any attack other than a Multi-die Skill Attack."}, "Weak": {"code": "h", "interacts": [], "description": "When a Weak Die rerolls for any reason, it first shrinks from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Ornery": {"code": "o", "interacts": {"Mood": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "Ornery dice reroll every time the player makes any attack - whether the Ornery dice participated in it or not. The only time they don\'t reroll is if the player passes, making no attack whatsoever."}, "Value": {"code": "v", "interacts": [], "description": "These dice are not scored like normal dice. Instead, a Value Die is scored as if the number of sides it has is equal to the value that it is currently showing. If a Value Die is ever part of an attack, all dice that are captured become Value Dice (i.e. They are scored by the current value they are showing when they are captured, not by their size)."}, "Chance": {"code": "c", "interacts": [], "description": "If you do not have the initiative at the start of a round you may re-roll one of your Chance Dice. If this results in you gaining the initiative, your opponent may re-roll one of their Chance Dice. This can continue with each player re-rolling Chance Dice, even re-rolling the same die, until one person fails to gain the initiative or lets their opponent go first. Re-rolling Chance Dice is not only a way to gain the initiative; it can also be useful in protecting your larger dice, or otherwise improving your starting roll. Unlike Focus Dice, Chance Dice can be immediately re-used in an attack even if you do gain the initiative with them."}, "Mighty": {"code": "H", "interacts": [], "description": "When a Mighty Die rerolls for any reason, it first grows from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Queer": {"code": "q", "interacts": [], "description": "These dice behave like normal dice when they show an even number, and like Shadow Dice when they show an odd number."}, "Shadow": {"code": "s", "interacts": [], "description": "These dice are normal in all respects, except that they cannot make Power Attacks. Instead, they make inverted Power Attacks, called \"Shadow Attacks.\" To make a Shadow Attack, Use one of your Shadow Dice to capture one of your opponent\'s dice. The number showing on the die you capture must be greater than or equal to the number showing on your die, but within its range. For example, a shadow 10-sided die showing a 2 can capture a die showing any number from 2 to 10."}, "Speed": {"code": "z", "interacts": [], "description": "These dice can also make Speed Attacks, which are the equivalent of inverted Skill Attacks. In a Speed Attack, one Speed Die can capture any number of dice which add up exactly to its value."}}, "gameActionLog": [{"timestamp": 1417525086, "message": "responder004 performed Power attack using [v(10):10] against [oh(8):5]; Defender oh(8) recipe changed to ohv(8), was captured; Attacker v(10) rerolled 10 => 5", "player": "responder004"}, {"timestamp": 1417525086, "message": "responder003\'s idle ornery dice rerolled at end of turn: oh(10) changed size from 10 to 8 sides, recipe changed from oh(10) to oh(8), rerolled 4 => 5", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder003 performed Speed attack using [Hwz(6):5] against [v(5):5]; Defender v(5) was captured; Attacker Hwz(6) changed size from 6 to 8 sides, recipe changed from Hwz(6) to Hwz(8), rerolled 5 => 2", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder003 won initiative for round 1. Initial die values: responder003 rolled [Hwz(6):5, dc(6):1, m(7):6, oh(10):4, (X=19)?:1], responder004 rolled [v(5):5, v(10):10, vq(10):7, vs(15):10, s(R=15,R=15)?:18]. responder003 has dice which are not counted for initiative due to die skills: [Hwz(6)].", "player": ""}, {"timestamp": 1417525086, "message": "responder004 set swing values: R=15", "player": "responder004"}, {"timestamp": 1417525085, "message": "responder003 set swing values: X=19", "player": "responder003"}], "maxWins": 3, "playerDataArray": [{"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#dd99dd", "roundScore": 25, "waitingOnAction": true, "playerId": 4, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "BMdefaultRound.png", "recipe": "Hwz(6) dc(6) m(7) oh(10) (X)?", "name": "kleric"}, "capturedDieArray": [{"recipe": "v(5)", "sides": 5, "value": 5, "properties": ["ValueRelevantToScore"]}], "activeDieArray": [{"description": "Mighty Slow Speed 8-sided die", "skills": ["Mighty", "Slow", "Speed"], "recipe": "Hwz(8)", "value": 2, "properties": [], "sides": 8}, {"description": "Stealth Chance 6-sided die", "skills": ["Stealth", "Chance"], "recipe": "dc(6)", "value": 1, "properties": [], "sides": 6}, {"description": "Morphing 7-sided die", "skills": ["Morphing"], "recipe": "m(7)", "value": 6, "properties": [], "sides": 7}, {"description": "X Mood Swing Die (with 19 sides)", "skills": ["Mood"], "recipe": "(X)?", "value": 1, "properties": [], "sides": 19}], "canStillWin": null, "swingRequestArray": {"X": [4, 20]}, "hasDismissedGame": false, "sideScore": -4, "playerName": "responder003", "prevSwingValueArray": [], "lastActionTime": 1417525086}, {"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#ddffdd", "roundScore": 31, "waitingOnAction": false, "playerId": 5, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "thefool.png", "recipe": "v(5) v(10) vq(10) vs(15) s(R,R)?", "name": "TheFool"}, "capturedDieArray": [{"recipe": "ohv(8)", "sides": 8, "value": 5, "properties": ["ValueRelevantToScore", "WasJustCaptured"]}], "activeDieArray": [{"description": "Value 10-sided die", "skills": ["Value"], "recipe": "v(10)", "value": 5, "properties": ["ValueRelevantToScore"], "sides": 10}, {"description": "Value Queer 10-sided die", "skills": ["Value", "Queer"], "recipe": "vq(10)", "value": 7, "properties": ["ValueRelevantToScore"], "sides": 10}, {"description": "Value Shadow 15-sided die", "skills": ["Value", "Shadow"], "recipe": "vs(15)", "value": 10, "properties": ["ValueRelevantToScore"], "sides": 15}, {"description": "Shadow Twin R Mood Swing Die (both with 15 sides)", "skills": ["Shadow", "Mood"], "recipe": "s(R,R)?", "value": 18, "properties": [], "sides": 30}], "canStillWin": null, "swingRequestArray": {"R": [2, 16]}, "hasDismissedGame": false, "sideScore": 4, "playerName": "responder004", "prevSwingValueArray": [], "lastActionTime": 1417525086}], "currentPlayerIdx": 0}', TRUE));
        $expData['gameId'] = $gameId;
        $expData['playerDataArray'][0]['playerId'] = $this->user_ids['responder003'];
        $expData['playerDataArray'][1]['playerId'] = $this->user_ids['responder004'];
        $retval = $this->verify_api_loadGameData($expData, $gameId, 10);

        $this->verify_api_submitTurn(
            array(3),
            'responder003 performed Power attack using [m(7):6] against [v(10):5]; Defender v(10) was captured; Attacker m(7) changed size from 7 to 10 sides, recipe changed from m(7) to m(10), rerolled 6 => 3. responder004 passed. ',
            $retval, array(array(0, 2), array(1, 0)),
            $gameId, 1, 'Power', 0, 1, '');

        $expData = $this->squash_game_data_timestamps(json_decode('{"previousGameId": null, "gameChatEditable": false, "gameId": 173, "description": "", "timestamp": 1417525086, "roundNumber": 1, "playerWithInitiativeIdx": 0, "gameChatLog": [], "gameState": "START_TURN", "validAttackTypeArray": ["Skill"], "activePlayerIdx": 0, "gameSkillsInfo": {"Slow": {"code": "w", "interacts": [], "description": "These dice are not counted for the purposes of initiative."}, "Mood": {"code": "?", "interacts": {"Ornery": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "These are a subcategory of Swing dice, whose size changes randomly when rerolled. At the very start of the game (and again after any round they lose, just as with normal Swing dice) the player sets the initial size of Mood Swing dice, but from then on whenever they are rolled their size is set randomly to that of a \"real-world\" die (i.e. 1, 2, 4, 6, 8, 10, 12, 20, or 30 sides) within the range allowable for that Swing type."}, "Morphing": {"code": "m", "interacts": [], "description": "When a Morphing Die is used in any attack, it changes size, becoming the same size as the die that was captured. It is then re-rolled. Morphing Dice change size every time they capture another die. If a Morphing die is captured, its scoring value is based on its size at the time of capture; likewise, if it is not captured during a round, its scoring value is based on its size at the end of the round"}, "Stealth": {"code": "d", "interacts": [], "description": "These dice cannot perform any type of attack other than Multi-die Skill Attacks, meaning two or more dice participating in a Skill Attack. In addition, Stealth Dice cannot be captured by any attack other than a Multi-die Skill Attack."}, "Weak": {"code": "h", "interacts": [], "description": "When a Weak Die rerolls for any reason, it first shrinks from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Ornery": {"code": "o", "interacts": {"Mood": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "Ornery dice reroll every time the player makes any attack - whether the Ornery dice participated in it or not. The only time they don\'t reroll is if the player passes, making no attack whatsoever."}, "Value": {"code": "v", "interacts": [], "description": "These dice are not scored like normal dice. Instead, a Value Die is scored as if the number of sides it has is equal to the value that it is currently showing. If a Value Die is ever part of an attack, all dice that are captured become Value Dice (i.e. They are scored by the current value they are showing when they are captured, not by their size)."}, "Chance": {"code": "c", "interacts": [], "description": "If you do not have the initiative at the start of a round you may re-roll one of your Chance Dice. If this results in you gaining the initiative, your opponent may re-roll one of their Chance Dice. This can continue with each player re-rolling Chance Dice, even re-rolling the same die, until one person fails to gain the initiative or lets their opponent go first. Re-rolling Chance Dice is not only a way to gain the initiative; it can also be useful in protecting your larger dice, or otherwise improving your starting roll. Unlike Focus Dice, Chance Dice can be immediately re-used in an attack even if you do gain the initiative with them."}, "Mighty": {"code": "H", "interacts": [], "description": "When a Mighty Die rerolls for any reason, it first grows from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Queer": {"code": "q", "interacts": [], "description": "These dice behave like normal dice when they show an even number, and like Shadow Dice when they show an odd number."}, "Shadow": {"code": "s", "interacts": [], "description": "These dice are normal in all respects, except that they cannot make Power Attacks. Instead, they make inverted Power Attacks, called \"Shadow Attacks.\" To make a Shadow Attack, Use one of your Shadow Dice to capture one of your opponent\'s dice. The number showing on the die you capture must be greater than or equal to the number showing on your die, but within its range. For example, a shadow 10-sided die showing a 2 can capture a die showing any number from 2 to 10."}, "Speed": {"code": "z", "interacts": [], "description": "These dice can also make Speed Attacks, which are the equivalent of inverted Skill Attacks. In a Speed Attack, one Speed Die can capture any number of dice which add up exactly to its value."}}, "gameActionLog": [{"timestamp": 1417525086, "message": "responder004 passed", "player": "responder004"}, {"timestamp": 1417525086, "message": "responder003 performed Power attack using [m(7):6] against [v(10):5]; Defender v(10) was captured; Attacker m(7) changed size from 7 to 10 sides, recipe changed from m(7) to m(10), rerolled 6 => 3", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder004 performed Power attack using [v(10):10] against [oh(8):5]; Defender oh(8) recipe changed to ohv(8), was captured; Attacker v(10) rerolled 10 => 5", "player": "responder004"}, {"timestamp": 1417525086, "message": "responder003\'s idle ornery dice rerolled at end of turn: oh(10) changed size from 10 to 8 sides, recipe changed from oh(10) to oh(8), rerolled 4 => 5", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder003 performed Speed attack using [Hwz(6):5] against [v(5):5]; Defender v(5) was captured; Attacker Hwz(6) changed size from 6 to 8 sides, recipe changed from Hwz(6) to Hwz(8), rerolled 5 => 2", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder003 won initiative for round 1. Initial die values: responder003 rolled [Hwz(6):5, dc(6):1, m(7):6, oh(10):4, (X=19)?:1], responder004 rolled [v(5):5, v(10):10, vq(10):7, vs(15):10, s(R=15,R=15)?:18]. responder003 has dice which are not counted for initiative due to die skills: [Hwz(6)].", "player": ""}, {"timestamp": 1417525086, "message": "responder004 set swing values: R=15", "player": "responder004"}, {"timestamp": 1417525085, "message": "responder003 set swing values: X=19", "player": "responder003"}], "maxWins": 3, "playerDataArray": [{"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#dd99dd", "roundScore": 31.5, "waitingOnAction": true, "playerId": 4, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "BMdefaultRound.png", "recipe": "Hwz(6) dc(6) m(7) oh(10) (X)?", "name": "kleric"}, "capturedDieArray": [{"recipe": "v(5)", "sides": 5, "value": 5, "properties": ["ValueRelevantToScore"]}, {"recipe": "v(10)", "sides": 10, "value": 5, "properties": ["ValueRelevantToScore"]}], "activeDieArray": [{"description": "Mighty Slow Speed 8-sided die", "skills": ["Mighty", "Slow", "Speed"], "recipe": "Hwz(8)", "value": 2, "properties": [], "sides": 8}, {"description": "Stealth Chance 6-sided die", "skills": ["Stealth", "Chance"], "recipe": "dc(6)", "value": 1, "properties": [], "sides": 6}, {"description": "Morphing 10-sided die", "skills": ["Morphing"], "recipe": "m(10)", "value": 3, "properties": [], "sides": 10}, {"description": "X Mood Swing Die (with 19 sides)", "skills": ["Mood"], "recipe": "(X)?", "value": 1, "properties": [], "sides": 19}], "canStillWin": null, "swingRequestArray": {"X": [4, 20]}, "hasDismissedGame": false, "sideScore": 2, "playerName": "responder003", "prevSwingValueArray": [], "lastActionTime": 1417525086}, {"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#ddffdd", "roundScore": 28.5, "waitingOnAction": false, "playerId": 5, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "thefool.png", "recipe": "v(5) v(10) vq(10) vs(15) s(R,R)?", "name": "TheFool"}, "capturedDieArray": [{"recipe": "ohv(8)", "sides": 8, "value": 5, "properties": ["ValueRelevantToScore"]}], "activeDieArray": [{"description": "Value Queer 10-sided die", "skills": ["Value", "Queer"], "recipe": "vq(10)", "value": 7, "properties": ["ValueRelevantToScore"], "sides": 10}, {"description": "Value Shadow 15-sided die", "skills": ["Value", "Shadow"], "recipe": "vs(15)", "value": 10, "properties": ["ValueRelevantToScore"], "sides": 15}, {"description": "Shadow Twin R Mood Swing Die (both with 15 sides)", "skills": ["Shadow", "Mood"], "recipe": "s(R,R)?", "value": 18, "properties": [], "sides": 30}], "canStillWin": null, "swingRequestArray": {"R": [2, 16]}, "hasDismissedGame": false, "sideScore": -2, "playerName": "responder004", "prevSwingValueArray": [], "lastActionTime": 1417525086}], "currentPlayerIdx": 0}', TRUE));
        $expData['gameId'] = $gameId;
        $expData['playerDataArray'][0]['playerId'] = $this->user_ids['responder003'];
        $expData['playerDataArray'][1]['playerId'] = $this->user_ids['responder004'];
        $retval = $this->verify_api_loadGameData($expData, $gameId, 10);

        $this->verify_api_submitTurn(
            array(6, 5, 9, 4, 3),
            'responder003 performed Skill attack using [Hwz(8):2,dc(6):1,m(10):3,(X=19)?:1] against [vq(10):7]; Defender vq(10) was captured; Attacker Hwz(8) changed size from 8 to 10 sides, recipe changed from Hwz(8) to Hwz(10), rerolled 2 => 6; Attacker dc(6) rerolled 1 => 5; Attacker m(10) remained the same size, rerolled 3 => 9; Attacker (X=19)? changed size from 19 to 12 sides, recipe changed from (X=19)? to (X=12)?, rerolled 1 => 3. responder004 passed. ',
            $retval, array(array(0, 0), array(0, 1), array(0, 2), array(0, 3), array(1, 0)),
            $gameId, 1, 'Skill', 0, 1, '');

        $expData = $this->squash_game_data_timestamps(json_decode('{"previousGameId": null, "gameChatEditable": false, "gameId": 173, "description": "", "timestamp": 1417525086, "roundNumber": 1, "playerWithInitiativeIdx": 0, "gameChatLog": [], "gameState": "START_TURN", "validAttackTypeArray": ["Skill"], "activePlayerIdx": 0, "gameSkillsInfo": {"Slow": {"code": "w", "interacts": [], "description": "These dice are not counted for the purposes of initiative."}, "Mood": {"code": "?", "interacts": {"Ornery": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "These are a subcategory of Swing dice, whose size changes randomly when rerolled. At the very start of the game (and again after any round they lose, just as with normal Swing dice) the player sets the initial size of Mood Swing dice, but from then on whenever they are rolled their size is set randomly to that of a \"real-world\" die (i.e. 1, 2, 4, 6, 8, 10, 12, 20, or 30 sides) within the range allowable for that Swing type."}, "Morphing": {"code": "m", "interacts": [], "description": "When a Morphing Die is used in any attack, it changes size, becoming the same size as the die that was captured. It is then re-rolled. Morphing Dice change size every time they capture another die. If a Morphing die is captured, its scoring value is based on its size at the time of capture; likewise, if it is not captured during a round, its scoring value is based on its size at the end of the round"}, "Stealth": {"code": "d", "interacts": [], "description": "These dice cannot perform any type of attack other than Multi-die Skill Attacks, meaning two or more dice participating in a Skill Attack. In addition, Stealth Dice cannot be captured by any attack other than a Multi-die Skill Attack."}, "Weak": {"code": "h", "interacts": [], "description": "When a Weak Die rerolls for any reason, it first shrinks from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Ornery": {"code": "o", "interacts": {"Mood": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "Ornery dice reroll every time the player makes any attack - whether the Ornery dice participated in it or not. The only time they don\'t reroll is if the player passes, making no attack whatsoever."}, "Value": {"code": "v", "interacts": [], "description": "These dice are not scored like normal dice. Instead, a Value Die is scored as if the number of sides it has is equal to the value that it is currently showing. If a Value Die is ever part of an attack, all dice that are captured become Value Dice (i.e. They are scored by the current value they are showing when they are captured, not by their size)."}, "Chance": {"code": "c", "interacts": [], "description": "If you do not have the initiative at the start of a round you may re-roll one of your Chance Dice. If this results in you gaining the initiative, your opponent may re-roll one of their Chance Dice. This can continue with each player re-rolling Chance Dice, even re-rolling the same die, until one person fails to gain the initiative or lets their opponent go first. Re-rolling Chance Dice is not only a way to gain the initiative; it can also be useful in protecting your larger dice, or otherwise improving your starting roll. Unlike Focus Dice, Chance Dice can be immediately re-used in an attack even if you do gain the initiative with them."}, "Mighty": {"code": "H", "interacts": [], "description": "When a Mighty Die rerolls for any reason, it first grows from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Queer": {"code": "q", "interacts": [], "description": "These dice behave like normal dice when they show an even number, and like Shadow Dice when they show an odd number."}, "Shadow": {"code": "s", "interacts": [], "description": "These dice are normal in all respects, except that they cannot make Power Attacks. Instead, they make inverted Power Attacks, called \"Shadow Attacks.\" To make a Shadow Attack, Use one of your Shadow Dice to capture one of your opponent\'s dice. The number showing on the die you capture must be greater than or equal to the number showing on your die, but within its range. For example, a shadow 10-sided die showing a 2 can capture a die showing any number from 2 to 10."}, "Speed": {"code": "z", "interacts": [], "description": "These dice can also make Speed Attacks, which are the equivalent of inverted Skill Attacks. In a Speed Attack, one Speed Die can capture any number of dice which add up exactly to its value."}}, "gameActionLog": [{"timestamp": 1417525086, "message": "responder004 passed", "player": "responder004"}, {"timestamp": 1417525086, "message": "responder003 performed Skill attack using [Hwz(8):2,dc(6):1,m(10):3,(X=19)?:1] against [vq(10):7]; Defender vq(10) was captured; Attacker Hwz(8) changed size from 8 to 10 sides, recipe changed from Hwz(8) to Hwz(10), rerolled 2 => 6; Attacker dc(6) rerolled 1 => 5; Attacker m(10) remained the same size, rerolled 3 => 9; Attacker (X=19)? changed size from 19 to 12 sides, recipe changed from (X=19)? to (X=12)?, rerolled 1 => 3", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder004 passed", "player": "responder004"}, {"timestamp": 1417525086, "message": "responder003 performed Power attack using [m(7):6] against [v(10):5]; Defender v(10) was captured; Attacker m(7) changed size from 7 to 10 sides, recipe changed from m(7) to m(10), rerolled 6 => 3", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder004 performed Power attack using [v(10):10] against [oh(8):5]; Defender oh(8) recipe changed to ohv(8), was captured; Attacker v(10) rerolled 10 => 5", "player": "responder004"}, {"timestamp": 1417525086, "message": "responder003\'s idle ornery dice rerolled at end of turn: oh(10) changed size from 10 to 8 sides, recipe changed from oh(10) to oh(8), rerolled 4 => 5", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder003 performed Speed attack using [Hwz(6):5] against [v(5):5]; Defender v(5) was captured; Attacker Hwz(6) changed size from 6 to 8 sides, recipe changed from Hwz(6) to Hwz(8), rerolled 5 => 2", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder003 won initiative for round 1. Initial die values: responder003 rolled [Hwz(6):5, dc(6):1, m(7):6, oh(10):4, (X=19)?:1], responder004 rolled [v(5):5, v(10):10, vq(10):7, vs(15):10, s(R=15,R=15)?:18]. responder003 has dice which are not counted for initiative due to die skills: [Hwz(6)].", "player": ""}, {"timestamp": 1417525086, "message": "responder004 set swing values: R=15", "player": "responder004"}, {"timestamp": 1417525085, "message": "responder003 set swing values: X=19", "player": "responder003"}], "maxWins": 3, "playerDataArray": [{"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#dd99dd", "roundScore": 36, "waitingOnAction": true, "playerId": 4, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "BMdefaultRound.png", "recipe": "Hwz(6) dc(6) m(7) oh(10) (X)?", "name": "kleric"}, "capturedDieArray": [{"recipe": "v(5)", "sides": 5, "value": 5, "properties": ["ValueRelevantToScore"]}, {"recipe": "v(10)", "sides": 10, "value": 5, "properties": ["ValueRelevantToScore"]}, {"recipe": "vq(10)", "sides": 10, "value": 7, "properties": ["ValueRelevantToScore"]}], "activeDieArray": [{"description": "Mighty Slow Speed 10-sided die", "skills": ["Mighty", "Slow", "Speed"], "recipe": "Hwz(10)", "value": 6, "properties": [], "sides": 10}, {"description": "Stealth Chance 6-sided die", "skills": ["Stealth", "Chance"], "recipe": "dc(6)", "value": 5, "properties": [], "sides": 6}, {"description": "Morphing 10-sided die", "skills": ["Morphing"], "recipe": "m(10)", "value": 9, "properties": [], "sides": 10}, {"description": "X Mood Swing Die (with 12 sides)", "skills": ["Mood"], "recipe": "(X)?", "value": 3, "properties": [], "sides": 12}], "canStillWin": null, "swingRequestArray": {"X": [4, 20]}, "hasDismissedGame": false, "sideScore": 7.3, "playerName": "responder003", "prevSwingValueArray": [], "lastActionTime": 1417525086}, {"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#ddffdd", "roundScore": 25, "waitingOnAction": false, "playerId": 5, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "thefool.png", "recipe": "v(5) v(10) vq(10) vs(15) s(R,R)?", "name": "TheFool"}, "capturedDieArray": [{"recipe": "ohv(8)", "sides": 8, "value": 5, "properties": ["ValueRelevantToScore"]}], "activeDieArray": [{"description": "Value Shadow 15-sided die", "skills": ["Value", "Shadow"], "recipe": "vs(15)", "value": 10, "properties": ["ValueRelevantToScore"], "sides": 15}, {"description": "Shadow Twin R Mood Swing Die (both with 15 sides)", "skills": ["Shadow", "Mood"], "recipe": "s(R,R)?", "value": 18, "properties": [], "sides": 30}], "canStillWin": null, "swingRequestArray": {"R": [2, 16]}, "hasDismissedGame": false, "sideScore": -7.3, "playerName": "responder004", "prevSwingValueArray": [], "lastActionTime": 1417525086}], "currentPlayerIdx": 0}', TRUE));
        $expData['gameId'] = $gameId;
        $expData['playerDataArray'][0]['playerId'] = $this->user_ids['responder003'];
        $expData['playerDataArray'][1]['playerId'] = $this->user_ids['responder004'];
        $retval = $this->verify_api_loadGameData($expData, $gameId, 10);

        $this->verify_api_submitTurn(
            array(12, 10, 8, 4, 12),
            'responder003 performed Skill attack using [Hwz(10):6,m(10):9,(X=12)?:3] against [s(R=15,R=15)?:18]; Defender s(R=15,R=15)? was captured; Attacker Hwz(10) changed size from 10 to 12 sides, recipe changed from Hwz(10) to Hwz(12), rerolled 6 => 12; Attacker m(10) changed size from 10 to 30 sides, recipe changed from m(10) to m(R=15,R=15), rerolled 9 => 18; Attacker (X=12)? remained the same size, rerolled 3 => 12. ',
            $retval, array(array(0, 0), array(0, 2), array(0, 3), array(1, 1)),
            $gameId, 1, 'Skill', 0, 1, '');

        $expData = $this->squash_game_data_timestamps(json_decode('{"previousGameId": null, "gameChatEditable": false, "gameId": 173, "description": "", "timestamp": 1417525086, "roundNumber": 1, "playerWithInitiativeIdx": 0, "gameChatLog": [], "gameState": "START_TURN", "validAttackTypeArray": ["Shadow"], "activePlayerIdx": 1, "gameSkillsInfo": {"Slow": {"code": "w", "interacts": [], "description": "These dice are not counted for the purposes of initiative."}, "Mood": {"code": "?", "interacts": {"Ornery": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "These are a subcategory of Swing dice, whose size changes randomly when rerolled. At the very start of the game (and again after any round they lose, just as with normal Swing dice) the player sets the initial size of Mood Swing dice, but from then on whenever they are rolled their size is set randomly to that of a \"real-world\" die (i.e. 1, 2, 4, 6, 8, 10, 12, 20, or 30 sides) within the range allowable for that Swing type."}, "Morphing": {"code": "m", "interacts": [], "description": "When a Morphing Die is used in any attack, it changes size, becoming the same size as the die that was captured. It is then re-rolled. Morphing Dice change size every time they capture another die. If a Morphing die is captured, its scoring value is based on its size at the time of capture; likewise, if it is not captured during a round, its scoring value is based on its size at the end of the round"}, "Stealth": {"code": "d", "interacts": [], "description": "These dice cannot perform any type of attack other than Multi-die Skill Attacks, meaning two or more dice participating in a Skill Attack. In addition, Stealth Dice cannot be captured by any attack other than a Multi-die Skill Attack."}, "Weak": {"code": "h", "interacts": [], "description": "When a Weak Die rerolls for any reason, it first shrinks from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Ornery": {"code": "o", "interacts": {"Mood": "Dice with both Ornery and Mood Swing have their sizes randomized during ornery rerolls"}, "description": "Ornery dice reroll every time the player makes any attack - whether the Ornery dice participated in it or not. The only time they don\'t reroll is if the player passes, making no attack whatsoever."}, "Value": {"code": "v", "interacts": [], "description": "These dice are not scored like normal dice. Instead, a Value Die is scored as if the number of sides it has is equal to the value that it is currently showing. If a Value Die is ever part of an attack, all dice that are captured become Value Dice (i.e. They are scored by the current value they are showing when they are captured, not by their size)."}, "Chance": {"code": "c", "interacts": [], "description": "If you do not have the initiative at the start of a round you may re-roll one of your Chance Dice. If this results in you gaining the initiative, your opponent may re-roll one of their Chance Dice. This can continue with each player re-rolling Chance Dice, even re-rolling the same die, until one person fails to gain the initiative or lets their opponent go first. Re-rolling Chance Dice is not only a way to gain the initiative; it can also be useful in protecting your larger dice, or otherwise improving your starting roll. Unlike Focus Dice, Chance Dice can be immediately re-used in an attack even if you do gain the initiative with them."}, "Mighty": {"code": "H", "interacts": [], "description": "When a Mighty Die rerolls for any reason, it first grows from its current size to the next larger size in the list of \"standard\" die sizes (1, 2, 4, 6, 8, 10, 12, 16, 20, 30)."}, "Queer": {"code": "q", "interacts": [], "description": "These dice behave like normal dice when they show an even number, and like Shadow Dice when they show an odd number."}, "Shadow": {"code": "s", "interacts": [], "description": "These dice are normal in all respects, except that they cannot make Power Attacks. Instead, they make inverted Power Attacks, called \"Shadow Attacks.\" To make a Shadow Attack, Use one of your Shadow Dice to capture one of your opponent\'s dice. The number showing on the die you capture must be greater than or equal to the number showing on your die, but within its range. For example, a shadow 10-sided die showing a 2 can capture a die showing any number from 2 to 10."}, "Speed": {"code": "z", "interacts": [], "description": "These dice can also make Speed Attacks, which are the equivalent of inverted Skill Attacks. In a Speed Attack, one Speed Die can capture any number of dice which add up exactly to its value."}}, "gameActionLog": [{"timestamp": 1417525086, "message": "responder003 performed Skill attack using [Hwz(10):6,m(10):9,(X=12)?:3] against [s(R=15,R=15)?:18]; Defender s(R=15,R=15)? was captured; Attacker Hwz(10) changed size from 10 to 12 sides, recipe changed from Hwz(10) to Hwz(12), rerolled 6 => 12; Attacker m(10) changed size from 10 to 30 sides, recipe changed from m(10) to m(R=15,R=15), rerolled 9 => 18; Attacker (X=12)? remained the same size, rerolled 3 => 12", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder004 passed", "player": "responder004"}, {"timestamp": 1417525086, "message": "responder003 performed Skill attack using [Hwz(8):2,dc(6):1,m(10):3,(X=19)?:1] against [vq(10):7]; Defender vq(10) was captured; Attacker Hwz(8) changed size from 8 to 10 sides, recipe changed from Hwz(8) to Hwz(10), rerolled 2 => 6; Attacker dc(6) rerolled 1 => 5; Attacker m(10) remained the same size, rerolled 3 => 9; Attacker (X=19)? changed size from 19 to 12 sides, recipe changed from (X=19)? to (X=12)?, rerolled 1 => 3", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder004 passed", "player": "responder004"}, {"timestamp": 1417525086, "message": "responder003 performed Power attack using [m(7):6] against [v(10):5]; Defender v(10) was captured; Attacker m(7) changed size from 7 to 10 sides, recipe changed from m(7) to m(10), rerolled 6 => 3", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder004 performed Power attack using [v(10):10] against [oh(8):5]; Defender oh(8) recipe changed to ohv(8), was captured; Attacker v(10) rerolled 10 => 5", "player": "responder004"}, {"timestamp": 1417525086, "message": "responder003\'s idle ornery dice rerolled at end of turn: oh(10) changed size from 10 to 8 sides, recipe changed from oh(10) to oh(8), rerolled 4 => 5", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder003 performed Speed attack using [Hwz(6):5] against [v(5):5]; Defender v(5) was captured; Attacker Hwz(6) changed size from 6 to 8 sides, recipe changed from Hwz(6) to Hwz(8), rerolled 5 => 2", "player": "responder003"}, {"timestamp": 1417525086, "message": "responder003 won initiative for round 1. Initial die values: responder003 rolled [Hwz(6):5, dc(6):1, m(7):6, oh(10):4, (X=19)?:1], responder004 rolled [v(5):5, v(10):10, vq(10):7, vs(15):10, s(R=15,R=15)?:18]. responder003 has dice which are not counted for initiative due to die skills: [Hwz(6)].", "player": ""}, {"timestamp": 1417525086, "message": "responder004 set swing values: R=15", "player": "responder004"}], "maxWins": 3, "playerDataArray": [{"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#dd99dd", "roundScore": 77, "waitingOnAction": false, "playerId": 4, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "BMdefaultRound.png", "recipe": "Hwz(6) dc(6) m(7) oh(10) (X)?", "name": "kleric"}, "capturedDieArray": [{"recipe": "v(5)", "sides": 5, "value": 5, "properties": ["ValueRelevantToScore"]}, {"recipe": "v(10)", "sides": 10, "value": 5, "properties": ["ValueRelevantToScore"]}, {"recipe": "vq(10)", "sides": 10, "value": 7, "properties": ["ValueRelevantToScore"]}, {"recipe": "s(R,R)?", "sides": 30, "value": 18, "properties": ["WasJustCaptured"]}], "activeDieArray": [{"description": "Mighty Slow Speed 12-sided die", "skills": ["Mighty", "Slow", "Speed"], "recipe": "Hwz(12)", "value": 12, "properties": ["HasJustGrown"], "sides": 12}, {"description": "Stealth Chance 6-sided die", "skills": ["Stealth", "Chance"], "recipe": "dc(6)", "value": 5, "properties": [], "sides": 6}, {"description": "Morphing Twin R Swing Die (both with 15 sides)", "skills": ["Morphing"], "recipe": "m(R,R)", "value": 18, "properties": ["HasJustMorphed"], "sides": 30}, {"description": "X Mood Swing Die (with 12 sides)", "skills": ["Mood"], "recipe": "(X)?", "value": 12, "properties": [], "sides": 12}], "canStillWin": null, "swingRequestArray": {"X": [4, 20], "R": [2, 16]}, "hasDismissedGame": false, "sideScore": 44.7, "playerName": "responder003", "prevSwingValueArray": [], "lastActionTime": 1417525086}, {"prevOptValueArray": [], "optRequestArray": [], "playerColor": "#ddffdd", "roundScore": 10, "waitingOnAction": true, "playerId": 5, "gameScoreArray": {"L": 0, "W": 0, "D": 0}, "button": {"artFilename": "thefool.png", "recipe": "v(5) v(10) vq(10) vs(15) s(R,R)?", "name": "TheFool"}, "capturedDieArray": [{"recipe": "ohv(8)", "sides": 8, "value": 5, "properties": ["ValueRelevantToScore"]}], "activeDieArray": [{"description": "Value Shadow 15-sided die", "skills": ["Value", "Shadow"], "recipe": "vs(15)", "value": 10, "properties": ["ValueRelevantToScore"], "sides": 15}], "canStillWin": null, "swingRequestArray": {"R": [2, 16]}, "hasDismissedGame": false, "sideScore": -44.7, "playerName": "responder004", "prevSwingValueArray": [], "lastActionTime": 1417525086}], "currentPlayerIdx": 0}', TRUE));
        $expData['gameId'] = $gameId;
        $expData['playerDataArray'][0]['playerId'] = $this->user_ids['responder003'];
        $expData['playerDataArray'][1]['playerId'] = $this->user_ids['responder004'];
        $retval = $this->verify_api_loadGameData($expData, $gameId, 10);

        $_SESSION = $this->mock_test_user_login('responder004');
        $this->verify_api_submitTurn(
            array(12),
            'responder004 performed Shadow attack using [vs(15):10] against [Hwz(12):12]; Defender Hwz(12) recipe changed to Hwzv(12), was captured; Attacker vs(15) rerolled 10 => 12. ',
            $retval, array(array(1, 0), array(0, 0)),
            $gameId, 1, 'Shadow', 1, 0, '');
        $_SESSION = $this->mock_test_user_login('responder003');

        // BUG encountered while logging game: load_game_data(173) unexpectedly failed: Internal error while loading game.
    }
blackshadowshade commented 9 years ago

That test should be more than enough for me to work out what the problem is, Chaos. Thanks for finding the bug! I'll look at this ASAP.

blackshadowshade commented 9 years ago

I'm pretty sure I know what's going on here. It's caused by not storing the individual die values in the twin die, and only storing the sum of the values.

Somewhere, the twin die is regenerating its max and min values from the subdice, but of course, these values are empty after the load from database. All I need to do is stop that regeneration when the subdie values are empty.

Note to self: perhaps in recalc_min_max()

cgolubi1 commented 9 years ago

Okay, there's a test, in https://github.com/cgolubi1/buttonmen/tree/1453_reproduce_bug

I realised we already have an Envy vs. James Beast sample game, so i just added this scenario as another few attacks on that game.