Improving implementation of cards which use voting (WIP) (#7566)

* created interface for handling voting

* created class for two choice votes, refactored a card to use it

* refactored all cards which use two choice votes

* updated VoteHandler to an abstract class to encapsulate more of its functions

* refactored cards which vote for more than two things

* [CNS] Implemented Brago's Representative

* [CN2] Implemented Ballot Broker

* [CN2] Implemented Illusion of Choice

* [CNS] Implemented Grudge Keeper

* added vote outcomes

* updated implementation of Illusion of Choice to work correctly in multiples

* added test for voting

* updated implementation of extra votes

* simplified vote message handling

* Improved names, additional comments

* Votes: fixed not working getMostVoted

* Votes: added final vote results to game logs;

* Votes: added additional info for the vote choices;

* Votes: added vote step info in choose dialogs, added AI support example for Tyrant's Choice;

Co-authored-by: Oleg Agafonov <jaydi85@gmail.com>
This commit is contained in:
Evan Kranzler 2021-03-20 10:32:54 -04:00 committed by GitHub
parent 991f154cd7
commit 1cbbcddcc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1615 additions and 786 deletions

View file

@ -0,0 +1,314 @@
package org.mage.test.multiplayer;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestCommander4PlayersWithAIHelps;
/**
* @author TheElk801
*/
public class VotingTest extends CardTestCommander4PlayersWithAIHelps {
// Player order: A -> D -> C -> B
// Councils dilemma When Lieutenants of the Guard enters the battlefield, starting with you,
// each player votes for strength or numbers. Put a +1/+1 counter on Lieutenants of the Guard
// for each strength vote and create a 1/1 white Soldier creature token for each numbers vote.
private static final String lieutenant = "Lieutenants of the Guard";
// While voting, you get an additional vote. (The votes can be for different choices or for the same choice.)
private static final String rep = "Brago's Representative";
// While voting, you may vote an additional time. (The votes can be for different choices or for the same choice.)
private static final String broker = "Ballot Broker";
// You choose how each player votes this turn.
private static final String illusion = "Illusion of Choice";
// TODO: add test with broker
// rulues:
// The ability only affects spells and abilities that use the word vote. Other cards that involve choices,
// such as Archangel of Strife, are unaffected.
// (2016-08-23)
// Whenever players finish voting, each opponent who voted for a choice you didnt vote for loses 2 life.
private static final String keeper = "Grudge Keeper";
// Will of the council - Starting with you, each player votes for death or torture. If death gets more votes,
// each opponent sacrifices a creature. If torture gets more votes or the vote is tied, each opponent loses 4 life.
private static final String tyrant = "Tyrant's Choice";
private void setChoices(String choice) {
setChoices(choice, choice, choice, choice);
}
private void setChoices(String choiceA, String choiceB, String choiceC, String choiceD) {
setChoice(playerA, choiceA);
setChoice(playerB, choiceB);
setChoice(playerC, choiceC);
setChoice(playerD, choiceD);
}
@Test
public void test_LieutenantsOfTheGuard_1() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lieutenant);
setChoices("Yes");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 6, 6);
assertPermanentCount(playerA, "Soldier", 0);
}
@Test
public void test_LieutenantsOfTheGuard_2() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lieutenant);
setChoices("No");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 2, 2);
assertPermanentCount(playerA, "Soldier", 4);
}
@Test
public void test_LieutenantsOfTheGuard_3() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lieutenant);
setChoices("Yes", "Yes", "No", "No");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 4, 4);
assertPermanentCount(playerA, "Soldier", 2);
}
@Test
public void test_TyrantsChoice_AI_Normal() {
addCard(Zone.HAND, playerA, tyrant); // {1}{B}
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
// ai play
// opponents must have more votes so final result is sacrifice (best for opponents)
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, tyrant);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerB);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerC);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerD);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, tyrant, 1);
assertLife(playerA, 20);
assertLife(playerB, 20);
assertLife(playerC, 20);
assertLife(playerD, 20);
}
@Test
@Ignore // TODO: fix after merge, see player.isComputer
public void test_TyrantsChoice_AI_UnderControl() {
addCard(Zone.HAND, playerA, tyrant); // {1}{B}
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
//
addCard(Zone.HAND, playerA, illusion, 1); // {U}
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
// prepare vote control
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, illusion);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkGraveyardCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, illusion, 1);
// ai play
// you control the opponents, so votes result must be lose life (best for controller)
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, tyrant);
checkStackSize("before resolve", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 1);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerB);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerC);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerD);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, tyrant, 1);
assertLife(playerA, 20);
assertLife(playerB, 20 - 4);
assertLife(playerC, 20 - 4);
assertLife(playerD, 20 - 4);
}
@Test
public void test_BragosRepresentative() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.BATTLEFIELD, playerA, rep);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lieutenant);
setChoice(playerA, "Yes");
setChoices("Yes", "Yes", "No", "No");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 5, 5);
assertPermanentCount(playerA, "Soldier", 2);
}
@Test
public void test_BallotBroker() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.BATTLEFIELD, playerA, broker);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lieutenant);
setChoices("Yes", "Yes", "No", "No");
setChoice(playerA, "Yes"); // to have an additional vote
setChoice(playerA, "No"); // the additional vote
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 4, 4);
assertPermanentCount(playerA, "Soldier", 3);
}
@Test
public void test_IllusionOfChoice_Single() {
addCard(Zone.BATTLEFIELD, playerA, "Tundra", 6);
addCard(Zone.HAND, playerA, illusion);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, illusion);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lieutenant);
setChoice(playerA, "Yes", 4);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 6, 6);
assertPermanentCount(playerA, "Soldier", 0);
}
@Test
public void test_IllusionOfChoice_WithBragosRepresentative() {
addCard(Zone.BATTLEFIELD, playerA, "Tundra", 6);
addCard(Zone.BATTLEFIELD, playerB, rep);
addCard(Zone.HAND, playerA, illusion);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, illusion);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lieutenant);
setChoice(playerA, "Yes", 5);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 7, 7);
assertPermanentCount(playerA, "Soldier", 0);
}
@Test
public void test_IllusionOfChoice_Double() {
addCard(Zone.BATTLEFIELD, playerA, "Tundra", 6);
addCard(Zone.BATTLEFIELD, playerB, "Island");
addCard(Zone.HAND, playerA, illusion);
addCard(Zone.HAND, playerB, illusion);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, illusion);
castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, illusion);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lieutenant);
setChoice(playerB, "Yes", 4);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 6, 6);
assertPermanentCount(playerA, "Soldier", 0);
}
@Test
public void test_GrudgeKeeper_1() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.BATTLEFIELD, playerA, keeper);
addCard(Zone.BATTLEFIELD, playerB, rep);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lieutenant);
setChoice(playerB, "Yes");
setChoices("Yes", "No", "No", "No");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 4, 4);
assertPermanentCount(playerA, "Soldier", 3);
assertLife(playerA, 20);
assertLife(playerB, 20 - 2);
assertLife(playerC, 20 - 2);
assertLife(playerD, 20 - 2);
}
@Test
public void test_GrudgeKeeper_2() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
addCard(Zone.BATTLEFIELD, playerA, keeper);
addCard(Zone.BATTLEFIELD, playerA, rep);
addCard(Zone.BATTLEFIELD, playerB, rep);
addCard(Zone.HAND, playerA, lieutenant);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lieutenant);
setChoice(playerA, "No");
setChoice(playerB, "No");
setChoices("Yes");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, lieutenant, 6, 6);
assertPermanentCount(playerA, "Soldier", 2);
assertLife(playerA, 20);
assertLife(playerB, 20);
assertLife(playerC, 20);
assertLife(playerD, 20);
}
}

View file

@ -0,0 +1,21 @@
package org.mage.test.serverside.base;
import mage.constants.RangeOfInfluence;
import org.mage.test.player.TestComputerPlayer7;
import org.mage.test.player.TestPlayer;
/**
* See more details in CardTestPlayerBaseWithAIHelps
*
* @author JayDi85
*/
public abstract class CardTestCommander4PlayersWithAIHelps extends CardTestCommander4Players {
@Override
protected TestPlayer createPlayer(String name, RangeOfInfluence rangeOfInfluence) {
// use same RangeOfInfluence.ALL as CardTestCommander4Players do
TestPlayer testPlayer = new TestPlayer(new TestComputerPlayer7(name, RangeOfInfluence.ALL, 6));
testPlayer.setAIPlayer(false); // AI can't play it by itself, use AI commands
return testPlayer;
}
}

View file

@ -5,9 +5,7 @@ import org.mage.test.player.TestComputerPlayerMonteCarlo;
import org.mage.test.player.TestPlayer;
/**
* Base class but with Monte Carlo computer player to test single AI commands (it's different from full AI simulation from CardTestPlayerBaseAI):
* 1. AI don't play normal priorities (you must use ai*** commands to play it);
* 2. AI will choose in non strict mode (it's simulated ComputerPlayerMCTS, not simple ComputerPlayer from basic tests)
* See more details in CardTestPlayerBaseWithAIHelps
*
* @author JayDi85
*/