0% found this document useful (0 votes)
48 views

All Practical AI

The document describes programs to solve two problems: 1) A Tic-Tac-Toe game using Python code that implements the game logic and checks for a win or draw. 2) A water jug problem using BFS and DFS search algorithms in Python and C++ respectively to find the steps to reach a target amount of water from empty jugs.

Uploaded by

55 Manvi
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
48 views

All Practical AI

The document describes programs to solve two problems: 1) A Tic-Tac-Toe game using Python code that implements the game logic and checks for a win or draw. 2) A water jug problem using BFS and DFS search algorithms in Python and C++ respectively to find the steps to reach a target amount of water from empty jugs.

Uploaded by

55 Manvi
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 28

Artificial Intelligence [3170716] | Manvi Savani

Practical 1
Write a program to implement Tic-Tac-Toe game problem.
 Code:
import os
import time
board = [' ',' ',' ',' ',' ',' ',' ',' ',' ',' ']
player = 1
Win = 1
Draw = -1
Running = 0
Stop = 1
Game = Running
Mark = 'X'
def DrawBoard():
print(" %c | %c | %c " % (board[1],board[2],board[3]))
print("___|___|___")
print(" %c | %c | %c " % (board[4],board[5],board[6]))
print("___|___|___")
print(" %c | %c | %c " % (board[7],board[8],board[9]))
print(" | | ")
def CheckPosition(x):
if(board[x] == ' '):
return True
else:
return False
def CheckWin():
global Game
if(board[1] == board[2] and board[2] == board[3] and board[1] != ' '):
Game = Win
elif(board[4] == board[5] and board[5] == board[6] and board[4] != ' '):
Game = Win
elif(board[7] == board[8] and board[8] == board[9] and board[7] != ' '):
Game = Win
elif(board[1] == board[4] and board[4] == board[7] and board[1] != ' '):
Game = Win
elif(board[2] == board[5] and board[5] == board[8] and board[2] != ' '):
Game = Win
elif(board[3] == board[6] and board[6] == board[9] and board[3] != ' '):
Game=Win

190760107055 1
Artificial Intelligence [3170716] | Manvi Savani

elif(board[1] == board[5] and board[5] == board[9] and board[5] != ' '):


Game = Win
elif(board[3] == board[5] and board[5] == board[7] and board[5] != ' '):
Game=Win
elif(board[1]!=' ' and board[2]!=' ' and board[3]!=' ' and board[4]!=' ' and board[5]!='
'
and board[6]!=' ' and board[7]!=' ' and board[8]!=' ' and board[9]!=' '):
Game=Draw
else:
Game=Running
print("Player 1 [X] --- Player 2 [O]\n")
print()
print()
print("Please Wait...")
time.sleep(3)
while(Game == Running):
os.system('cls')
DrawBoard()
if(player % 2 != 0):
print("Player 1's chance")
Mark = 'X'
else:
print("Player 2's chance")
Mark = 'O'
choice = int(input("Enter the position between [1-9] where you want to mark : "))
if(CheckPosition(choice)):
board[choice] = Mark
player+=1
CheckWin()
os.system('cls')
DrawBoard()
if(Game==Draw):
print("Game Draw")
elif(Game==Win):
player-=1
if(player%2!=0):
print("Player 1 Won")
else:
print("Player 2 Won")

190760107055 2
Artificial Intelligence [3170716] | Manvi Savani

 Output:

Practical 2
190760107055 3
Artificial Intelligence [3170716] | Manvi Savani

Write a program to implement BFS for Water Jug problem.


 Code: (Python)
from collections import defaultdict
Jug1= int(input("Enter 1st jug capacity :"))
Jug2= int(input("Enter 2nd jug capacity :"))
aim = int(input("Enter target :"))
visited = defaultdict(lambda: False)
def waterJugSolver(amt1, amt2):
if (amt1 == aim and amt2 == 0) or (amt2 == aim and amt1 == 0):
print(amt1, amt2)
return True
if visited[(amt1, amt2)] == False:
print(amt1, amt2)
visited[(amt1, amt2)] = True
return (waterJugSolver(0, amt2) or
waterJugSolver(amt1, 0) or
waterJugSolver(jug1, amt2) or
waterJugSolver(amt1, jug2) or
waterJugSolver(amt1 + min(amt2, (jug1-amt1)),
amt2 - min(amt2, (jug1-amt1))) or
waterJugSolver(amt1 - min(amt1, (jug2-amt2)),
amt2 + min(amt1, (jug2-amt2))))
else:
return False
print("Steps: ")
waterJugSolver(0, 0)

 Output:

190760107055 4
Artificial Intelligence [3170716] | Manvi Savani

Practical 3

190760107055 5
Artificial Intelligence [3170716] | Manvi Savani

Write a program to implement DFS for Water Jug problem.


 Code: (C++)
#include <cstdio>
#include <stack>
#include <map>
#include <algorithm>
using namespace std;
struct state {
int x, y;
bool operator < (const state& that) const {
if (x != that.x) return x < that.x;
return y < that.y;
}
};
int capacity_x, capacity_y, target;
void dfs(state start, stack <pair <state, int> >& path) {
stack <state> s;
state goal = (state) {-1, -1};
map <state, pair <state, int> > parentOf;
s.push(start);
parentOf[start] = make_pair(start, 0);
while (!s.empty()) {
state top = s.top();
s.pop();
if (top.x == target || top.y == target) {
goal = top;
break;
}
if (top.x < capacity_x) {
state child = (state) {capacity_x, top.y};
if (parentOf.find(child) == parentOf.end()) {
s.push(child);
parentOf[child] = make_pair(top, 1);
}
}
if (top.y < capacity_y) {
state child = (state) {top.x, capacity_y};
if (parentOf.find(child) == parentOf.end()) {
s.push(child);
parentOf[child] = make_pair(top, 2);

190760107055 6
Artificial Intelligence [3170716] | Manvi Savani

}
}
if (top.x > 0) {
state child = (state) {0, top.y};
if (parentOf.find(child) == parentOf.end()) {
s.push(child);
parentOf[child] = make_pair(top, 3);
}
}
if (top.y > 0) {
state child = (state) {top.x, 0};
if (parentOf.find(child) == parentOf.end()) {
s.push(child);
parentOf[child] = make_pair(top, 4);
}
}
if (top.y > 0) {
state child = (state) {min(top.x + top.y, capacity_x), max(0, top.x + top.y -
capacity_x)};
if (parentOf.find(child) == parentOf.end()) {
s.push(child);
parentOf[child] = make_pair(top, 5);
}
}
if (top.x > 0) {
state child = (state) {max(0, top.x + top.y - capacity_y), min(top.x + top.y,
capacity_y)};
if (parentOf.find(child) == parentOf.end()) {
s.push(child);
parentOf[child] = make_pair(top, 6);
}
}
}
if (goal.x == -1 || goal.y == -1)
return;
path.push(make_pair(goal, 0));
while (parentOf[path.top().first].second != 0)
path.push(parentOf[path.top().first]);
}

190760107055 7
Artificial Intelligence [3170716] | Manvi Savani

int main() {
stack <pair <state, int> > path;
printf("Enter the capacities of the two jugs : ");
scanf("%d %d", &capacity_x, &capacity_y);
printf("Enter the target amount : ");
scanf("%d", &target);
dfs((state) {0, 0}, path);
if (path.empty())
printf("\nTarget cannot be reached.\n");
else {
printf("\nNumber of moves to reach the target : %d\nOne path to the target is as
follows :\n", path.size() - 1);
while (!path.empty()) {
state top = path.top().first;
int rule = path.top().second;
path.pop();
switch (rule) {
case 0: printf("State : (%d, %d)\n#\n", top.x, top.y);
break;
case 1: printf("State : (%d, %d)\nAction : Fill the first jug\n", top.x,
top.y);
break;
case 2: printf("State : (%d, %d)\nAction : Fill the second jug\n", top.x,
top.y);
break;
case 3: printf("State : (%d, %d)\nAction : Empty the first jug\n", top.x,
top.y);
break;
case 4: printf("State : (%d, %d)\nAction : Empty the second jug\n", top.x,
top.y);
break;
case 5: printf("State : (%d, %d)\nAction : Pour from second jug into first
jug\n", top.x, top.y);
break;
case 6: printf("State : (%d, %d)\nAction : Pour from first jug into second
jug\n", top.x, top.y);
break;
}
}
}

190760107055 8
Artificial Intelligence [3170716] | Manvi Savani

return 0;
}

 Output:

190760107055 9
Artificial Intelligence [3170716] | Manvi Savani

Practical 4
Write a program to implement Single Player Game.
 Code:
import random
def guess(x):
random_number = random.randint(1, x)
guess = 0
while guess != random_number:
guess = int(input(f'Guess a number between 1 and {x}: '))
if guess < random_number:
print('Sorry, guess again. Too low.')
elif guess > random_number:
print('Sorry, guess again. Too high.')
print(f'Yay, congrats. You have guessed the number {random_number} correctly!!')
def computer_guess(x):
low = 1
high = x
feedback = ''
while feedback != 'c':
if low != high:
guess = random.randint(low, high)
else:
guess = low # could also be high b/c low = high
feedback = input(f'Is {guess} too high (H), too low (L), or correct (C)??
').lower()
if feedback == 'h':
high = guess - 1
elif feedback == 'l':
low = guess + 1
print(f'Yay! The computer guessed your number, {guess}, correctly!')
guess(10)

190760107055 10
Artificial Intelligence [3170716] | Manvi Savani

 Output:

190760107055 11
Artificial Intelligence [3170716] | Manvi Savani

Practical 5
Write a program to Implement A* Algorithm.
 Code:
from __future__ import annotations
DIRECTIONS = [
[-1, 0],
[0, -1],
[1, 0],
[0, 1],
]
def search(
grid: list[list[int]],
init: list[int],
goal: list[int],
cost: int,
heuristic: list[list[int]],
) -> tuple[list[list[int]], list[list[int]]]:
closed = [
[0 for col in range(len(grid[0]))] for row in range(len(grid))
]
closed[init[0]][init[1]] = 1
action = [
[0 for col in range(len(grid[0]))] for row in range(len(grid))
]
x = init[0]
y = init[1]
g=0
f = g + heuristic[x][y]
cell = [[f, g, x, y]]
found = False
resign = False
while not found and not resign:
if len(cell) == 0:
raise ValueError("Algorithm is unable to find solution")
else:
cell.sort()
cell.reverse()
next_cell = cell.pop()
x = next_cell[2]

190760107055 12
Artificial Intelligence [3170716] | Manvi Savani

y = next_cell[3]
g = next_cell[1]
if x == goal[0] and y == goal[1]:
found = True
else:
for i in range(len(DIRECTIONS)):
x2 = x + DIRECTIONS[i][0]
y2 = y + DIRECTIONS[i][1]
if x2 >= 0 and x2 < len(grid) and y2 >= 0 and y2 < len(grid[0]):
if closed[x2][y2] == 0 and grid[x2][y2] == 0:
g2 = g + cost
f2 = g2 + heuristic[x2][y2]
cell.append([f2, g2, x2, y2])
closed[x2][y2] = 1
action[x2][y2] = i
invpath = []
x = goal[0]
y = goal[1]
invpath.append([x, y])
while x != init[0] or y != init[1]:
x2 = x - DIRECTIONS[action[x][y]][0]
y2 = y - DIRECTIONS[action[x][y]][1]
x = x2
y = y2
invpath.append([x, y])
path = []
for i in range(len(invpath)):
path.append(invpath[len(invpath) - 1 - i])
return path, action
if __name__ == "__main__":
grid = [
[0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 1, 0],
[0, 0, 0, 0, 1, 0],
]
print("Initial Map")
print(grid)
init = [0, 0]

190760107055 13
Artificial Intelligence [3170716] | Manvi Savani

goal = [len(grid) - 1, len(grid[0]) - 1]


cost = 1
heuristic = [[0 for row in range(len(grid[0]))] for col in range(len(grid))]
for i in range(len(grid)):
for j in range(len(grid[0])):
heuristic[i][j] = abs(i - goal[0]) + abs(j - goal[1])
if grid[i][j] == 1:
heuristic[i][j] = 99
path, action = search(grid, init, goal, cost, heuristic)
print("Action Map")
for i in range(len(action)):
print(action[i])
print("Output")
for i in range(len(path)):
print(path[i])

 Output:

190760107055 14
Artificial Intelligence [3170716] | Manvi Savani

Practical 6
Write a program to implement mini-max algorithm for any game
development.
 Code:
from math import inf as infinity
from random import choice
import platform
import time
from os import system
HUMAN = -1
COMP = +1
board = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
]
def evaluate(state):
if wins(state, COMP):
score = +1
elif wins(state, HUMAN):
score = -1
else:
score = 0
return score
def wins(state, player):
win_state = [
[state[0][0], state[0][1], state[0][2]],
[state[1][0], state[1][1], state[1][2]],
[state[2][0], state[2][1], state[2][2]],
[state[0][0], state[1][0], state[2][0]],
[state[0][1], state[1][1], state[2][1]],
[state[0][2], state[1][2], state[2][2]],
[state[0][0], state[1][1], state[2][2]],
[state[2][0], state[1][1], state[0][2]],
]
if [player, player, player] in win_state:
return True
else:
return False

190760107055 15
Artificial Intelligence [3170716] | Manvi Savani

def game_over(state):
return wins(state, HUMAN) or wins(state, COMP)
def empty_cells(state):
cells = []
for x, row in enumerate(state):
for y, cell in enumerate(row):
if cell == 0:
cells.append([x, y])
return cells
def valid_move(x, y):
if [x, y] in empty_cells(board):
return True
else:
return False
def set_move(x, y, player):
if valid_move(x, y):
board[x][y] = player
return True
else:
return False
def minimax(state, depth, player):
if player == COMP:
best = [-1, -1, -infinity]
else:
best = [-1, -1, +infinity]
if depth == 0 or game_over(state):
score = evaluate(state)
return [-1, -1, score]
for cell in empty_cells(state):
x, y = cell[0], cell[1]
state[x][y] = player
score = minimax(state, depth - 1, -player)
state[x][y] = 0
score[0], score[1] = x, y
if player == COMP:
if score[2] > best[2]:
best = score
else:
if score[2] < best[2]:
best = score

190760107055 16
Artificial Intelligence [3170716] | Manvi Savani

return best
def clean():
os_name = platform.system().lower()
if 'windows' in os_name:
system('cls')
else:
system('clear')
def render(state, c_choice, h_choice):
chars = {
-1: h_choice,
+1: c_choice,
0: ' '
}
str_line = '---------------'
print('\n' + str_line)
for row in state:
for cell in row:
symbol = chars[cell]
print(f'| {symbol} |', end='')
print('\n' + str_line)
def ai_turn(c_choice, h_choice):
depth = len(empty_cells(board))
if depth == 0 or game_over(board):
return
clean()
print(f'Computer turn [{c_choice}]')
render(board, c_choice, h_choice)
if depth == 9:
x = choice([0, 1, 2])
y = choice([0, 1, 2])
else:
move = minimax(board, depth, COMP)
x, y = move[0], move[1]
set_move(x, y, COMP)
time.sleep(1)
def human_turn(c_choice, h_choice):
depth = len(empty_cells(board))
if depth == 0 or game_over(board):
return
move = -1

190760107055 17
Artificial Intelligence [3170716] | Manvi Savani

moves = {
1: [0, 0], 2: [0, 1], 3: [0, 2],
4: [1, 0], 5: [1, 1], 6: [1, 2],
7: [2, 0], 8: [2, 1], 9: [2, 2],
}
clean()
print(f'Human turn [{h_choice}]')
render(board, c_choice, h_choice)
while move < 1 or move > 9:
try:
move = int(input('Use numpad (1..9): '))
coord = moves[move]
can_move = set_move(coord[0], coord[1], HUMAN)
if not can_move:
print('Bad move')
move = -1
except (EOFError, KeyboardInterrupt):
print('Bye')
exit()
except (KeyError, ValueError):
print('Bad choice')
def main():
clean()
h_choice = ''
c_choice = ''
first = ''
while h_choice != 'O' and h_choice != 'X':
try:
print('')
h_choice = input('Choose X or O\nChosen: ').upper()
except (EOFError, KeyboardInterrupt):
print('Bye')
exit()
except (KeyError, ValueError):
print('Bad choice')
if h_choice == 'X':
c_choice = 'O'
else:
c_choice = 'X'
clean()

190760107055 18
Artificial Intelligence [3170716] | Manvi Savani

while first != 'Y' and first != 'N':


try:
first = input('First to start?[y/n]: ').upper()
except (EOFError, KeyboardInterrupt):
print('Bye')
exit()
except (KeyError, ValueError):
print('Bad choice')
while len(empty_cells(board)) > 0 and not game_over(board):
if first == 'N':
ai_turn(c_choice, h_choice)
first = ''
human_turn(c_choice, h_choice)
ai_turn(c_choice, h_choice)
if wins(board, HUMAN):
clean()
print(f'Human turn [{h_choice}]')
render(board, c_choice, h_choice)
print('YOU WIN!')
elif wins(board, COMP):
clean()
print(f'Computer turn [{c_choice}]')
render(board, c_choice, h_choice)
print('YOU LOSE!')
else:
clean()
render(board, c_choice, h_choice)
print('DRAW!')
exit()
if __name__ == '__main__':
main()

190760107055 19
Artificial Intelligence [3170716] | Manvi Savani

 Output:

190760107055 20
Artificial Intelligence [3170716] | Manvi Savani

Practical 7 and 8
Assume given a set of facts of the form father(name1,name2) (name1 is the
father of name2).
Define a predicate brother(X,Y) which holds iff X and Y are brothers.
Define a predicate cousin(X,Y) which holds iff X and Y are cousins.
Define a predicate grandson(X,Y) which holds iff X is a grandson of Y.
Define a predicate descendent(X,Y) which holds iff X is a descendent of Y.
Consider the following genealogical tree:
father(a,b).
father(a,c).
father(b,d).
father(b,e).
father(c,f).
Say which answers, and in which order, are generated by your definitions for
the following queries in Prolog:
?- brother(X,Y).
?- cousin(X,Y).
?- grandson(X,Y).
?- descendent(X,Y).
 Code:
father(a,b).
father(a,c).
father(b,d).
father(b,e).
father(c,f).
brother(X,Y) :- father(Z,X), father(Z,Y), not(X=Y).
cousin(X,Y) :- father(Z,X), father(W,Y), brother(Z,W).
grandson(X,Y) :- father(Z,X), father(Y,Z).
descendent(X,Y) :- father(Y,X).
descendent(X,Y) :- father(Z,X), descendent(Z,Y).

190760107055 21
Artificial Intelligence [3170716] | Manvi Savani

 Output:

190760107055 22
Artificial Intelligence [3170716] | Manvi Savani

Practical 9
Write a program to solve Tower of Hanoi problem using Prolog.
 Code:
move(1,X,Y,_) :-
write('Move top disk from '), write(X), write(' to '), write(Y), nl.
move(N,X,Y,Z) :-
N>1,
M is N-1,
move(M,X,Z,Y),
move(1,X,Y,_),
move(M,Z,Y,X).

 Output:

190760107055 23
Artificial Intelligence [3170716] | Manvi Savani

Practical 10
Write a program to solve N-Queens problem using Prolog.
 Code:
:- use_rendering(chess).
queens(N, Queens) :-
length(Queens, N),
board(Queens, Board, 0, N, _, _),
queens(Board, 0, Queens).
board([], [], N, N, _, _).
board([_|Queens], [Col-Vars|Board], Col0, N, [_|VR], VC) :-
Col is Col0+1,
functor(Vars, f, N),
constraints(N, Vars, VR, VC),
board(Queens, Board, Col, N, VR, [_|VC]).
constraints(0, _, _, _) :- !.
constraints(N, Row, [R|Rs], [C|Cs]) :-
arg(N, Row, R-C),
M is N-1,
constraints(M, Row, Rs, Cs).
queens([], _, []).
queens([C|Cs], Row0, [Col|Solution]) :-
Row is Row0+1,
select(Col-Vars, [C|Cs], Board),
arg(Row, Vars, Row-Row),
queens(Board, Row, Solution).

 Output:

190760107055 24
Artificial Intelligence [3170716] | Manvi Savani

Practical 11
Write a program to solve 8 puzzle problem using Prolog.
 Code:
test(Plan):-
write('Initial state:'),nl,
Init= [at(tile4,1), at(tile3,2), at(tile8,3), at(empty,4), at(tile2,5), at(tile6,6), at(tile5,7),
at(tile1,8), at(tile7,9)],
write_sol(Init),
Goal= [at(tile1,1), at(tile2,2), at(tile3,3), at(tile4,4), at(empty,5), at(tile5,6),
at(tile6,7), at(tile7,8), at(tile8,9)],
nl,write('Goal state:'),nl,
write(Goal),nl,nl,
solve(Init,Goal,Plan).
solve(State, Goal, Plan):-
solve(State, Goal, [], Plan).
is_movable(X1,Y1) :- (1 is X1 - Y1) ; (-1 is X1 - Y1) ; (3 is X1 - Y1) ; (-3 is X1 - Y1).
solve(State, Goal, Plan, Plan):-
is_subset(Goal, State), nl,
write_sol(Plan).
solve(State, Goal, Sofar, Plan):-
act(Action, Preconditions, Delete, Add),
is_subset(Preconditions, State),
\+ member(Action, Sofar),
delete_list(Delete, State, Remainder),
append(Add, Remainder, NewState),
solve(NewState, Goal, [Action|Sofar], Plan).
act(move(X,Y,Z),
[at(X,Y), at(empty,Z), is_movable(Y,Z)],
[at(X,Y), at(empty,Z)],
[at(X,Z), at(empty,Y)]).
is_subset([H|T], Set):-
member(H, Set),
is_subset(T, Set).
is_subset([], _).
delete_list([H|T], Curstate, Newstate):-
remove(H, Curstate, Remainder),
delete_list(T, Remainder, Newstate).
delete_list([], Curstate, Curstate).

190760107055 25
Artificial Intelligence [3170716] | Manvi Savani

remove(X, [X|T], T).


remove(X, [H|T], [H|R]):-
remove(X, T, R).
write_sol([]).
write_sol([H|T]):-
write_sol(T),
write(H), nl.
append([H|T], L1, [H|L2]):-
append(T, L1, L2).
append([], L, L).
member(X, [X|_]).
member(X, [_|T]):-
member(X, T).

 Output:

190760107055 26
Artificial Intelligence [3170716] | Manvi Savani

Practical 12
Write a program to solve travelling salesman problem using Prolog.
 Code:
edge(a, b, 3).
edge(a, c, 4).
edge(a, d, 2).
edge(a, e, 7).
edge(b, c, 4).
edge(b, d, 6).
edge(b, e, 3).
edge(c, d, 5).
edge(c, e, 8).
edge(d, e, 6).
edge(b, a, 3).
edge(c, a, 4).
edge(d, a, 2).
edge(e, a, 7).
edge(c, b, 4).
edge(d, b, 6).
edge(e, b, 3).
edge(d, c, 5).
edge(e, c, 8).
edge(e, d, 6).
edge(a, h, 2).
edge(h, d, 1).
len([], 0).
len([H|T], N):- len(T, X), N is X+1 .
best_path(Visited, Total):- path(a, a, Visited, Total).
path(Start, Fin, Visited, Total) :- path(Start, Fin, [Start], Visited, 0, Total).
path(Start, Fin, CurrentLoc, Visited, Costn, Total) :-
edge(Start, StopLoc, Distance), NewCostn is Costn + Distance, \+ member(StopLoc,
CurrentLoc),
path(StopLoc, Fin, [StopLoc|CurrentLoc], Visited, NewCostn, Total).
path(Start, Fin, CurrentLoc, Visited, Costn, Total) :-
edge(Start, Fin, Distance), reverse([Fin|CurrentLoc], Visited), len(Visited, Q),
(Q\=7 -> Total is 100000; Total is Costn + Distance).
shortest_path(Path):-setof(Cost-Path, best_path(Path,Cost), Holder),pick(Holder,Path).
best(Cost-Holder,Bcost-_,Cost-Holder):- Cost<Bcost,!.
best(_,X,X).

190760107055 27
Artificial Intelligence [3170716] | Manvi Savani

pick([Cost-Holder|R],X):- pick(R,Bcost-Bholder),best(Cost-Holder,Bcost-Bholder,X),!.
pick([X],X).

 Output:

190760107055 28

You might also like