Table of Contents
Januari 2014 blog archive
This page contains all the blog posts from january 2014. To read the most recent blog posts, click here.
26/01 - measuring simulation accuracy
Very little new code has been added to the game in the past weeks, mostly i've been working out some details of the first AI setup i want to test. The setup uses full simulations (see last week's blogpost) and then based on the action the AI is doing (playing a new card, or activating something), certain elements of the simulation are interpreted and compared with the current state of the board to see what the impact is of this action on the board (and then see if this impact is beneficial to the AI, and to what extent).
I've been setting up the outline of the different steps that need to be present in an AI like this in detail so i can see if there are issues or problems that might arise. Thus far, there have been quite a few things that i've run into that caused me to review steps or even the entire setup of a full-simulation AI. Slowly, things are coming together however, but it might still be a bit before i can actually see the AI in action in-game.
Right now the AI's second layer of code is already present in-game - this is code that gets triggered from the main AI framework (ie the first layer - see blogpost from 2 weeks ago) that contains the specifics for the major actions the AI can undertake during its turn, these are : playing a ship or structure, playing an action, activating an acti and recycling structures. These actions will then call upon the final layer of AI code, which is the one that calculates scores for these actions so that the AI can decide what to do. Right now, there is placeholder code for this final layer, that simply generates random scores, allowing me to test the setup thus far. So the AI is already capable of playing ships, structs and actions, activating abilities, recycling and deciding what to discard at the end of a turn, but its decisions are all random. So the AI isn't very smart at this point, but at least this allows me to make sure that the AI is capable of doing all these things.
One of the things the AI did in HDx that is no longer going to be possible in HD3, is something that happens after calculating how much damage the AI can do during its combat phase. If the AI saw that it could do enough damage to defeat this player, it wouldn't bother looking at cards it could play or activate and it would go straight to the combat phase.
This mainly matters in games where the AI might face multiple opponents : the AI could be capable of defeating one opponent, but if it goes straight to the combat phase, not playing any ships, it gives the other opponent (the one that doesn't get defeated) a big advantage over itself. At the start of each AI cycle, the AI will run a simulation of the combat phase, which takes into account how many combat phases the AI has, and even if the AI has a combat phase at all. The AI will then apply a score to the outcome of the battle, and then it will start doing sims for all other cards it can play or activate. Once it's done with all of these steps, the AI has access to a set of scores, which includes the score of the combat phase. At this point, the AI can still decide to not play any cards and start its combat phase, but at least it has made sure that the score of the combat phase is higher than the score of any card it could play or activate.
While setting up the simulation of the combat phase i ran into another thing that could be a minor issue. It's thanks to preparing this in detail on paper that i came across this potential problem. It's very likely that this issue never produces effects that can alter the outcome of a game, as it's more a technical thing, but it does show that simulations of a certain state of the game can still result in differences between the result of the sim and reality.
During combat, ships can be destroyed and when a ship is destroyed there are two important things that happen. The moment a ship's death is detected, any abilities the ship has that must trigger on death are activated. If there are auras in play that trigger when a ship dies, they too need to be checked and activated if needed. At this point, the ship's exit animation starts : this is a combination of the card itself fading out and an explosion animation playing on top of the card. For the entire duration of this animation, the ship still takes up a slot in play, but it should no longer be seen as a valid target for anything, thus this ship is placed in a special state, the 'dying' state. This matters for combat as you could have a ship with cerberus or hydra that deals damage to another ship on the board, destroying that ship - if you have a blocker in front of this dying ship, that blocker must now deal damage to the base instead of to this ship that's already dying. The dying animation is rather short, so it's possible that this ship has already been removed from the board when its blocker gets to attack, but this can't be sure, so this attacker checks the 'dying' state of the ship in front of it to know if it must attack this ship or the opposing base.
When the dying ship has completed it's animation a few more things need to happen : the ship must actually be removed from play and the card that represents this ship must go to scrap. There is now an amount of time between these two things, just as there is an amount of time between ships attacking during a combat phase.
A simulation doesn't have timers, else the AI's turn would simply take too long. All ships that need to attack still do this in the correct order, but basically no in-game time passes between the attacks of different ships. When ships are destroyed in a simulation, the first part of a ship's death (where the on-death abilities trigger) is handled instantly, just as it would in the game itself. The problem comes from the second part of a card's exit that must trigger after the ship's explosion animation. Again this animation is not present in the simulation, but here timing might be important. In a normal combat phase, when a ship is destroyed, somewhere in the course of the combat phase (or even after the combat phase), this ship will have completed it's animation and will have gone to scrap. The exact timing is hard to predict but can technically be simulated with acceptable precision (but i'm not planning to go that far with my sims, as this means i would have to start simulating timers as well). As a result of a combat phase, structs can be destroyed as well (and these will go through a similar exit setup), but cards can be discarded from hand as well (which happens instantly).
A sim of the combat phase handles all ships attacking and automatically takes care of the first part of the exit of any ship that was destroyed. Any structs that are destroyed will also go through this first part. Any cards that get discarded will automatically be transfered to the scrap instantly. When this part of the sim is done, i run a check on all cards in play in and see if any are in the 'dying' state, if so, the second part of part of the exit gets activated for all these cards. Technically this second part could be run right after the first part, but this could introduce inaccuracies, since dying ships stay in play for some period of time, and running the second part instantly basically means they leave play instantly. Running the second part after all ships have finished attacking at least simulates some time passing (and the presence of dying ships might be important for future triggers).
For a normal battle, there is no problem, the order in which cards enter scrap will be the same. For a battle where cards get discarded or structs are destroyed this is no longer true. Combat resolves from left to right, the second part of ships dying (i.e. the part where they go to scrap) is also resolved from left to right in my sim, but when a struct is destroyed at a certain point in the battle, the sim can not guarantee that the time at which this struct enters scrap, relative to other cards is correct. This is because the second part of the exit, which is run on all dying cards in the sim, first checks structs before it check ships, so structs will always enter scrap first, which is not always the case in reality. Since technically no time passes in the simulation, there is no way to determine in what order cards were destroyed. When cards get discarded during battle or in sim, these go to scrap instantly, if in reality the point in time where a discard happened was towards the end of the combat phase, a ship that was destroyed earlier might already have finished its animation and entered the scrap before this card does. In simulation, discarded cards will always end up in the scrap first.
Does all of this matter? In most cases not at all - the order of cards in scrap is only important for a few cards, and the odds that these cards need to be simulated on a boardstate that itself is already the simulation of a combat phase is even less likely. But it can't be ruled out that there will ever be an ability in play that requires these simulations to happen in this order and thus the AI might never be able to reliably predict the boardstate in this case.
Technically, i can simulate timers as well and make sure the simulation is much closer to reality, but even then i can't guarantee that the sim is 100% correct and this setup would be a fair bit more complex as well. The potential impact of this is minimal, so i'll leave things as they are and worry about them if/when cards or abilities get added to HD3 that might be affected by this in such a way that it would make the AI unreliable for these abilities.
Trying to predict the requirements of special cases like this or even of individual, yet complex, abilities is the main reason why implementing this part of the AI evolves rather slowly. I prefer to make sure that when these special cases get implemented in the future that the AI can deal with them without me having to rewrite large sections of the AI that are otherwise working correctly. This also involves setting up the importance of individual abilities towards the score of a single card. If the AI is looking for the optimal target for an action or acti and those targets are in play, then it makes sense that the AI pays no attention to the play abilities of these cards as those abilities can't influence the board anymore. However if the AI has an action that allows it to look at the contents of a hand, deck or scrap and pick a target there, play abilities are important to a certain extent since these are cards that can be played. Finally, when the AI is deciding which of its ships or structs in hand it wants to play, play abilities on these cards are even more important since they'll have an immediate effect on the board. In this case, the AI is almost always going to run simulations to get a good idea of the impact of these abilities, but in case of cards that are in another players hand, or in a deck or scrap, the AI shouldn't have to simulate the impact of each of those cards entering play - instead the AI looks at the strength and type of play ability and assigns it a score that way.
Discuss this blog post in this topic on the NULLL forums.
19/01 - boardstate simulations
I'm currently in the process of deciding the type of AI HD3 would need. In it's current state, the HDx AI isn't really suitable and i'm trying to figure out if a brand new system is needed or if the HDx AI can upgraded so it works with HD3.
In HDx (and in HDs), the AI looks at abilities on cards and assigns these scores based on what the effect would be of these abilities on the current state of the board. These scores allow the AI to compare all the cards it can play and pick the highest-scoring one since scores represent how useful this card would be to the AI once played or activated.
To be able to see the effect of a card play or activation, the AI does a local simulation. This means it tries to predict what (a part of) the board would look like after the card has been played or activated and then it compares this predicted boardstate with the current one and assigns a score to the difference. For instance, the AI has a damage action and there are several opposing ship targets in play. Simulating what happens is simply checking the amount of damage this action would do to each target and then looking at the result (did the ship die, or is it now damaged enough that it can be killed by the blocker the AI owns?). This is a local simulation since the AI only looks a certain elements, based on the type of ability it is simulating. In case of the damage action example, the AI for instance is not going to look at the state of structures on board since there's a reasonable expectation that playing a damage action on a ship target is never going to affect a structure (which is true for HDx).
Note that i said that the AI is 'trying' to predict - there are a number of things that causes the prediction to not be 100% accurate. The most obvious one is random elements - players can't predict these, so the AI can't either - and this actually hurts the AI more than the player. The prediction also does not take into account all possible elements, for instance when the damage action above were to destroy a ship, it might result in another card of the opponent receive a bonus (after all there are cards that get a bonus when ships die).
Yet, technically it should be possible for the AI to have perfect predictions, after all, the game is capable of correctly changing the boardstate as a result of an action being played - so why can't the AI do this? This is actually the biggest flaw of the AI : it doesn't use the capabilities of the game to predict the result of abilities. The reason is that the game is setup such that it can only apply the effects of abilities to the player and the AI, and it can't reverse those effects afterwards. So if the AI were to use the code the game has to generate the effects of abilities, you would actually see this happen in-game and thus change the boardstate, which obviously should only happen when the AI actually plays a card and not when the AI is still trying to figure out what it should play. The amount of code that went into the HDx AI is truly huge, yet a lot of it could be scrapped if the AI could reuse the code that handles the effects of abilities.
Local simulations by default are inaccurate, since they only look a certain elements (only those that were predicted), while others might actually need to be taken into account. This was not really a big issue in HDx, but it can be in HD3. I said above in the example that the AI doesn't really need to look at structures when it's calculating the effects of damage actions on ships, but HD3 is introducing a new class of abilities that work as triggers (these are the 'aura' abilities i've mentioned in previous blogposts). While i don't have any cards in the game yet with such abilties, i do have many ideas that might eventually be added, and i rather make sure the AI is capable of dealing with them properly beforehand so i don't have to change the AI every time a new type of trigger is added.
Imagine a card with an aura that says : "whenever a player launches a ship, this card deals 1 damage to that player's base". It doesn't feel all that powerful, but if you ignore it when you have one HP left on your base, this tiny ability could cause you to lose the game. If the AI ignores triggers completely, it might end up doing things that cause it to lose. Yet, if the game has 20-30 different triggers, the AI for a single ability gets even bigger : the AI must look if each of these triggers exist, then it must simulate the effects of these, then it must determine the danger of these effects and factor them in when setting up scores for abilities it wants to play. At this point, i already know that a setup like this is not possible - the amount of work that has to go in such an AI is simply too much. On top of that, all these attempted simulations of triggers introduce small inaccuracies since the AI still can't predict perfectly, and if you have enough small inaccuracies, the final score might be completely worthless and the AI might end up making incorrect decisions anyway.
What this comes down to is that a local simulation is going to be less accurate in HD3 than before, and that the amount of code needed to make it work well is too huge to bother.
In HD3, when you play a certain ability, or bring a new card into play and there are triggers that need to respond to this, the game will take care of these triggers and make sure they all go off when needed, and that they do so in the default order and that their effects are handled. So again, the game is capable of dealing with all this stuff correctly, yet the AI is not. There is luckily a way to let the AI use the code the game uses to resolve abilities.
First off, the AI and the human player are very similar from the point of view of the game. The main difference is that the input that game accepts from the player is external (you control the mouse and the keyboard, and the game then process the results), and the input from the AI comes from the AI code. When you click a card to play it and then choose the slot in play where it must go, a number of things happen to make this actually happen - the AI goes through these same steps, but instead of clicking cards and slots, it simply tells the game which card and which slot it wants. The advantage is that i don't have to write seperate code for the AI and the player to make things like this happen. This was the not case in HD1 and HDs, HDx completely changed this system and in HD3 the system is refined even more - all things that have to do with a player are grouped into a single datastructure : this contains the cards in the deck for this player, the cards in play, the amount of energy in stock, etc. The framework (i.e. the layout of all this data) is identical for human and AI players, so when an ability is played that affects the energy stock of a player, the code that makes this effect happen only needs to know what player is affected by this. In HD1/HDs, each ability had two versions : one when the ability targeted the AI and one when it targeted the player. Again this was changed in HD3, which is important since HD3 as around 500 abilities - that's a lot of code that didn't have to be created in two versions.
The plan is still for HD3 to support up to 4 players, which technically means there are 4 player datastructures, instead of only 2 as in HDx. Now since ability code can technically target any player, why not add some more players that are hidden from sight, just to run some simulations? That's the plan for a new AI i'm currently considering. Here is how it's supposed to work : when the AI wants to simulate the results of a cardplay or activation, it copies all players (max 4) into temporary player datastructures, then it uses the regular ability code to trigger the ability it wants to simulate and makes sure that the ability code targets one of the simulated players. That simulated player should then have the exact same boardstate as if that ability was really played. Simulated players are not visible, they only exist in memory, so the real board is not affected and the AI can compare the real board and the simulation to see if it likes the differences.
It's a bit more complex than that, but that's the general setup of a full simulation. There are some advantages :
1. accuracy : the simulation uses the same code the game uses to produce the results of ability effects, so you're sure that result of the simulation is an accurate representation of reality, so you have a good basis on which to make decisions regarding playing this card on this particular target or not.
2. less code : the AI doesn't need code to simulate things anymore, since it uses code the game already has. On top of that if an ability needs to be changed, i wouldn't have to change both the ability handler and and AI code, now only the handler needs to be changed and this makes sure the AI is up-to-date instantly.
3. simulation scope : using the game code automatically make sure all triggers that have to go off, do go off and their effects are handled as well, this means the result of the simulation takes into account all possible secondary effects of playing or activating a card.
4. simulation depth : HD3 introduces cards that can have multiple abilities of a single type (except for actis). The HD AI thus far only had to deal with cards that could only have one ability of each type. The local simulation setup can't reliable deal with multiple abilities as it would mean it would have to run a simulation ontop of the results of the first simulation in case a card has two abilities of a single type. If the first simulation is already inaccurate, the second one will even be worse. A full simulation simply runs all abilities on a card in the correct order, the outcome is again a correct representation of reality, even though multiple things were simulated.
5. phases : it's technically possible to activate the code that handles the automatic aspects of the game on a simulation. Example : the upkeep phase, where all auto abilities are handled, is completely automatic, players don't have to do anything here, but there could be a ton of abilities that have to go off as well as additional triggers as a result of that. Trying to simulate this in the old way is simply too much work, under the new system, it would be a matter of activating the code that handles the upkeep phase and make sure that code targets a simulated player. While it don't see i direct use for simulating an upkeep phase, there is a hidden phase at the end of a turn (after combat has completed), where all the effects that must trigger at the end of a turn are handled - it might be important to simulate this phase as well when the AI needs to know what the boardstate would look like after a combat phase when it's trying to decide which player it should attack.
6. environments : the ability to simulate phases in a turn has another advantage : it allows the AI to take into account the effects of any deckmods that are present, since these usually trigger at specific points in a turn. In HDx, the AI almost completely ignores extra special effects from environments.
There are also disadvantages that come with this setup :
1. loss of detail : if a bunch of triggers go off, you only see their result in the simulation, but unless you go look for cards with triggers, you don't really know in what order the triggers went off and in what way they individually contributed to the final result. In some cases this might be important - there are no triggers in game yet, so i can't predict this. A similar thing happens as a result of cards possibly having multiple abilities of the same type - the effects of these abilities could overlap or influence each other and thus the effect of each seperate ability will be hard or even impossible to determine from the final simulation.
2. interpretating results : in a local simulation, you only simulate what gives rise to results you want to know about, this means you get a small set of data you need to interpretate. In case of a full boardstate simulation, the entire board is now something that possibly needs to be interpretated. You can still ignore certain elements based on the kind of ability you play, but if you want accuracy, you'll have to look at everything that changed on the board, and assign a score to it that indicates how important this change is to you. The code that needs to do this scanning an interpreting of changed elements in the boardstate will be vastly more complex than the HDx code.
3. code complexity : even though things get simpler in certain parts of the game, others will get more complex - it's especially important to make sure that a simulation has absolutely no effect on the real game in progress. A simple example : when the AI plays a card, you'll get a message telling what happened, obviously you don't want to see these messages appear when the AI is simulating things. Preparing and running a simulation also takes up more work since all players (thus all their cards) need to be copied over to temporary player structures, and this needs to be repeated for every valid target of the ability being simulated, so you can compare targets to see which one is the best. As a result, the duration of AI turns might increase noticably. For some actions (like playing a ship), it might be interesting to run multiple simulations, which greatly increases the complexity of the code that needs to keep track of all this and that needs to grant scores to the different sims (I'll give an example of this below).
4. random elements : even though the AI was never 100% accurate, in part due to abilities with random components, a full sim setup will suffer more from this. Technically you could run a simulation for each possibility of the random component of the ability, and average the results. Example : you play a ship that upon entry deals between 3 and 5 damage to opposing base. So this ship has 3 possibilties : it deals 3, 4 or 5 damage. You could simulate all 3 and average the results, but if the example where the ship deals 5 damage is the only one where the opposing base dies, the average is wrongly skewed towards this ability having a huge impact. There is a solution, but it would mean that the AI would be cheating somewhat. The actual effect is in reality quite minimal since i'm continuously lowering the number and strength of abilities with random components (since it's also hard for players to predict them reliably). The solution is to use a pseudo-random number generator that is guaranteed to generate the same sequence of numbers every time for a given seed value. If the AI is given access to the outcome of this generator it knows that when this ship were to played next, how much damage it would do - in other words, the simulation can predict the future, which is important if you want a sim that's 100% correct. But it's a bit like the AI is cheating - the real impact of this might be small, but i need to look into it in more detail. Another element where this would be important is environments, which often have random elements - this would allow the AI to predict what an environment would do next and take it into account properly.
5. invisibility : the entire simulation runs in memory, which is thus invisible to players as it should be, but it's also a lot trickier to monitor this compared to a local sim that's much smaller in scope. So debugging will be even harder than it was in HDx, even more due to the AI taking into account more elements to make its decisions.
Multiple simulations - a big advantage of being sure that the sim is 100% correct is that you can use this sim as the basis of a new sim. Example : the AI plays a ship. What is important when you play a ship? Abilities that trigger when the ship enters play, auto and aura abilities, as well as actis on the ship, but also how well this ship will perform in combat : can it protect your base from a dangerous attacker, can it destroy an attacker, can it destroy the opposing base? Knowing these things helps in deciding where to play this new ship. Players who play a new ship are likely to run a quick calculation in their head, trying to figure out if this ship will at least survive a certain number of turns, or how long it would take before their new ship has defeated the opposing blocker, etc. It would be nice if the AI could do this as well, in HDx the AI doesn't do this as this is more complex to than it sounds if you want to make sure the results are 100% correct (auto abilities on other cards can affect both ships, fi).
Technically the new AI can : in the same way that ability handler code can target a single player, so can the code that handles the phases of a turn. So the AI could simulate a ship entering play, then it could simulate a combat phase, then it could simulate the end of turn (since there will be abilities that trigger at end of turn), then it could simulate the upkeep, draw, attack, end-of-turn phases of the opponent. All of this to see if the ship survives one turn or if it has destroyed its blocker or if the opposing base died. Then you can simulate your own upkeep phase to see the impact of any auto abilities on the card.
But it's a complex thing to do, and not always ships are intended to be in battle, so for some ships, the fact that it enters play and has a certain play ability is more important than how it performs in battle. Or perhaps the ship has a very powerful acti ability and you don't really care about it's combat capabilities. All of this means that different elements of the ship can play different roles in the final score of this ship - and running multiple simulations isn't always going to make this clear. Also even with the full sim setup, you still can't predict what new cards the opponent is going to play, so your ship might survive the turn if the opponent plays nothing, as in your sim, but if the opponent plays a damage action on your ship, the outcome might be very different (technically, if the opponent is an AI, you could have the AI simulate what the other AI would do - but obviously this is a bit crazy to set up). This becomes an even bigger problem in games where the AI faces multiple opponents : technically the AI should take into account the order in which opponents have turns and thus for simulations it would have to simulate a full turn of one opponent before it can simulate the turn of the other (even though the AI can't predict what these two players might play during their turns).
So multiple sims are possible, but it remains to be seen if the result of this is valuable - it will definately depend on the type of ability you're trying to get a score for. Example : a ship that has the ability 'repair all allies when this enters play'. You could run a sim of this ship entering play, and then you compare the defense on your other ships in the sim with the defense of these ships in reality, this would give you a clear indication for how much these ships have been repaired. But ships are a part of combat, and a ship being repaired might mean it lives another turn, combined with trample and cerberus/hydra effects, repairing a ship might mean that your base lives another turn. To know this, you would want to run a sim of the opponent attacking your ships and comparing the results of that sim with yet another sim : one where the opponent attacks your ships, but where you never played the repair ship. At this point there are 3 simulations for just one ability.
HD3 needs a different AI than the one from HDx - new elements in HD3 make it so that the HDx AI would be too inaccurate in many situations. But a full simulation AI presents its own set of issues. I'm currently trying to figure out if both setups can be combined : perform full simulations, but only take into account local or significant results. This would greatly simplify the part of the code where the AI has to interpret the results of a simulation, while still keeping most of the advantages of using a full sim. A simple example : a ship with a play ability that repairs all allies. If this ship isn't very strong in combat, it might receive a low score, but at the same time in certain situations, it's play ability could be very beneficial. So the AI simulates the effects of this ship entering play, then it looks at the ship itself to see what its strengths are (in this case, a potentially powerful play ability), then the AI can do an interpretation of local data only (only allied ships) and use that data to determine most of the score of this card. If this ship can be placed in front of a very dangerous attacker at the same time in a certain slot, the score could be increased for this particular slot (since the location of this ship doesn't matter for its play ability). In a next step, the AI could then scan the simulation for large changes as a result of possible triggers (rather than taking into account every tiny change), this could be ships or structures that are suddenly gone, or a player that has suddenly died. In the end this will still result in a fairly large AI, but most of the code that goes into this AI is no longer simulation code, instead i can focus on creating code that allows the AI to interpretate the simulations - that alone will be a big advantage over the HDx AI.
Discuss this blog post in this topic on the NULLL forums.
12/01 - HD3 AI cycles
Work has started on the framework that will eventually contain code that allows the AI to calculate what would be the best thing to do during its turns. While the rough outlines of this framework are not too different from HDx, i'm not simply copying it over, but rewriting it as that allows me to go over all the basic building blocks of this framework again and make adjustments where needed. There are also some concerns regarding performance that i need to take into account as the AI might simply have more things to calculate in HD3 compared to HDx, even if only for the reason that the AI might be facing multiple opponents.
The framework itself is rather simple, it mainly makes sure that the order in which the AI does things is preserved. The different things the AI will do, are not in yet and this is mainly because i haven't decided yet what kind of an AI HD3 will need. Regardless of the kind of AI, the time at which the major functions of this AI will be called upon are the same, so i can already add in this framework, without there being any actual AI code to call upon.
During the AI's main phase, the AI continuously loops trough a number of actions until it decides to end the turn. Every such loop or cycle allows the AI to do only one thing : play a single card from hand, or activate one card in play, or recycle one structure.
In every cycle, the AI starts by making a list of all the cards in hand for which it has enough energy to play them, then it does the same for all cards in play with activatable abilities for which it has enough energy to activate them. Then, for each of these cards the AI checks if they are actually playable (are there enough open slots in play for new ships or structures, are there valid targets for actions or abilities, etc) and when they are, the AI simulates what would happen if it were to be played or activated. For cards that can target other cards, this means that all valid targets need to be compared to see which one is the most interesting. When all of this is done, the AI has a list of playable cards, and for each card it has a score that determines how important playing or activating this would be to the AI, as well as a preferred target should the card need it.
In a next step in the cycle, the AI goes through this list, finds the highest scoring card and plays or activates it. If, at this point, there were no cards with scores, it means the AI has run out of things it wants to play and it will end its turn.
After the AI has played the higest scoring card, the cycle starts over again - any action the AI does results in a small or large change to the board, so the scores the AI had calculated before for other playable cards might no longer be valid. Example : the AI has a damaged ship and two different actions that allow it to repair the ship - one action ends up being more interesting to the AI, so that action gets played, but the other action might have received a score as well. If the ship is now fully (or almost fully) repaired, this other action might no longer receive a score at all, so it's important that the AI calculates scores again taking into account the new state of the board.
This overall setup is still the same as it was in HDs and HDx. The major difference is that the AI now must be able to account for different opponents. So when the AI calculates scores for cards, it now does this once for every opponent - some abilities will be more effective when played against a different opponent, after all. Another new thing in HD3 is that the AI must now also decide against which player it will launch an attack - in older versions of HD, the AI always had only one opponent, so it didn't have to care about finding out how efficient an attack would be against this opponent as it had no other choice anyway.
Yet another thing that's different is how structure recycling will work. A rather common (but hard to trace down) problem in HDx was that the AI would recycle a structure and then it seemed to forget about it and played something else first. The AI loops trough a cycle where it looks at all the cards it can play and activate and gives each of these a score based on how useful this card would be - higher scores means a certain card is more useful to the AI when played or activated, as explained above. But once the AI has 5 structures in play and also has some in hand, it also starts considering replacing some structures in play by those in hand, instead of simply ignoring structs in hand since there appears to be no room for more structs in play. This could be for simple reasons such as the structure in hand giving a bonus to HP or energy when it enters play, but it might as well be for more complex reasons when the AI determines it needs more energy generation of a certain color. In any case, when the AI decides it rather has a new structure in play, it obviously first recycles the least wanted structure in play, then the entire AI cycle starts over. In this new cycle, structures in hand are now playable since the AI no longer has 5 structures in play, unlike the previous cycle. This is set up such because the AI in general gives out high scores to structures, so the AI is always very likely to play structures before playing anything else. One of the reasons why the AI decides to recycle is in fact due to it having structures in hand that would have a high score if the AI could play those cards. The problem is that the AI doesn't always decide to actually play a structure in the turn following a recycle.
The difference between the current cycle (4 structs in play) and the previous one (5 structs in play), might actually cause other cards in the AI's hand or in play to receive different scores, and as such, the AI could decide to play or activate that card instead of playing a structure. Once this card has been played, a new cycle starts (since the AI only performs one action per cycle). The card that was activated or played obviously had an impact on the board, so now the difference between the boardstate of when the AI decided to recycle and the current boardstate is even larger, so it's even more likely that the structure in hand that triggered the recycle is not getting played.
The problem here comes from the fact that the entire recycling process is split in two actions (remove the struct, then play a new one) and thus ends up being played over two cycles. In HD3, this process will be treated as one action, causing the AI to properly remember what card triggered the removal of a structure so it can play that card instantly after the recycle completes.
Adding the framework to the game didn't take that much time, a lot of time is being spent currently in figuring out what kind of AI the game needs. I have some setups in mind and these are currently being worked out in a bit more detail on paper so i can run some tests (on paper, again), in an attempt to predict the strengths and weaknesses of these setups. This will take a bit, but it concerns a very important aspect of the game, so it shouldn't be rushed.
Discuss this blog post in this topic on the NULLL forums.
05/01 - HD3 & 2014
Meanwhile, we're 2014. I took some time off, so there hasn't been that much progress on HD3 over the past week. Some work has been done on the input system, related to pausing the AI when players are interacting with the board (opening card popups and such) and the outline of the card destruction system is in place.
When cards leave play, a number of things need to happen. First of all, there's the exit animation and any abilities the card might have that need to trigger at this point. There might also be other cards in play that have abilities that trigger when something leaves play. This entire setup is now more detailed than before, as it can now distinguish between cards leaving play due to them being destroyed, or due to them being forced to return to deck. On top of that, returning to deck now accepts 3 different situations : the card is shuffled back into the deck somewhere, or the card goes to the bottom of the deck, or it goes to the top of deck. There aren't that much cards in HDx where this would matter, but it will allow for more future abilities that might require this level of detail. Differently from HDx, i'll also look into expanding this mechanic to action cards. Right now, actions always go to the scrap after they've been played - it would be nice to have some action that, for instance, would go to the bottom of your deck instead after they've been played.
With things related to cards leaving play, the scrapyard is important as well. The game can't render the scrapyard yet and there's no way to browse it's contents yet either. The way this will work will be slightly different from HDx. In HD3, scrapyards will still show small images of their top 3 cards, but these will no longer produce card popups themselves. Instead, the scrapyard needs to be clicked (or tapped) so it can bring up a dialog box with the complete contents of the scrapyard, which can then be clicked to get card popups or the detailed card info dialog.
I have no idea when HD3 will be ready or even when there will be an alpha available for testing. HD3 is planned to have many new mechanics and gameplay modes on top of what HDx already offers, so it's clear there's a lot work that still needs to be done. I also have some plans for a detailed event system that allows indirect pvp and co-op. This is a complex system however, so testing alone is bound to take some time. Perhaps direct pvp might be added eventually as well, but even more stuff needs to be prepared and tested before i can even figure if it'll be possible at all to have cross-platform pvp.
One thing i'm considering is spreading out the larger features over a number of expansions, instead of trying to get everything ready at release. This would allow to release the game much earlier in an 'open beta' state, where i could get feedback on existing stuff before adding in more gameplay modes or exotic new card abilities for instance.
For the nearby future, the focus is on making the game capable of handling a complete single duel versus an AI. This includes AI code for the small set of cards the game currenty supports. In HDx, i added most cards, before creating the AI, resulting in a lot of AI-only work being done over the course of several weeks - it might be more interesting to work with smaller sets of cards and add more AI code as new cards and abilities are being added. Once all of this is in, i'll start looking into which features i want to be ready for a first alpha. I expect most of 2014 will be spent on HD3.