oyachai / HearthSim

Generic Hearthstone game simulator
MIT License
316 stars 59 forks source link

Invalid minion indexes #112

Open slaymaker1907 opened 9 years ago

slaymaker1907 commented 9 years ago

By using randomly generated decks, I've found a very strange bug that occurs very often with a variety of cards, but especially Dark Cultist. What seems to happen is that it first targets a valid minion, however, somewhere along the line the board state loses this minion so when it tries to find the index again it for some reason returns either an index greater than the size of the lists or -1 (which means it couldn't find it in the player's minion list).

I'm not entirely sure why this is happening, but I'm going to try and add functionality to the random deck generator (DeckFactory under util) to see if this issue can be isolated to a few cards or whether it is a problem on the part of the AI itself.

I would appreciate any insight into this because right now this bug is causing about 50% of random deck games to generate an exception.

slaymaker1907 commented 9 years ago

It also seems to appear that at some point, new BoardStates are being generated without a target character being updated. Since we use IdentityLinkedList for minions, that means that the game thinks that the target character is no longer present. If that is the case, a potential fix might be to assign each card an ID which would remain the same when a BoardState is copied but be different in the case that there are two copies of the same Minion on the field at the same time.

oyachai commented 9 years ago

Thanks for spotting the bug. It looks like a bug in the way deathrattles that target random minions are handled. I've committed a fix in master, so pls do a pull and let me know how it works.

slaymaker1907 commented 9 years ago

It still seems to be failing, though it appears that the Dark Cultist isn't the cause anymore. I'll post the code I've been using here since it's not really code that I think should be in the master branch. The int returned is just to see how many games were ran if the function finishes without throwing an exception (though it hasn't even gotten close to using five minutes yet on my machine).

public static void main(String[] args) throws HSException 
{
    DeckFactory factory = new DeckFactory.DeckFactoryBuilder().buildDeckFactory();
    Hero hero = new TestHero();
    runTest(factory, hero, 5.0);
}

public static int runTest(DeckFactory factory, Hero hero, double minutesToRun) throws HSException
{
    int result = 0;
    long start = System.currentTimeMillis();
    BruteForceSearchAI ai = BruteForceSearchAI.buildStandardAI2();
    while((System.currentTimeMillis() - start) / 60_000.0 < minutesToRun)
    {
        PlayerModel model0 = new PlayerModel((byte) 0, "", hero.deepCopy(), factory.generateRandomDeck());
        PlayerModel model1 = new PlayerModel((byte) 1, "", hero.deepCopy(), factory.generateRandomDeck());
        Game testGame = new Game(model0, model1, ai.deepCopy(), ai.deepCopy());
        testGame.runGame();
        result++;
    }

    return result;
}
oyachai commented 9 years ago

It looks like a few different things are going on. I'll work though them one by one in the next few days.

MrHen commented 9 years ago

There is a test case you can enable that does something very similar. It builds a set of random cards for each player and runs through a full game.

https://github.com/oyachai/HearthSim/blob/master/src/test/groovy/com/hearthsim/test/GameRepeatableSpec.groovy#L175

It's turned off by default because it isn't repeatable but it often finds interesting bugs.