Merge branch 'master' into 'master'

[Bug] - Allow games that throw exceptions or timer runs out to allow GameOutcomes to return with a NPE being thrown

See merge request core-developers/forge!3264
This commit is contained in:
Michael Kamensky
2020-10-14 04:21:48 +00:00
2 changed files with 66 additions and 75 deletions

View File

@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
@@ -33,7 +33,7 @@ import java.util.Map.Entry;
* <p> * <p>
* GameInfo class. * GameInfo class.
* </p> * </p>
* *
* @author Forge * @author Forge
* @version $Id: GameOutcome.java 17608 2012-10-20 22:27:27Z Max mtg $ * @version $Id: GameOutcome.java 17608 2012-10-20 22:27:27Z Max mtg $
*/ */
@@ -47,7 +47,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
public final List<PaperCard> lostCards; public final List<PaperCard> lostCards;
public final List<PaperCard> wonCards; public final List<PaperCard> wonCards;
private AnteResult(List<PaperCard> cards, boolean won) { private AnteResult(List<PaperCard> cards, boolean won) {
// Need empty lists for other results for addition of change ownership cards // Need empty lists for other results for addition of change ownership cards
if (won) { if (won) {
@@ -67,15 +67,20 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
this.lostCards.addAll(cards); this.lostCards.addAll(cards);
} }
public static AnteResult won(List<PaperCard> cards) { return new AnteResult(cards, true); } public static AnteResult won(List<PaperCard> cards) {
public static AnteResult lost(List<PaperCard> cards) { return new AnteResult(cards, false); } return new AnteResult(cards, true);
}
public static AnteResult lost(List<PaperCard> cards) {
return new AnteResult(cards, false);
}
} }
private int lastTurnNumber = 0; private int lastTurnNumber = 0;
private int lifeDelta = 0; private int lifeDelta = 0;
private int winningTeam = -1; private int winningTeam = -1;
private final HashMap<RegisteredPlayer, PlayerStatistics> playerRating = new HashMap<>(); private final HashMap<RegisteredPlayer, PlayerStatistics> playerRating = new HashMap<>();
private final HashMap<RegisteredPlayer, String> playerNames = new HashMap<>(); private final HashMap<RegisteredPlayer, String> playerNames = new HashMap<>();
public final Map<RegisteredPlayer, AnteResult> anteResult = new HashMap<>(); public final Map<RegisteredPlayer, AnteResult> anteResult = new HashMap<>();
@@ -83,21 +88,22 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
public GameOutcome(GameEndReason reason, final Iterable<Player> players) { public GameOutcome(GameEndReason reason, final Iterable<Player> players) {
winCondition = reason; winCondition = reason;
calculateLifeDelta(players);
int winnersHealth = 0;
int opponentsHealth = 0;
for (final Player p : players) { for (final Player p : players) {
this.playerRating.put(p.getRegisteredPlayer(), p.getStats()); this.playerRating.put(p.getRegisteredPlayer(), p.getStats());
this.playerNames.put(p.getRegisteredPlayer(), p.getName()); this.playerNames.put(p.getRegisteredPlayer(), p.getName());
if (p.getOutcome().hasWon() && winCondition == GameEndReason.AllOpposingTeamsLost) { if (winCondition == GameEndReason.AllOpposingTeamsLost && p.getOutcome().hasWon()) {
// Only mark the WinningTeam when "Team mode" is on. // Only mark the WinningTeam when "Team mode" is on.
winningTeam = p.getTeam(); winningTeam = p.getTeam();
} }
} }
// Unable to calculate lifeDelta between a winning and losing player whe a draw is in place
if (winCondition == GameEndReason.Draw) return;
int winnersHealth = 0;
int opponentsHealth = 0;
for (final Player p : players) { for (final Player p : players) {
if (p.getTeam() == winningTeam) { if (p.getTeam() == winningTeam) {
winnersHealth += p.getLife(); winnersHealth += p.getLife();
@@ -106,22 +112,22 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
} }
} }
calculateLifeDelta(players);
lifeDelta = Math.max(0, winnersHealth - opponentsHealth); lifeDelta = Math.max(0, winnersHealth - opponentsHealth);
} }
private void calculateLifeDelta(Iterable<Player> players) { private void calculateLifeDelta(Iterable<Player> players) {
int opponentsHealth = 0; int opponentsHealth = 0;
int winnersHealth = 0; int winnersHealth = 0;
for (Player p : players) { for (Player p : players) {
if (p.getOutcome().hasWon()) { if (p.getOutcome().hasWon()) {
winnersHealth += p.getLife(); winnersHealth += p.getLife();
} } else {
else {
opponentsHealth += p.getLife(); opponentsHealth += p.getLife();
} }
} }
lifeDelta = Math.max(0, winnersHealth - opponentsHealth); lifeDelta = Math.max(0, winnersHealth - opponentsHealth);
} }
@@ -150,7 +156,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
/** /**
* Gets the winner. * Gets the winner.
* *
* @return the winner * @return the winner
*/ */
public LobbyPlayer getWinningLobbyPlayer() { public LobbyPlayer getWinningLobbyPlayer() {
@@ -169,7 +175,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
* distinguish between human player names (a problem for hotseat games). * distinguish between human player names (a problem for hotseat games).
*/ */
public RegisteredPlayer getWinningPlayer() { public RegisteredPlayer getWinningPlayer() {
for(Entry<RegisteredPlayer, PlayerStatistics> pair : playerRating.entrySet()) { for (Entry<RegisteredPlayer, PlayerStatistics> pair : playerRating.entrySet()) {
if (pair.getValue().getOutcome().hasWon()) { if (pair.getValue().getOutcome().hasWon()) {
return pair.getKey(); return pair.getKey();
} }
@@ -196,7 +202,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
/** /**
* Gets the win spell effect. * Gets the win spell effect.
* *
* @return the win spell effect * @return the win spell effect
*/ */
public String getWinSpellEffect() { public String getWinSpellEffect() {
@@ -227,7 +233,7 @@ public final class GameOutcome implements Iterable<Entry<RegisteredPlayer, Playe
public List<String> getOutcomeStrings() { public List<String> getOutcomeStrings() {
List<String> outcomes = Lists.newArrayList(); List<String> outcomes = Lists.newArrayList();
for(RegisteredPlayer player : playerNames.keySet()) { for (RegisteredPlayer player : playerNames.keySet()) {
outcomes.add(getOutcomeString(player)); outcomes.add(getOutcomeString(player));
} }
return outcomes; return outcomes;

View File

@@ -1,34 +1,33 @@
package forge.view; package forge.view;
import forge.LobbyPlayer;
import forge.deck.Deck;
import forge.deck.DeckGroup;
import forge.deck.io.DeckSerializer;
import forge.game.*;
import forge.game.player.RegisteredPlayer;
import forge.model.FModel;
import forge.player.GamePlayerUtil;
import forge.properties.ForgeConstants;
import forge.tournament.system.*;
import forge.util.Lang;
import forge.util.TextUtil;
import forge.util.WordUtil;
import forge.util.storage.IStorage;
import org.apache.commons.lang3.time.StopWatch;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import forge.LobbyPlayer;
import forge.deck.DeckGroup;
import forge.game.*;
import forge.properties.ForgeConstants;
import forge.tournament.system.*;
import forge.util.TextUtil;
import forge.util.WordUtil;
import forge.util.storage.IStorage;
import org.apache.commons.lang3.time.StopWatch;
import forge.deck.Deck;
import forge.deck.io.DeckSerializer;
import forge.game.player.RegisteredPlayer;
import forge.model.FModel;
import forge.player.GamePlayerUtil;
import forge.util.Lang;
public class SimulateMatch { public class SimulateMatch {
public static void simulate(String[] args) { public static void simulate(String[] args) {
FModel.initialize(null, null); FModel.initialize(null, null);
System.out.println("Simulation mode"); System.out.println("Simulation mode");
if(args.length < 4) { if (args.length < 4) {
argumentHelp(); argumentHelp();
return; return;
} }
@@ -49,11 +48,9 @@ public class SimulateMatch {
options = new ArrayList<>(); options = new ArrayList<>();
params.put(a.substring(1), options); params.put(a.substring(1), options);
} } else if (options != null) {
else if (options != null) {
options.add(a); options.add(a);
} } else {
else {
System.err.println("Illegal parameter usage"); System.err.println("Illegal parameter usage");
return; return;
} }
@@ -97,7 +94,7 @@ public class SimulateMatch {
int i = 1; int i = 1;
if (params.containsKey("d")) { if (params.containsKey("d")) {
for(String deck : params.get("d")) { for (String deck : params.get("d")) {
Deck d = deckFromCommandLineParameter(deck, type); Deck d = deckFromCommandLineParameter(deck, type);
if (d == null) { if (d == null) {
System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start")); System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start"));
@@ -130,7 +127,7 @@ public class SimulateMatch {
if (matchSize != 0) { if (matchSize != 0) {
int iGame = 0; int iGame = 0;
while(!mc.isMatchOver()) { while (!mc.isMatchOver()) {
// play games until the match ends // play games until the match ends
simulateSingleMatch(mc, iGame, outputGamelog); simulateSingleMatch(mc, iGame, outputGamelog);
iGame++; iGame++;
@@ -159,38 +156,26 @@ public class SimulateMatch {
} }
public static void simulateSingleMatch(final Match mc, int iGame, boolean outputGamelog) { public static void simulateSingleMatch(final Match mc, int iGame, boolean outputGamelog) {
final StopWatch sw = new StopWatch(); final StopWatch sw = new StopWatch();
sw.start(); sw.start();
final Game g1 = mc.createGame(); final Game g1 = mc.createGame();
// will run match in the same thread // will run match in the same thread
long startTime = System.currentTimeMillis();
try { try {
TimeLimitedCodeBlock.runWithTimeout(new Runnable() { TimeLimitedCodeBlock.runWithTimeout(() -> {
@Override mc.startGame(g1);
public void run() { sw.stop();
mc.startGame(g1);
sw.stop();
}
}, 120, TimeUnit.SECONDS); }, 120, TimeUnit.SECONDS);
} } catch (TimeoutException e) {
catch (TimeoutException e) {
System.out.println("Stopping slow match as draw"); System.out.println("Stopping slow match as draw");
g1.setGameOver(GameEndReason.Draw); } catch (Exception | StackOverflowError e) {
sw.stop();
}catch (Exception e){
e.printStackTrace(); e.printStackTrace();
g1.setGameOver(GameEndReason.Draw); } finally {
sw.stop();
}catch(StackOverflowError e){
g1.setGameOver(GameEndReason.Draw); g1.setGameOver(GameEndReason.Draw);
sw.stop(); sw.stop();
} }
List<GameLogEntry> log; List<GameLogEntry> log;
if (outputGamelog) { if (outputGamelog) {
log = g1.getGameLog().getLogEntries(null); log = g1.getGameLog().getLogEntries(null);
@@ -198,15 +183,15 @@ public class SimulateMatch {
log = g1.getGameLog().getLogEntries(GameLogEntryType.MATCH_RESULTS); log = g1.getGameLog().getLogEntries(GameLogEntryType.MATCH_RESULTS);
} }
Collections.reverse(log); Collections.reverse(log);
for(GameLogEntry l : log) { for (GameLogEntry l : log) {
System.out.println(l); System.out.println(l);
} }
// If both players life totals to 0 in a single turn, the game should end in a draw // If both players life totals to 0 in a single turn, the game should end in a draw
if(g1.getOutcome().isDraw()){ if (g1.getOutcome().isDraw()) {
System.out.println(String.format("Game %d ended in a Draw! Took %d ms.", 1+iGame, sw.getTime())); System.out.printf("\nGame Result: Game %d ended in a Draw! Took %d ms.%n", 1 + iGame, sw.getTime());
} else { } else {
System.out.println(String.format("\nGame %d ended in %d ms. %s has won!\n", 1+iGame, sw.getTime(), g1.getOutcome().getWinningLobbyPlayer().getName())); System.out.printf("\nGame Result: Game %d ended in %d ms. %s has won!\n%n", 1 + iGame, sw.getTime(), g1.getOutcome().getWinningLobbyPlayer().getName());
} }
} }
@@ -219,7 +204,7 @@ public class SimulateMatch {
List<TournamentPlayer> players = new ArrayList<>(); List<TournamentPlayer> players = new ArrayList<>();
int numPlayers = 0; int numPlayers = 0;
if (params.containsKey("d")) { if (params.containsKey("d")) {
for(String deck : params.get("d")) { for (String deck : params.get("d")) {
Deck d = deckFromCommandLineParameter(deck, rules.getGameType()); Deck d = deckFromCommandLineParameter(deck, rules.getGameType());
if (d == null) { if (d == null) {
System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start")); System.out.println(TextUtil.concatNoSpace("Could not load deck - ", deck, ", match cannot start"));
@@ -239,7 +224,7 @@ public class SimulateMatch {
if (!folder.isDirectory()) { if (!folder.isDirectory()) {
System.out.println("Directory not found - " + foldName); System.out.println("Directory not found - " + foldName);
} else { } else {
for(File deck : folder.listFiles(new FilenameFilter() { for (File deck : folder.listFiles(new FilenameFilter() {
@Override @Override
public boolean accept(File dir, String name) { public boolean accept(File dir, String name) {
return name.endsWith(".dck"); return name.endsWith(".dck");
@@ -281,16 +266,16 @@ public class SimulateMatch {
System.out.println(TextUtil.concatNoSpace("Starting a ", tournament, " tournament with ", System.out.println(TextUtil.concatNoSpace("Starting a ", tournament, " tournament with ",
String.valueOf(numPlayers), " players over ", String.valueOf(numPlayers), " players over ",
String.valueOf(tourney.getTotalRounds()), " rounds")); String.valueOf(tourney.getTotalRounds()), " rounds"));
while(!tourney.isTournamentOver()) { while (!tourney.isTournamentOver()) {
if (tourney.getActiveRound() != curRound) { if (tourney.getActiveRound() != curRound) {
if (curRound != 0) { if (curRound != 0) {
System.out.println(TextUtil.concatNoSpace("End Round - ", String.valueOf(curRound))); System.out.println(TextUtil.concatNoSpace("End Round - ", String.valueOf(curRound)));
} }
curRound = tourney.getActiveRound(); curRound = tourney.getActiveRound();
System.out.println(); System.out.println();
System.out.println(TextUtil.concatNoSpace("Round ", String.valueOf(curRound) ," Pairings:")); System.out.println(TextUtil.concatNoSpace("Round ", String.valueOf(curRound), " Pairings:"));
for(TournamentPairing pairing : tourney.getActivePairings()) { for (TournamentPairing pairing : tourney.getActivePairings()) {
System.out.println(pairing.outputHeader()); System.out.println(pairing.outputHeader());
} }
System.out.println(); System.out.println();
@@ -311,10 +296,10 @@ public class SimulateMatch {
int iGame = 0; int iGame = 0;
while (!mc.isMatchOver()) { while (!mc.isMatchOver()) {
// play games until the match ends // play games until the match ends
try{ try {
simulateSingleMatch(mc, iGame, outputGamelog); simulateSingleMatch(mc, iGame, outputGamelog);
iGame++; iGame++;
} catch(Exception e) { } catch (Exception e) {
exceptions++; exceptions++;
System.out.println(e.toString()); System.out.println(e.toString());
if (exceptions > 5) { if (exceptions > 5) {
@@ -349,10 +334,10 @@ public class SimulateMatch {
private static Deck deckFromCommandLineParameter(String deckname, GameType type) { private static Deck deckFromCommandLineParameter(String deckname, GameType type) {
int dotpos = deckname.lastIndexOf('.'); int dotpos = deckname.lastIndexOf('.');
if(dotpos > 0 && dotpos == deckname.length()-4) { if (dotpos > 0 && dotpos == deckname.length() - 4) {
String baseDir = type.equals(GameType.Commander) ? String baseDir = type.equals(GameType.Commander) ?
ForgeConstants.DECK_COMMANDER_DIR : ForgeConstants.DECK_CONSTRUCTED_DIR; ForgeConstants.DECK_COMMANDER_DIR : ForgeConstants.DECK_CONSTRUCTED_DIR;
return DeckSerializer.fromFile(new File(baseDir+deckname)); return DeckSerializer.fromFile(new File(baseDir + deckname));
} }
IStorage<Deck> deckStore = null; IStorage<Deck> deckStore = null;