package com.hearthsim;
import com.hearthsim.card.Deck;
import com.hearthsim.card.minion.Hero;
import com.hearthsim.exception.HSException;
import com.hearthsim.exception.HSInvalidParamFileException;
import com.hearthsim.exception.HSParamNotFoundException;
import com.hearthsim.io.ParamFile;
import com.hearthsim.model.PlayerModel;
import com.hearthsim.player.playercontroller.ArtificialPlayer;
import com.hearthsim.results.GameResult;
import com.hearthsim.results.GameResultSummary;
import java.io.*;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Observable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public abstract class HearthSimBase extends Observable {
private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(this
.getClass());
int numSims_;
int numThreads_;
private String gameResultFileName_;
protected Path rootPath_;
protected Path aiParamFilePath0_;
protected Path aiParamFilePath1_;
/**
* Constructor
*
* @param setupFilePath
* @throws HSInvalidParamFileException
* @throws HSParamNotFoundException
* @throws IOException
*/
HearthSimBase(Path setupFilePath) throws HSInvalidParamFileException,
HSParamNotFoundException, IOException {
rootPath_ = setupFilePath.getParent();
ParamFile masterParam = new ParamFile(setupFilePath);
numSims_ = masterParam.getInt("num_simulations", 40000);
numThreads_ = masterParam.getInt("num_threads", 1);
aiParamFilePath0_ = FileSystems.getDefault()
.getPath(rootPath_.toString(),
masterParam.getString("aiParamFilePath0"));
aiParamFilePath1_ = FileSystems.getDefault()
.getPath(rootPath_.toString(),
masterParam.getString("aiParamFilePath1"));
gameResultFileName_ = masterParam.getString("output_file",
"gameres.txt");
}
public HearthSimBase(int numSimulations, int numThreads) {
numSims_ = numSimulations;
numThreads_ = numThreads;
}
/**
* Run a single game.
*
* Must be overridden by a concrete subclass. The subclass's job is to set
* up the decks and the AIs and to call runSigleGame(ArtificialPlayer, Hero,
* Deck, ArtificialPlayer, Hero, Deck).
*
* @return
* @throws HSException
* @throws IOException
*/
protected abstract GameResult runSingleGame(int gameId) throws HSException,
IOException;
protected GameResult runSingleGame(ArtificialPlayer ai0, Hero hero0,
Deck deck0, ArtificialPlayer ai1, Hero hero1, Deck deck1)
throws HSException {
return this.runSingleGame(ai0, hero0, deck0, ai1, hero1, deck1, 0);
}
/**
* Run a single game
*
* @param ai0
* AI for player 0
* @param hero0
* Hero class for player 0
* @param deck0
* Deck for player 0
* @param ai1
* AI for player 1
* @param hero1
* Hero class for player 1
* @param deck1
* Deck for player 1
* @param shufflePlayOrder
* Randomizes the play order if set to true
* @return
* @throws HSException
*/
protected GameResult runSingleGame(ArtificialPlayer ai0, Hero hero0,
Deck deck0, ArtificialPlayer ai1, Hero hero1, Deck deck1,
boolean shufflePlayOrder) throws HSException {
// Shuffle the decks!
deck0.shuffle();
deck1.shuffle();
PlayerModel playerModel0 = new PlayerModel((byte) 0, "player0", hero0,
deck0);
PlayerModel playerModel1 = new PlayerModel((byte) 1, "player1", hero1,
deck1);
Game game = new Game(playerModel0, playerModel1, ai0, ai1,
shufflePlayOrder);
return game.runGame();
}
protected GameResult runSingleGame(ArtificialPlayer ai0, Hero hero0,
Deck deck0, ArtificialPlayer ai1, Hero hero1, Deck deck1,
int firstPlayerId) throws HSException {
// Shuffle the decks!
deck0.shuffle();
deck1.shuffle();
PlayerModel playerModel0 = new PlayerModel((byte) 0, "player0", hero0,
deck0);
PlayerModel playerModel1 = new PlayerModel((byte) 1, "player1", hero1,
deck1);
Game game = new Game(playerModel0, playerModel1, ai0, ai1,
firstPlayerId);
return game.runGame();
}
public void run() throws IOException, InterruptedException {
long simStartTime = System.currentTimeMillis();
Path outputFilePath = FileSystems.getDefault().getPath(
rootPath_.toString(), gameResultFileName_);
Writer writer = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(outputFilePath.toString()), "utf-8"));
ExecutorService taskQueue = Executors
.newFixedThreadPool(this.numThreads_);
for (int i = 0; i < numSims_; ++i) {
GameThread gThread = new GameThread(i, writer);
taskQueue.execute(gThread);
}
taskQueue.shutdown();
taskQueue.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
writer.close();
long simEndTime = System.currentTimeMillis();
double simDeltaTimeSeconds = (simEndTime - simStartTime) / 1000.0;
String prettyDeltaTimeSeconds = String.format("%.2f",
simDeltaTimeSeconds);
double secondsPerGame = simDeltaTimeSeconds / numSims_;
String prettySecondsPerGame = String.format("%.2f", secondsPerGame);
log.info(
"completed simulation of {} games in {} seconds on {} thread(s)",
numSims_, prettyDeltaTimeSeconds, numThreads_);
log.info("average time per game: {} seconds", prettySecondsPerGame);
}
@Override
public synchronized void notifyObservers(Object o) {
super.notifyObservers(o);
}
@Override
public synchronized void notifyObservers() {
this.notifyObservers(null);
}
public class GameThread implements Runnable {
final int gameId_;
Writer writer_;
public GameThread(int gameId, Writer writer) {
gameId_ = gameId;
writer_ = writer;
}
@Override
public void run() {
try {
GameResult res = runSingleGame(gameId_);
if (writer_ != null) {
synchronized (writer_) {
GameResultSummary grs = new GameResultSummary(res);
writer_.write(grs.toJSON().toString() + "\n");
writer_.flush();
}
}
setChanged();
notifyObservers(res);
} catch (HSException | IOException e) {
log.error("Error! " + e);
}
}
}
}