Strategy game, battle system and basic AI (Post #22, Strategy #13)

 In this post we'll delve a bit into creating a simple AI for our enemy factions, it won't be a challenging one and it will need lots of updating and modifying afterwards depending on what you want to achieve, and how hard you want it to be. We will also make some other additions to our code for some visuals we haven't used yet.

➤ Unit selection information

We have on our UI side bar a place that was created to hold information for selected unit that we haven't implemented into our code yet. In the image below you can see where the values will be displayed.

Since we have created the unit selection system and these values are assigned when a unit is created through our unit library, we'll just display this information on our UI.

Add the following code on our UI render, (I added this after 'main UI overlap part)

'Unit description
            DrawText(Lib_Construct_S(Faction(0).Unit(UnitSelected).ID).Name, 868 * (ScreenWidth / UISizeW), 368 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1) 'Unit name
            If Faction(0).Unit(UnitSelected).ID < 18 Then
                DrawText("Ground", 868 * (ScreenWidth / UISizeW), 388 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1)  'Unit type
            ElseIf Faction(0).Unit(UnitSelected).ID < 18 Then
                DrawText("Air", 868 * (ScreenWidth / UISizeW), 388 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1)  'Unit type
            ElseIf Faction(0).Unit(UnitSelected).ID < 21 Then
                DrawText("Sea", 868 * (ScreenWidth / UISizeW), 388 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1)  'Unit type
            End If
            DrawText("Hit points : " & Faction(0).Unit(UnitSelected).Health, 835 * (ScreenWidth / UISizeW), 408 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1) 'Unit hit points
            DrawText("Att/Def : " & Faction(0).Unit(UnitSelected).Attack & "/" & Faction(0).Unit(UnitSelected).Defence, 835 * (ScreenWidth / UISizeW), 428 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1) 'Unit attack and defence
            DrawText("Turn points : " & Faction(0).Unit(UnitSelected).TurnPoints, 835 * (ScreenWidth / UISizeW), 448 * (ScreenHeight / UISizeH), 1.1, 1, 0.8, 0.8, 0.8, 1) 'Unit attack and defence
            'Drawing unit image

            sRect = New Rectangle(Faction(0).Unit(UnitSelected).ID * 64, 0, 64, 56) 'Y * 56
            dRect = New Rectangle(828 * (ScreenWidth / UISizeW), 364 * (ScreenHeight / UISizeH), 35 * (ScreenWidth / UISizeW), 35 * (ScreenHeight / UISizeH))
            DrawTexturedQuad(sRect, dRect, 1472, 448, 1, 1, 1, 1, 1, 1)

By adding this part of code you should see the result as shown on the photo below. When pressing space or whichever button you have assigned to switch between units the information on the UI will update.


➤ Adding research and construction libraries to all enemy factions

In order to add the other factions, we need to change some of our code that we created before, since the libraries where made with no index for  enemy factions and they were created and handled, only for the player.

First we need to use the find and replace code and change this

Lib_Research_S( to Lib_Research_S(0,

and replace on the declaration of the library the 0 with maxfactions

Public Lib_Research_S(MaxFactions, 28) As Library_Research_S

With this simple change we add an extra index that will hold the research data for all factions.

Let's create an extra loop on library creation and replace the 0 in there.

Our sub was like this : Public Sub Game_Library_Construct(ByVal Func As String, ByVal X As Integer, ByVal FactionID As Integer), we just had not used the factionID value till now.

So let's change inside that sub Lib_Research_S(0, with Lib_Research_S(FactionID,

After that we'll search all calls to this sub which are the following and change these calls.

Inside FACTION_F > END_TURN_STEPS change it to the following

Game_Library_Construct("REFRESH_RESEARCH", Faction(I).CurResearchID, I)

 Inside Scene_Build replace it with the following loop

Dim I As Int16
            For I = 0 To MaxFactions
                Game_Library_Construct("LOAD", 0, I)
            Next

 Now the research library initializes and gets refreshed for all factions. We should repeat this process for our construction libraries now too. After that we should be able to start working on the logic algorithm which our enemies will use.

For the construction library we follow the same process.

Where Lib_Construct_S( we replace it with Lib_Construct_S(0,

Our declaration of Lib_Construct should be changed to hold maxfactions instead of 0

Public Lib_Construct_S(MaxFactions, 36) As Library_Constructions_S

Inside our Game_Library_Construct sub we replace

Lib_Construct_S(0 with Lib_Construct_S(FactionID

 ➤ Adding movement functions to enemy factions

Now that we added the libraries to all factions, we will add an index inside Unit_F for our movement so we can use the sub for enemies.

The unit_F sub will now be like this : Public Sub Unit_F(ByVal Func As String, ByVal ID As Integer, ByVal FactionID As Integer)

And we replace inside it

Faction(0) with Faction(FactionID)

Now go through the error list through wherever Unit_F is called and add a ,0) at the end of the call. Like the example below (including the references)

Unit_F("MOUSE_HOVER_UNIT", UnitSelected, 0)

Will need to add it on these calls

GLMouseMove (Change it to ,0)

GLMouseDown (Change it to ,0) 

Faction_F (Change it to ,0)

Unit_M (change it to ,Y)

Create_Unit (change it to ,FactionID)

The above is the reason why we should add references at the start of our code or make our code in a way it can be easily manipulated based on what we want to do in the future. When the code gets spaghetti everything gets harder to change.

➤ Creating a rudimentary AI

Creating a proper AI is not an easy process, but in any case we need to have a base for it. So we'll create a chart which will represent the basic process of AI action flow. When this is made you can add more clauses and results, that they lead to. Here we'll work the AI like the image below. Simple research and building assigning, moving around and if there is an enemy sighted, attack. Doesn't make for a hard opponent but at least it's an opponent.

Change the Mousedown event to the following, cause it will bug out in the process and move your units too while enemy is playing.

Public Sub GLMouseDown(ByVal Btn As MouseEventArgs)
        If TurnChange = False Then
            If Btn.Button = MouseButtons.Left Then
                If SButtonHover <> 3 Then Unit_F("MOUSE_MOVE_UNIT", UnitSelected, 0)
                GameButtons("CLICK")
            End If
        End If
    End Sub

Now let's move on to implementing our new code.

First add these declarations inside our unit struct

Dim DamageSeq As Int16 'This is the sequence for the damage animation
Dim DamageApl As Single 'This is how much damage will be applied

We'll add the following subs to our code. And I'll explain over each one what they are.

The following sub is just to apply damage. We will hold the damage on the unit that receives it.

    Public Sub ApplyDamage(ByVal FactionID As Integer, ByVal UnitID As Integer, ByVal TargetFaction As Integer, ByVal TargetUnit As Integer)
        Faction(TargetFaction).Unit(TargetUnit).DamageApl = Faction(FactionID).Unit(UnitID).Attack
        Faction(TargetFaction).Unit(TargetUnit).DamageSeq = 20
    End Sub

  The next sub is our actual logical sequence to move units, build and research that our enemy factions will follow. It's based on the above image with our chart.

 Public FactionResearch(MaxFactions) As Integer

    Public Sub EnemyFactions()
        Dim I, Q, L As Int16
        Dim RndN As Integer
        Dim RndTile As Integer
        'Dim MoveLoop As Integer
        Dim CounterT As Integer = 100
        Dim RndConstructArray(25) As Int16
        Dim ConstructArrayMax As Int16

        '(A) /////////////////////Loop through factions/////////////////////////////////////
        For I = 1 To MaxFactions

            '(C)+(D) Assign available constructions inside a random array
            ConstructArrayMax = 0
            For Q = 1 To 36
                If Lib_Construct_S(I, Q).Unlocked = 1 Then
                    ConstructArrayMax += 1
                    RndConstructArray(ConstructArrayMax) = Q
                End If
            Next

            '(B) /////////////////////Assign Research if there is none/////////////////////////////////////

            If Faction(I).CurResearchID = 0 Then 'If there is no research at the moment
                For Q = 0 To 28 'Loop through the research library and pick the first available
                    If Lib_Research_S(I, Q).Researched = 0 And Lib_Research_S(I, Q).Available = 1 Then
                        Faction(I).CurResearchID = Q
                        Faction(I).CurResearchTurns = Int(Lib_Research_S(I, Q).Turns - Lib_Research_S(I, Q).Turns * (Faction(I).STLibrary.BasicB(1) + Faction(0).STLibrary.BasicB(4) + Faction(0).STLibrary.BasicB(7)) / 100)
                        Exit For
                    End If
                Next
            Else 'If there is a research do nothing

            End If

            '(C)+(D) /////////////////////Assign construction to cities//////////////////////////////////////

            For Q = 0 To MaxCitiesPF 'Loop through city indices
                If Faction(I).City(Q).Enabled = 1 Then 'There is a city built on this index
                    If Faction(I).City(Q).ConstructionID = 0 Then 'The city is not building anything
                        RndN = Int(Rnd() * ConstructArrayMax) + 1 'Get a random value
                        Faction(I).City(Q).ConstructionID = RndConstructArray(RndN) 'Pick from the available constructions array random value to build
                        Faction(I).City(Q).ConstructTurns = Int(Lib_Construct_S(I, RndConstructArray(RndN)).Turns - Lib_Construct_S(I, RndConstructArray(RndN)).Turns * (Faction(I).STLibrary.BasicB(6) + Faction(I).STLibrary.BasicB(4)) / 100)
                        'MsgBox(RndN & " , " & " max = " & ConstructArrayMax & " /" & RndConstructArray(0) & RndConstructArray(1) & RndConstructArray(2))
                    End If
                End If
            Next

            '(E) /////////////////////Loop through units/////////////////////////////////////
            For Q = 0 To MaxUnitsPF
                If Faction(I).Unit(Q).ID < 1 Then
                    'No unit
                Else 'There is a unit

                    If Faction(I).Unit(Q).ID < 2 Then 'Settler
                        RndN = Int(Rnd() * 10)
                        UnitSelected = Q
                        CounterT = 100
                        Do Until CounterT = 0
                            If RndN <= 8 Then
                                Unit_F("MOVE_CALCULATE", Q, I) 'Calculate available tiles
                                RndTile = Int(Rnd() * 8)
                                Do Until Faction(I).Unit(Q).TileToMA(RndTile) = True
                                    RndTile = Int(Rnd() * 8)
                                Loop
                                UnitSelectedMouseHover = RndTile
                                Unit_F("MOUSE_MOVE_UNIT", Q, I) 'Move unit
                            Else 'Create a city

                                For L = 0 To MaxCitiesPF
                                    If Faction(I).City(L).Enabled = 0 Then
                                        City_M("CREATE", L, I, Faction(I).Unit(UnitSelected).TileX, Faction(I).Unit(UnitSelected).TileY)
                                        Exit For
                                    End If
                                Next
                                Exit Do
                            End If
                            CounterT -= 1

                        Loop

                    ElseIf Faction(I).Unit(Q).ID >= 2 Then

                        Unit_FOV_Check = 0
                        Get_Unit_FOV(I, Q)
                        UnitSelected = Q
                        CounterT = 100

                        If Unit_FOV_Check = 0 Then
                            Do Until CounterT = 0

                                Unit_F("MOVE_CALCULATE", Q, I) 'Calculate available tiles
                                RndTile = Int(Rnd() * 8)
                                Do Until Faction(I).Unit(Q).TileToMA(RndTile) = True
                                    RndTile = Int(Rnd() * 8)
                                Loop
                                UnitSelectedMouseHover = RndTile
                                Unit_F("MOUSE_MOVE_UNIT", Q, I) 'Move unit
                                CounterT -= 1
                            Loop
                        ElseIf Unit_FOV_Check = 1 Then
                            Do Until CounterT = 0

                                Unit_F("MOVE_CALCULATE", Q, I) 'Calculate available tiles
                                If Unit_FOV_Direction < 8 Then
                                    UnitSelectedMouseHover = Unit_FOV_Direction

                                    Unit_F("MOUSE_MOVE_UNIT", Q, I) 'Move unit
                                Else 'Unit_FOV_Direction = 8
                                    If Faction(I).Unit(Q).TurnPoints > 0 Then
                                        ApplyDamage(I, Q, TargetF, TargetU)
                                        CounterT = 1
                                    End If
                                End If
                                    CounterT -= 1
                            Loop
                        End If
                    End If
                End If
            Next
        Next
    End Sub

The next sub is to check tiles, available positions to move and checking for enemies that AI controlled units will follow.

  Public Unit_FOV_Check As Int16
    Public Unit_FOV_Direction As Int16
    Public TempEnemyL As Int16
    Public TargetF, TargetU As Int16

    Private Sub Get_Unit_FOV(ByVal FactionID As Integer, ByVal UnitID As Integer)
        Dim X, Y As Integer
        Dim I, Q, L As Integer
        Dim Xto, Yto As Int16

        For Y = (Faction(FactionID).Unit(UnitID).TileY - 2) To (Faction(FactionID).Unit(UnitID).TileY + 2)
            For X = (Faction(FactionID).Unit(UnitID).TileX - 2) To (Faction(FactionID).Unit(UnitID).TileX + 2)
                If X >= 0 And X <= MaxGridX And Y >= 0 And Y <= MaxGridY Then
                    For I = 0 To MaxFactions
                        For Q = 0 To MaxUnitsPF
                            If Faction(I).Unit(Q).TileX = X And Faction(I).Unit(Q).TileY = Y And I <> FactionID Then
                                Unit_FOV_Check = 1
                                Xto = Faction(I).Unit(Q).TileX - Faction(FactionID).Unit(UnitID).TileX
                                Yto = Faction(I).Unit(Q).TileY - Faction(FactionID).Unit(UnitID).TileY
                                If Xto > 0 And Yto = 0 Then
                                    Unit_FOV_Direction = 0
                                ElseIf Xto > 0 And Yto > 0 Then
                                    Unit_FOV_Direction = 1
                                ElseIf Xto = 0 And Yto > 0 Then
                                    Unit_FOV_Direction = 2
                                ElseIf Xto < 0 And Yto > 0 Then
                                    Unit_FOV_Direction = 3
                                ElseIf Xto < 0 And Yto = 0 Then
                                    Unit_FOV_Direction = 4
                                ElseIf Xto < 0 And Yto < 0 Then
                                    Unit_FOV_Direction = 5
                                ElseIf Xto = 0 And Yto < 0 Then
                                    Unit_FOV_Direction = 6
                                ElseIf Xto > 0 And Yto < 0 Then
                                    Unit_FOV_Direction = 7
                                End If

                                For L = 0 To 7
                                    If Faction(I).Unit(Q).TileX = Faction(FactionID).Unit(UnitID).TileToM(L).X And Faction(I).Unit(Q).TileY = Faction(FactionID).Unit(UnitID).TileToM(L).Y Then
                                        Unit_FOV_Direction = 8
                                        TempEnemyL = L
                                        TargetF = I
                                        TargetU = Q
                                        Exit For
                                    End If
                                Next

                            End If
                        Next
                    Next
                End If
            Next
        Next
    End Sub

That's it for a basic AI, now when you run it you'll figure out that it has lots of problems that need to be fixed. Problems like the enemy faction constructs too many settler units or sea units are built even though the city is not next to water. The base is there for the logical sequence our enemy will use.

The last bit of code for the damage to show we'll add the following under our unit drawing part of the code.

'Damage
                        If Faction(Y).Unit(X).DamageSeq > 0 And Faction(Y).Unit(X).DamageApl <> 0 Then
                            Faction(Y).Unit(X).DamageSeq -= 1
                            sRect = New Rectangle(560 + (5 - Faction(Y).Unit(X).DamageSeq \ 4) * 49, 461, 49, 52)
                            dRect = New Rectangle(Faction(Y).Unit(X).X + OffSetX + 4, Faction(Y).Unit(X).Y + OffSetY - 22 * ZoomVar, 49 * ZoomVar, 52 * ZoomVar)
                            DrawTexturedQuad(sRect, dRect, 1020, 548, 3, 1.0F, 1, 1, 1, 1)
                        ElseIf Faction(Y).Unit(X).DamageSeq <= 0 And Faction(Y).Unit(X).DamageApl <> 0 Then
                            Faction(Y).Unit(X).Health -= Faction(Y).Unit(X).DamageApl
                            Faction(Y).Unit(X).DamageApl = 0
                        End If

We now should have enemy factions that build move and fight. On our next post we'll add the battle system to our player system too, since by now only enemy factions can initiate a battle.


Comments