How the AI works in Sharks v. Fish
WA-TOR, the computer simulation that Sharks v. Fish is based on, is very rudimentary with regards to Artificial Intelligence. In fact there is no AI. If a shark has the good fortune to land in a square adjacent to a square occupied by a fish, then the fish is doomed to be eaten on the next turn. Else the shark bumbles about to find whatever random empty space is next door. Similarly, the fish are dumb as fence posts, searching randomly for any empty space to occupy. This simplicity adds up to very boring visuals.
I wondered, how can I make this better? And what is better? I decided that better meant more interesting to look at. Still I had to follow the basic rules required for the simulation to somewhat model real scientific data.
The challenge was to do this while learning a new programming language - C#. In the end I turned a simple program into something complicated but I had a lot of fun doing it.
A description of the original program can be found here:
The world in Sharks v. Fish is a torroid covered with water. The sea you see on the screen is a 2D flattened out map of this donut shape. The top connects to the bottom and the left to the right in such a way that creatures can scroll across the four edges to the opposite sides. For the simulation, the world is divided up into grid like squares. Each creature always occupies a single square and each square only contains a single creature. The creatures take turns looking in all eight compass directions for an adjacent square to move into. Sharks hunt for adjacent squares with fish while fish seek empty squares. Unlike with WA-TOR, all movement in Sharks v. Fish is done in asynchronous fashion so no creature is waiting its turn. Though constrained by the grid (one creature for one square) the creatures obfuscate this relationship by considering any random point within the grid square to be a valid centering point. This makes things look more hectic and disordered and hides evenly spaced grid layout. Anytime a creature completes a move into a square it makes a decision on the next square it intends to move into. For the case of choosing an empty space, AI is used to compare any available choices to decide the best move. The creatures continue to hunt, seek, and breed and the simulation runs until one of the species becomes extinct - the fish can all be eaten or the sharks can starve to death. With ideal input parameters, the sim will run forever,
The basic real world behaviors must be modeled. Fundamentally, sharks are interested in hunting fish. So they like to go where the fish are. Conversely, fish are interested in staying far away from sharks. Simply searching for adjacent squares that may contain creatures that meet the criteria is inadequate. Depending on blind luck is not an effective strategy. What if there is nothing nearby and a more distant square matches my needs? How can I move to that goal? What data could I use to get information about places that exist beyond my vision?
The answer I chose was using "scent trails". If a shark could detect not just where a fish is, but where a fish has been, that could be a huge clue in picking a productive move direction. Thus each square keeps track of its history by storing time values indicating how long it has been since either a shark or a fish has occupied that space. When given a choice a shark will choose to move to the empty square with the "freshest" (i.e. shortest amount of time vacated) fish trails. The hope is following this path will eventually lead to an actual fish. Meanwhile, the fish are searching for old shark trails, preferring those squares with higher shark vacant times. Go where the sharks aren't.
Great, now we have some data to guide us with our AI needs. But doing this alone would result in a boring, wooden, predictable simulation. Rigid rules take out the element of random chance so important in nature. But what should be randomized?
This is where the designer needs to be creative. Remember my goal with this project - to make something fascinating to watch. That turns out to be a pretty hard thing to define. Dynamic visuals like color patterns fit the bill. Exciting population events like near extinction followed by a population explosion can be interesting. Gives you a species to cheer for. When a single fish comes back to dominate the ocean, staying just out of reach of starving sharks does one cheer for the fish or feel sorry for the shark? With a goal of having both species survive to eternity the designer can not be playing favorites. Predators, prey - we need them both to continue living.
Here are the creature behaviors I found could be fiddled with to achieve the "fascination goal".
First, individual sharks and fish are members of "groups". New groups are generated frequently and define a collection of visual and behavioral traits that are echoed by all members of the group . This new DNA is passed to all spawn of individuals within the group. Eventually one of the offspring will mutate and a new unique DNA will be passed to its descendants. The spawning/mutating cycle continues, guaranteeing that small groups of like minded thinkers will dictate the happenings within their corner of the sea. When taken as a whole these groups drive the overall feel of the simulation and create the randomness needed to overcome less than ideal life parameters.
Traits that all members of a particular group have include:
- How effective they are at searching. Creatures will miss sometimes (fairly often) just like in nature. This is important to keep it running.
- How fast they are. Speed is always a good thing. A fast fish group stands a better chance of escaping a slow group of sharks.
- Common color coding and animation. This is hard to discern with the animals moving. Pausing the sim may reveal group mates visually,
- What sort of AI they use. Hunt, flee, explore, retreat. Some groups do the opposite of what is expected. By going "the wrong way" creatures can discover new schools of fish or vast open ocean.
- How lazy they are. Some groups like to lollygag rather than hunt. But this is good for the nearby fish who wants to sneak past!
- Individuals further modify the settings based on the DNA of the group they belong to.
- Sometimes individuals disobey the group rules. These rouges may make random decisions instead of following the crowd.
- Other minor things that give each group its personality. This is the secret sauce.
By varying the numbers between the groups it is possible to introduce unpredictable variations that greatly affect the sim on the micro level. Smart groups vs. dumb groups. Fast vs. lazy, etc. By presenting the creatures with different situations across the sea the sim is more likely to maintain stability with both species surviving. On the larger macro level the sums of these behaviors hopefully allow both species to survive. Good for nature. Good for the simulation.
Debugging the AI by visualizing the data
Tuning these numbers and observing results was an iterative process greatly helped by using the AI debug maps shown in the screenshots. Being able to evaluate this data visually at any time was invaluable. I recommend implementing something like this early on in your project. It will keep you from guessing at the validity of your programming algorithms.
Have a look at the one on the left. to visualize the time vacant data used for "scent trails" mentioned above. Each sea square displays a pair of red and green indicators. The color describes the creature while the alpha value indicates time vacant. Bright red means a shark has been there recently. Bright green a fish. Over time these indicators fade out reflecting the values stored as time progresses. The creatures access this data to rank adjacent empty squares for move potential.
Without going into detail, the colors, scaling, and alpha of the rendered creatures tell me what AI methods are being used. Dropping the sim speed (slow motion) tells me if my assumptions are correct - Is the AI following the correct rules? Are random permutations happening at the right frequency? These answers would be impossible to get without this debug mode.
The next screenshot shows creature "grouping" as mentioned. Creatures that share a color are part of a unique group that share common AI behaviors. Controlling the frequency of spawning new groups and managing the behavior associated with those groups is important.
The last screenshot is simply the normal non-debug view to be used as a reference.
With the right combination of variables applied, and a whole lot of colors, the mundane WA-TOR program is transformed into something exciting to watch and control. I like to leave Sharks v. Fish running on my big screen TV in full screen mode. It is stimulating, yet calming, to watch the patterns created by the cycling populations. The default random mode is tuned to deliver mostly sustainable, mostly fascinating combinations of life parameters that reset at user controlled intervals. After leaning how the input parameters affect the sim dynamics, it is fun to predict outcomes.
- I was impressed with the performance of an app coded in C# using XNA Game Studio. More than 8000 2D sprites - scaled, blended, colored, rotated - all drawn on top of 3 layers of sea texture blended using a pixel shader, 1080p full screen . Despite this load it updates and renders at 60 frames per second on my not very powerful old computer.
- There are many paradoxes when programming for both sides to win. It is hard to keep the AI balanced. Logic says that making the sharks better hunters is good for the sharks' survival. In fact such an AI boost may ensure that tomorrow's food supply is eaten prematurely, leaving nothing for the next generation of sharks. God, together with Mother Nature, has done a great job designing the creatures of the world so that everything is in harmony.
- Letting fish escape is the secret to the sharks' plan for longevity! And it's more fun to watch than extinction.
- It pays to optimize. The only wasted memory is memory you don't use. Cache anything calculated for re-use. Avoid expensive operations by using lookup tables for things like logarithms and sine functions. Avoid expensive random number generation by designing your random needs around powers of two so you only need to grab the bits that you need (e.g. one bit to flip a coin). Replenish the bucket with a new random number from the system only when all 32 bits have been used. Profile your code to identify bottlenecks. You may be surprised at the cost of some functions, including seemingly harmless system level routines.
- It is cheap to animate texture colors. By generating colors procedurally, infinite variations can be drawn with no additional source art. All of the wildly colored fish are tinted versions of the same gray scale texture.
None with this Windows PC version. Sharks v. FIsh is already a ridiculously over the top implementation of WA-TOR. It is finished. I wish though that I knew how to get it into the hands of others who would appreciate it. It is hard for an app to get noticed these days given the crowded market of other free entertainment software available.
Maybe someday I can think of a mobile project based on this work.
See for yourself
Give it download. Watch the creatures and see if you notice anything AI related. Input your own parameters and see how long you can keep a simulation running. The goal is to keep both species alive forever.
Project page and download link (it's free!):
Get Sharks v. Fish
Leave a comment
Log in with itch.io to leave a comment.