AI-G Lab
AI-G Lab
BACHELOR OF TECHNOLOGY
Artificial Intelligence
for Games
(BAI-406)
PRACTICAL FILE
AIM
SOFTWARE REQUIRED
THEORY
A* (A-Star) is an informed search algorithm widely used in artificial intelligence for games,
robotics, and real-world navigation. It is an extension of Dijkstra’s algorithm with an added
heuristic to improve efficiency. A* efficiently finds the shortest path from a start position to a
goal while minimizing computational costs.
where:
• represents the actual cost from the start node to the current node.
• is a heuristic estimate of the cost from the current node to the goal.
• is the total estimated cost of the cheapest path passing through node .
Heuristic Function
A* uses a heuristic function to guide its search. A common heuristic for grid-based pathfinding is
the Manhattan Distance, which assumes only horizontal and vertical movement: Other heuristic
functions include:
Why A* is Efficient?
A* is more efficient than uninformed search algorithms like Breadth-First Search (BFS) and
Dijkstra’s Algorithm because it prioritizes paths that appear more promising. It balances between
optimality (Dijkstra’s approach) and efficiency (Greedy Best-First Search). The algorithm
guarantees an optimal path as long as the heuristic function is admissible (never overestimates the
actual cost).
CODE
#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
while (!openList.empty()) {
Node* current = openList.top();
openList.pop();
// Explore neighbors
for (size_t i = 0; i < directions.size(); ++i) {
int newX = current->x + directions[i].first;
int newY = current->y + directions[i].second;
int main() {
// Weighted maze: -1 indicates obstacle, other values represent path cost
vector<vector<int> > maze = {
{1, -1, 3, 1, 1},
{1, -1, 2, -1, 2},
{1, 2, 2, -1, 2},
{-1, -1, 1, -1, 1},
{1, 1, 1, 2, 1}
};
return 0;
}
EXPLANATION
• Node Structure:
o Stores the coordinates (x, y), actual path cost g, heuristic cost h, total cost f = g + h,
and a pointer to its parent node.
• Priority Queue (openList):
o Maintains nodes sorted by the lowest f value to explore the most promising paths
first.
• 2D Boolean Vector (closedList):
o Keeps track of visited nodes to avoid redundant processing.
2. Functions
3. Execution Flow
o The algorithm starts at the given initial position and expands nodes based on f(n) =
g(n) + h(n).
o The lowest-cost path is expanded first, avoiding obstacles.
o The final path is traced from the goal back to the start.
OUTPUT
CONCLUSION
The A* algorithm efficiently finds the shortest path in a weighted maze, balancing cost (g) and
heuristic (h). This technique is widely used in game AI for real-time pathfinding in navigation
meshes, 2D tile-based maps, and 3D environments.
EXPERIMENT- 2
AIM
To implement the A* (A-Star) algorithm for solving the 8-puzzle game, ensuring an optimal
solution while minimizing the number of moves required.
SOFTWARE REQUIRED
THEORY
The 8-puzzle game consists of a 3x3 grid with 8 numbered tiles and one empty space. The
objective is to rearrange the tiles to reach a goal state by sliding tiles into the empty space.
A* (A-Star) is an informed search algorithm widely used for pathfinding and problem-solving
in artificial intelligence. It extends Dijkstra’s algorithm by using a heuristic to improve
efficiency. A* finds the optimal solution while minimizing computational costs.
where:
• represents the actual cost from the start node to the current node.
• is a heuristic estimate of the cost from the current node to the goal.
• is the total estimated cost of the cheapest solution passing through node .
Heuristic Function
A* uses a heuristic function to guide its search. Common heuristics for the 8-puzzle include:
• Manhattan Distance: Sum of the vertical and horizontal distances of each tile from its
goal position.
#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <cmath>
struct Node {
vector<vector<int>> state;
int g, h, f;
Node* parent;
pair<int, int> blank;
struct Compare {
bool operator()(const Node* a, const Node* b) {
return a->f > b->f;
}
};
vector<pair<int, int>> directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
while (!openList.empty()) {
Node* current = openList.top();
openList.pop();
if (isGoal(current->state, goal)) {
cout << "Solution Found:\n";
printPath(current);
return;
}
closedList.insert(current->state);
if (newX >= 0 && newX < 3 && newY >= 0 && newY < 3) {
vector<vector<int>> newState = current->state;
swap(newState[current->blank.first][current->blank.second], newState[newX][newY]);
if (closedList.find(newState) == closedList.end()) {
Node* neighbor = new Node(newState, current->g + 1, heuristic(newState, goal),
current, {newX, newY});
openList.push(neighbor);
}
}
}
}
cout << "No solution found." << endl;
}
int main() {
vector<vector<int>> start = {{1, 2, 3}, {4, 0, 5}, {6, 7, 8}};
vector<vector<int>> goal = {{1, 2, 3}, {4, 5, 6}, {7, 8, 0}};
aStar(start, goal);
return 0;
}
EXPLANATION
3. Execution Flow
o The algorithm expands nodes with the lowest f(n) = g(n) + h(n).
o The blank tile moves in all possible directions.
o The optimal sequence of moves is printed upon reaching the goal.
OUTPUT
CONCLUSION
The A* algorithm efficiently finds the optimal solution for the 8-puzzle game, minimizing moves
while ensuring correctness. It is widely used in AI for game solving and robotics applications.
EXPERIMENT- 3
AIM
SOFTWARE REQUIRED
THEORY
A* (A-Star) is an efficient and widely used pathfinding algorithm that finds the shortest path
between a start and a goal node in a graph. It is commonly used in games and robotics for AI
navigation.
Working of A*
• It uses g(n) (cost from start to current node) and h(n) (heuristic estimated cost from
current to goal).
• The f(n) = g(n) + h(n) function determines the best path.
• A* expands the most promising nodes first, ensuring an optimal and fast path.
In traditional maze games, AI characters need to navigate from a start position to a goal while
avoiding obstacles (walls). Instead of checking every cell in a grid-based approach, we use
NavMesh (Navigation Mesh) to optimize pathfinding.
CODE
#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
#include <unordered_map>
#include <algorithm>
// Maze dimensions
const int ROWS = 5;
const int COLS = 5;
return closest;
}
// A* Pathfinding algorithm on NavMesh
vector<int> AStarNavMesh(pair<int, int> start, pair<int, int> goal) {
int startNode = FindClosestPolygon(start);
int goalNode = FindClosestPolygon(goal);
gScore[startNode] = 0;
fScore[startNode] = Heuristic(navMesh[startNode].center, navMesh[goalNode].center);
while (!openSet.empty()) {
int current = openSet.top().second;
openSet.pop();
if (current == goalNode) {
vector<int> path;
while (cameFrom.find(current) != cameFrom.end()) {
path.push_back(current);
current = cameFrom[current];
}
path.push_back(startNode);
reverse(path.begin(), path.end());
return path;
}
// Main function
int main() {
GenerateNavMesh();
if (!path.empty()) {
cout << "Path found through NavMesh nodes:\n";
for (int id : path) {
auto node = navMesh[id];
cout << "ID: " << node.id << " -> (" << node.center.first << ", " <<
node.center.second << ")\n";
}
cout << "Goal Reached!\n";
} else {
cout << "No path found!\n";
}
return 0;
}
EXPLANATION
2. Functions
• Heuristic (Euclidean Distance)
• Computes the heuristic h(n) as the Euclidean distance between two polygon centers:
h(n)=sqrt((xgoal−xcurrent)^2+(ygoal−ycurrent)^2 )
• Find Closest NavMesh Polygon
• Finds the nearest polygon for a given start or goal position by comparing distances.
• A Algorithm on NavMesh*
1. Initialize the Open List
o Start polygon is added with g = 0 and f = h(start, goal).
2. Expand the Best Node (Lowest f-value)
o Pick the polygon with the lowest f from the open list.
o If it is the goal polygon, terminate and reconstruct the path.
3. Explore Neighbors
o Calculate g and f values for each connected polygon.
o If the new path is better, update scores and parent pointers.
4. Continue Until Goal is Found or Open List is Empty
• Trace the Path
• Backtracks from the goal polygon to the start polygon using cameFrom.
• Prints the polygon IDs and their center coordinates.
3. Execution Flow
1. Convert the maze into a Navigation Mesh by grouping walkable areas into polygons.
2. Find the closest polygons to the start and goal positions.
3. Run A pathfinding* to find the shortest route through the polygons.
4. Output the final path, listing both polygon IDs and coordinates.
OUTPUT
CONCLUSION
AIM
To implement a Finite State Machine (FSM) for an NPC (Non-Player Character) Zombie
AI in a survival game using Python, where the NPC transitions between different behavioral
states.
SOFTWARE REQUIRED
THEORY
In modern video games, Non-Player Characters (NPCs) are controlled using Artificial
Intelligence (AI) techniques to make them behave realistically. One common approach to
implementing NPC behavior is using a Finite State Machine (FSM), which allows the NPC
to transition between different states based on external events and conditions.
In this experiment, we implement Zombie AI for a Survival Game using FSM in Python.
The zombie will have different states like Wandering, Investigating, Chasing, Attacking,
Feeding, and Searching, making its behavior dynamic and engaging.
CODE
import time
import random
class ZombieFSM:
def init (self):
self.state = "Wandering"
self.search_time = 0 # Time zombie spends investigating
self.hunger_level = random.randint(3, 6) # Hunger increases aggression
if event == "saw_player":
print("Zombie: *Found fresh meat! (Chasing!)*")
self.state = "Chasing"
def get_state(self):
return self.state
EXPLANATION
This Python program implements a Finite State Machine (FSM) for a Zombie NPC in a
Survival Game. The zombie reacts to environmental stimuli such as noises and the player's
presence, transitioning between different states based on events.
• The on_event(event) method processes events and changes the zombie’s state accordingly.
• The zombie begins in the Wandering state and transitions based on stimuli:
o Noise → Investigating
o Seeing a player → Chasing
o Losing sight of the player → Searching
o Getting close to the player → Attacking
o Killing the player → Feeding
o If hunger is satisfied → Wandering again
• The FSM detects environment changes via events (heard_noise, saw_player, etc.).
• Events simulate an AI perception system in a real game.
1. Wandering
o The zombie roams without a specific target.
o If it hears a noise, it moves to Investigating.
o If it sees the player, it starts Chasing.
2. Investigating
o The zombie looks for the source of the noise.
o If the search time expires, it resumes wandering.
o If it spots the player, it switches to Chasing.
3. Chasing
o The zombie aggressively follows the player.
o If it loses sight of the player, it starts Searching.
o If it gets close enough, it switches to Attacking.
4. Attacking
o The zombie tries to bite the player.
o If the player escapes, it transitions to Searching.
o If the player dies, it starts Feeding.
5. Searching
o The zombie looks for the player for a while.
o If the search time expires, it resumes wandering.
6. Feeding
o The zombie eats the player's body for a few cycles.
o Once its hunger is satisfied, it returns to Wandering.
The provided code runs a simulation where events are triggered sequentially.
Analysis
OUTPUT
CONCLUSION
In this experiment, we designed and implemented Finite State Machine (FSM) based Zombie
AI for a Survival Game using Python. The FSM model allows the zombie to react
dynamically to external stimuli, providing engaging and challenging gameplay for the player.
This approach is widely used in game development to create intelligent and realistic NPC
behaviors.
EXPERIMENT- 5
AIM
SOFTWARE REQUIRED
THEORY
A Behavior Tree (BT) is a hierarchical decision-making model used in game AI. It consists of
different node types that control NPC (Non-Playable Character) behavior in a structured way. Unlike
Finite State Machines (FSM), BTs provide modularity, flexibility, and reusability.
Key Components of a Behavior Tree
1. Control Nodes
o Selector: Tries each child node until one succeeds.
o Sequence: Executes children in order; fails if any fail.
2. Decorator Nodes
o Modify the behavior of a child (e.g., Inverter flips success/failure).
3. Leaf Nodes
o Condition Nodes: Check game states (e.g., "Is the player nearby?").
o Action Nodes: Perform actions (e.g., attack, move, flee).
CODE
import random
def run(self):
for child in self.children:
if child.run(): # If any child succeeds, return success
return True
return False # If all fail, return failure
class Sequence(Node):
def init (self, children):
self.children = children
def run(self):
for child in self.children:
if not child.run(): # If any child fails, return failure
return False
return True # If all succeed, return success
class ChasePlayer(Node):
def run(self):
print("[Action] Zombie is chasing the player...")
return True
class CheckAttackRange(Node):
def run(self):
"""Check if the player is within attack range"""
in_range = random.choice([True, False])
print(f"[Check] Player in Attack Range: {in_range}")
return in_range
class AttackPlayer(Node):
def run(self):
print("[Action] Zombie is attacking the player!")
return random.choice([True, False]) # Attack might fail
class CheckHealth(Node):
def init (self):
self.health = random.randint(10, 100) # Random initial health
def run(self):
"""Check if zombie has low health (below 30)"""
print(f"[Check] Zombie Health: {self.health}")
return self.health < 30
class Retreat(Node):
def run(self):
print("[Action] Zombie is retreating to recover...")
return True
class Wander(Node):
def run(self):
print("[Action] Zombie is wandering around...")
return True
EXPLANATION
This code implements a Behavior Tree (BT) to control a Zombie AI in a game. The tree allows the
zombie to make dynamic decisions such as chasing the player, attacking, retreating when health
is low, or wandering if idle.
The base node class defines the structure of all behavior tree nodes. Each node must implement a
run() method, which determines whether the action succeeds (True) or fails (False).
These nodes determine how different behaviors are executed in a structured way.
• Selector Node: Tries each child node one by one. If any child succeeds, it stops and returns
success. If all fail, it returns failure.
• Sequence Node: Executes its children in order. If any child fails, the sequence stops and
returns failure. If all succeed, it returns success.
• CheckPlayerNearby: Randomly determines if the player is near. If true, the zombie chases
the player.
• ChasePlayer: Executes the action of chasing the player when nearby.
• CheckAttackRange: Checks whether the zombie is close enough to attack.
• AttackPlayer: If in range, the zombie attacks the player, but the attack may fail.
• CheckHealth: Determines whether the zombie's health is low (below a threshold).
• Retreat: If health is low, the zombie retreats to recover.
• Wander: If the zombie is not engaged in combat, it wanders randomly in the environment.
The tree is structured with different behaviors prioritized using Selector and Sequence nodes. The
logic follows:
The behavior tree is run multiple times, simulating multiple decision cycles. Each cycle determines
the zombie’s action based on random conditions, making the AI dynamic and unpredictable.
This structure allows the zombie to react logically and adaptively to the game environment, making
the AI behavior more engaging and realistic.
OUTPUT
CONCLUSION
This experiment demonstrated the effectiveness of Behavior Trees (BTs) for Zombie AI in a
survival game. Compared to Finite State Machines (FSMs), BTs offer modularity,
flexibility, and scalability for decision-making. The zombie dynamically chased, attacked,
retreated, or wandered based on conditions, making its behavior more realistic and
adaptive. This structured AI approach enhances gameplay immersion and can be further
improved with group coordination, environmental awareness, and advanced behaviors.
EXPERIMENT- 6
AIM
SOFTWARE REQUIRED
THEORY
In game AI, Behavior Trees (BTs) provide a structured and modular approach to decision-
making, allowing AI characters to execute actions based on logical conditions. However, BTs alone
follow a fixed hierarchical structure, making them less flexible in dynamic situations.
Utility Theory enhances BTs by introducing a numerical decision-making system, where actions
are assigned utility values based on game conditions such as health, distance to the player, and
attack success rates. The action with the highest utility is chosen, ensuring that AI behavior is more
adaptive and context-sensitive rather than following rigid priority orders.
By integrating Utility Theory with Behavior Trees, the Zombie AI can:
• Dynamically switch behaviors based on environmental factors.
• Prioritize actions like chasing, attacking, retreating, or wandering based on real-time
utility calculations.
• Make more realistic and unpredictable decisions, improving gameplay immersion.
This hybrid approach creates a scalable, intelligent, and efficient AI model, making NPCs more
responsive and lifelike.
CODE
import random
def run(self):
for child in self.children:
if child.run(): # If any child succeeds, return success
return True
return False # If all fail, return failure
class Sequence(Node):
def init (self, children):
self.children = children
def run(self):
for child in self.children:
if not child.run(): # If any child fails, return failure
return False
return True # If all succeed, return success
def run(self):
# Calculate utility for each action
utilities = {action: utility() for action, utility in self.actions.items()}
# Select the action with the highest utility
best_action = max(utilities, key=utilities.get)
print(f"[Decision] Selected action: {best_action. class . name } with utility
{utilities[best_action]:.2f}")
return best_action.run()
class ChasePlayer(Node):
def run(self):
print("[Action] Zombie is chasing the player...")
return True
class CheckAttackRange(Node):
def run(self):
"""Check if the player is within attack range"""
in_range = random.choice([True, False])
print(f"[Check] Player in Attack Range: {in_range}")
return in_range
class AttackPlayer(Node):
def run(self):
print("[Action] Zombie is attacking the player!")
return random.choice([True, False]) # Attack might fail
class CheckHealth(Node):
def init (self):
self.health = random.randint(10, 100) # Random initial health
def run(self):
"""Check if zombie has low health (below 30)"""
print(f"[Check] Zombie Health: {self.health}")
return self.health < 30
class Retreat(Node):
def run(self):
print("[Action] Zombie is retreating to recover...")
return True
class Wander(Node):
def run(self):
print("[Action] Zombie is wandering around...")
return True
def utility_attack():
"""Higher utility if the player is in attack range"""
return random.uniform(0.7, 1.0) if random.choice([True, False]) else random.uniform(0.2, 0.5)
def utility_retreat():
"""Higher utility if health is low"""
return random.uniform(0.8, 1.0) if random.randint(0, 100) < 30 else random.uniform(0.1, 0.3)
def utility_wander():
"""Default wandering action when no other priority exists"""
return random.uniform(0.2, 0.6)
This code integrates Behavior Trees (BTs) with Utility Theory to create an adaptive Zombie AI in
a survival game. The AI makes decisions dynamically based on logical conditions and utility scores,
ensuring more realistic and flexible behavior.
1. Core Components
CONCLUSION
Combining Utility Theory with Behavior Trees creates a dynamic and adaptive Zombie
AI. Unlike static decision-making, this approach allows real-time evaluation of factors like
health, distance, and attack success, making zombies more responsive and unpredictable.
It enhances realism, scalability, and gameplay immersion, ensuring a challenging and
engaging survival experience.