I needed a CPU AI opponent for a simple game, so I decided to build one, and this article details my process. There’s no specific Unity content or project for this article - it’s all theory.
What is Game Artificial Intelligence?
Game artificial intelligence (AI) provides opponents for a human player. Those opponents need to simulate human (or robot, or animal) behaviour to provide a challenge.
A player wants opponents that act and react as a human opponent would. This is difficult because humans are organic, and computers are digital. And of course, computers don’t think for themselves like people can.
Game AI therefore needs to use purely logical algorithms to simulate the problem solving that a human player does organically. You need to make those algorithms feel natural.
A Simple Multiplayer Game
The game I’m creating AI for is a very simple card memory game, the kind where you have a grid of face-down cards, and you try to turn over two matching cards to win a point:
This game hardly requires a super-intelligent robot mind, but I found it was a great way to teach myself the fundamentals of game AI.
Define the Rules
To start with you need to know your game’s rules. Memory card games have very simple gameplay:
Players take turns to:
- Turn over two cards, one at a time (making them face-up and visible to both players)
- If the cards match, they are removed from play and the player gets a point and another turn
- If the cards don’t match, they are turned back face down and the other player has their turn.
The player with the most points when all the cards are gone wins.
I chose to implement a very rough AI to start with. AI version 1.0 picks cards completely at random. As you can guess, this is not challenging, and it doesn’t fit any definition of AI. But it did let me set up the programming infrastructure in my game.
My standard approach to adding functionality is to add in the skeleton first - typically the way the new functionality interacts with the rest of the code. Doing this lets me work on the new feature in isolation knowing that changes to it won’t affect the rest of my game (so long as I keep a simple interface between the main game and the new functionality).
I created a class called AiOpponent
containing a single public method ‘ChooseCard()’. ChooseCard() returns a randomly selected card from the current game board. The core game knows that the player is AI and therefore must be asked to select a card rather than wait for a human player to tap the screen. The rest of the game continues as normal.
Once this was working I could encapsulate all my AI code into the AiOpponent class, but never have to change any other code in the game. The game manager doesn’t care how a card is selected, just that one is.
From Zero to Hero
I played the game a few times to figure out the main strategy. The core strategy is to remember the cards that are turned face-up and then returned face-down. The perfect strategy was to remember every card and then use that information to improve the chance of making pairs.
I decided that version 2.0 of my AI would play a ‘perfect game’ and remember every single card with 100% accuracy (this is actually simpler than implementing an imperfect strategy), and always make a pair when possible (i.e. when it knows where two matching cards are or when it knows where a card matching its first selection is). I broke this down into the following flowchart:
Considerations for v2.0
As you can probably guess, this AI is almost unbeatable. It doesn’t make mistakes, and it has a perfect memory. Unless you get lucky and also have a perfect memory you will usually lose. Like a random opponent, a perfect opponent isn’t any fun.
AI v2.0 doesn’t forget a card they saw several turns ago; it doesn’t accidentally pick up the card next to the one it intended. AI v2.0 is too machine-like; too perfect.
I’m the first to admit that the AI for my card memory game is overkill. It’s a simple kids’ game, and having a purely random opponent would work just fine. But I wanted to teach myself how to create an AI opponent so I can apply that knowledge to a more complex game I plan to make later.
So I set out to make the most human card memory AI I could.
The next step was to make the AI adaptable to different difficulty levels by blending the random card selection from v1.0 and the perfect matching skills of v2.0. Based on the difficulty level my AI would choose randomly or perfectly in different proportions.
- Easy AI - totally random
- Medium AI - half random, half perfect
- Hard AI - totally perfect.
v3.0 is probably the most complex AI you could possibly need for this kind of game. In fact, it’s probably already overkill. But once I’d started deconstructing the game I realised even this simple game has more potential for AI, so in a future post I will go into the nuances of a more human card memory opponent (even though it’s overkill).