So, now I’ve created the second part of my AI for Part II of the Empire project.
So, here’s what I did since last time, in terms of refactoring.
In terms of architecture, I wanted to move towards a more structured approach where a “UnitMind” is assigned to each unit. Basically, I have a “UnitHiveMind” class, which has a map of <UINT, UnitMind*> key-value pairs. This way each unit’s UnitID is mapped to a UnitMind. This Mind handles the Unit’s behavior (via states), so that the Unit code isn’t all jammed into the primary DLL cpp file. After I had implemented this it made my code a lot cleaner.
The primary unit logic now occurs in each UnitMind’s updateDirection() function, which is called from UnitHiveMind’s updateDirection() function.
The FOLLOW_PATTERN state is the pattern-following logic from part I of the Empire project, and is primarily used for exploring the map. FORMATION is used for pathing when a unit is part of a formation (more on that later). And lastly, SEEK_CITY is used to path towards cities that my AI does not currently own, when they are discovered.
I turned my PatternPathing class from part I into a static class. Previously, it had basically been the manager of unit movement, and I didn’t need it to do that anymore now that I had a real unit manager, UnitHiveMind. So, making it a static class and removing all of the unit management code really cleaned it up and made it into a great utility class.
Formations are pretty simple. UnitMinds default to the FOLLOW_PATTERN state, but if you call setFormationOffset(), this will switch the UnitMind’s state to FORMATION. SetFormationOffset() takes in parameters for an x offset, a y offset, and a leader UnitMind* (a pointer to the UnitMind that the current Unit will follow). The Unit then paths towards the UnitMind’s position plus the offset. Every time a Unit is created, there is a random chance it will be assigned to follow another random UnitMind.
City Seek is fairly simple. If an undiscovered city becomes visible to me, I assign all of my units to follow it using setCityAssignment(), which changes their state to SEEK_CITY. Unfortunately, this only works for cities that are owned by another player, not neutral cities. Anyway, they then follow a simple pathing function to path to the city’s location (by adjusting their x and y positions until they match the city’s x and y positions).
Diving into batch scripting
After discovering some of the exploits available within the framework given (I did not incorporate any of these exploits into my final DLL build), I wanted to see if there was some way I could incorporate batch scripts into my DLL to hack the game parameters. I found out there weren’t really any real exploits I could incorporate using batch scripts (since all the loading of game-related data is done before the DLLs are initialized), so I decided to create some “fun” “exploits.” I discovered that passing a string into the system() function calls that string as if it were a command-line input. With that knowledge in hand, I quickly implemented a way to open a YouTube clip in Firefox and Chrome from my DLL, an easy thing to do using a Windows command-line input.
But I wanted to go one step beyond: append a section of text to the stats.txt file that is generated after the Empire application closes. Seemed simple enough at first: I would just have to use ofstream and do a simple append to the stats file. But then, I realized the following situation: the stats.txt file is generated/overwritten after all of the AI player DLLs had closed. Seemed like a dead end at first. I needed a way to delay my overwrite of the file until after all the DLLs and the main Empire application had terminated. After a deep dive into batch scripting (aka searching StackOverflow) I formulated this plan:
- During cleanup of my DLL, call a batch script that launches a new command-line window (minimized for stealth using /MIN and @echo off)
- In this new window, call a second batch script that PINGs an IP address that I know I will not get an answer from (such as 220.127.116.11) with a certain timeout delay
- After the PING had timed out (and hopefully the main Empire app had exited), then perform the overwrite of the file
This way, the append is handled by a series of batch scripts in a new command-line window after termination of my DLL and the main application.
But, I ran into a new problem: as it turns out, upon the end of a game, the main Empire application cleans up the DLLs then waits for a user input (using a system(“pause”) call). After the user input, the main application finally terminates. Unfortunately, my append would happen a few seconds after the DLL cleanup, but not before this user input (at least, not unless the user pressed a key and exited the application very quickly). Therefore, my append was occurring before the main application terminated, and my changes would be overwritten and my efforts would all be for naught.
The solution to this problem was clear: continually re-append the file for an extended period of time after the DLL had terminated, so that hopefully, the main application will terminate before my batch scripts were finished. But, the whole reason I wanted the append to only happen a few seconds after DLL cleanup was to maintain stealth. If there was a random command-line window minimized at the bottom of the screen for an extended period of time, it might go unnoticed, but it also might not.
Fortunately, I found a solution that uses a short VBScript to run a batch script in a hidden manner.
So, the pipeline now looked like this:
- The initial batch script is called from my DLL using a system() call
- This initial batch script then calls the second batch script (using the aforementioned VBScript), which makes it run in a hidden command-line window
- The second batch script runs a for loop 50 times
- Within the for loop, it first PINGs for 1 second, then appends both the stats.txt file, as well as the DLLControl.txt file (which is used by the Empire application to specify file paths for the AI player DLLs)
So, it essentially appends both the stats.txt and DLLControl.txt files 50 times in 50 seconds. This delay would hopefully cover the amount of time between the game ending and exiting the main application. Also, since this is done in its own command-line window, it is done independently of the main application, and thus survives termination of the DLLs and the main application.
For the final build of my DLL, I ended up making the following adjustments:
- Instead of opening the YouTube clip from my DLL, I opened it after the append loop in the batch file pictured above (thus, the clip opens 50 seconds after the game ends)
- Rather than reappend the text files for 50 seconds (resulting in text files with upwards of 500000 lines) I opted to make a copy of the original text file, then append the original for 50 seconds (thus, the contents of my text file would only show up once, rather than be repeated 50 times)
- In addition, I use the copy command to replace the font of the Empire application with a font I stored in my DLL folder
So, this portion of my project didn’t have a lot to do with AI, but I had a lot of fun learning more about batch scripting.
Overall, my AI did fairly decently in the in-class tournament. I was actually a bit surprised. My system of unit formations actually ended up helping me out in the earlier rounds, as this, combined with how all of my units would seek a particular undiscovered city, ended up being a formidable force. At least, it worked fairly well in the earlier rounds. But, as we reached the Winner’s Finals match of the bracket, my AI was stranded near a few lakes and couldn’t find any other cities, and ended up losing that one handily. A similar thing happened with my Loser’s Bracket match, and so I was knocked out for the tournament, but not before getting pretty far in the bracket.
Overall, I enjoyed working on this project. I had fun refactoring my AI and making it more architecturally sound. This made it a lot easier to add additional functionality (such as the formation system) than in my exploration AI code. I probably spent more time on batch scripting than I should’ve, and but this diversion has me excited to learn more about scripting/tools.