AI with complex board representation: simulating a single possible move

I am currently developing a turn based strategy strategy game (similar to Heroes of Might and Magic and Age of Wonders) using C #. Focus on the warhead first.
There are several units on the grid, taking turns attacking, shooting, casting spells, moving, etc.

A bit stuck with AI programming. I want the AI ​​to be able to plan ahead, so I figured the process of finding the best action could be like this:

  • Create a list of possible moves
  • For each possible move:
    • Simulate movement
    • Assess condition

Then I repeat this process in the Minimax algorithm, somewhat similar to creating a chess AI.

The big difference from making a chess AI would be a "board representation" (or state); this game has a lot of complexity and just storing the state as an integer array won't be enough. There are several units with all kinds of health fields, type of attack, how they move, and special abilities, etc. & Hellip ;.

So the problem I am facing is part of the "simulation movement". I need to make a function like (previous state, move) => state

. I usually copy the previous state and move through the copy. But I cannot find an efficient way to copy the state in which the transition will take place as it is very complex with many different objects. And this step should not change the previous state.

What would be a good code structure to simulate motion without allowing the current board to be influenced?

This can probably be done simply by using the Undo () method for each step, but as the logic gets complicated it would be difficult to keep track of everything that was changed.

I might have some kind of software design pattern, or if anyone has done something similar, I would like to know if they have encountered this problem.

A lot of Stack Overflow questions are about the AI ​​game play, but it's always like chess / tic-tac-toe with light advice.

+3


source to share


1 answer


This looks like a problem that most constraint programming solvers deal with. In general, they implement a hybrid between pattern Command

and Memento

. It works like this:

You need a class that represents the state of the game (called here State

). Something that contains, for example, the state of the board, the current player who can make a move, the resources each player has (if this is part of the game), etc. It is better to make such a state an instance ICloneable

: such that you can clone the state and perform hypothetical processing on it.

So:

     ICloneable
          ^
          |
+-------------------+
|       State       |
+-------------------+
|+ Clone() : Object |
+-------------------+

      

Then you have a class IMove

that represents the possible state movements. IMove

maybe Alter

State

. In most cases, such a method will return something like IAlterResult

: an object that stores some data, for example, to undo a move.

+------------------------------+
|            IMove             |
+------------------------------+
|+ Alter(State) : IAlterResult |
+------------------------------+

      

and



+---------------------+
|    IAlterResult     |
+---------------------+
|+ Undo(State) : void |
+---------------------+

      

For example, if you remove a piece from the board, it IAlterResult

stores where that piece stood, so if you undo the move, it IAlterResult

can put the pawn (as an example) back on the board.

Each time you do IMove

, you click the appropriate IAlterResult

on Stack<IAlterResult>

. This stack, thus, keeps track of the moves made and can, by iteratively jumping IAlterResult

out and executing the command Undo

, returns State

to its original state.

Most constraint programming solvers alternate copies State

with IAlterResults

. This is how the stack looks like.

+---------------+
| AlterResult5  |
| AlterResult4  |
| Copy2         |
| AlterResult3  |
| AlterResult2  |
| AlterResult1  |
| Original copy |
+---------------+

      

If you want to undo the four states, you can look first if there is a copy available, just enter the first two changes (no Undo

ing), then use Copy2

and undo AlterResult3

and AlterResult2

. This can improve efficiency if the Undo action requires heavy computational results.

You can also save the move you made in IAlterMove

, in this case - if the move cannot be undone or is difficult to undo, you can simply back out to the last saved copy and do all the moves on the stack, so if you want to undo for example last move ( AlterResult5

), but this result cannot be undone, you can use Copy2

and redo the move saved in AlterResult4

.

0


source







All Articles