MicroSft IDE for Bison and Flex
https://github.com/Ishgar14/Artificial-Intelligence
https://github.com/JyotBuch/ESE-Codes/tree/master/AI%20Assignments
1. Tic tac Magic Square
import random
import sys
from random import randint
computer_turn_counter = 0
def printBoard(board):
print(' ' + board[1] + ' |' + board[2] + ' |' + board[3])
print(' ' + board[4] + ' |' + board[5] + ' |' + board[6])
print(' ' + board[7] + ' |' + board[8] + ' |' + board[9])
def isSpaceFree(board, move):
if board[move] == ' ':
return True
else:
False
def my_custom_random(takenmoves):
# Returns a valid move from the passed list on the passed board.
# Returns None if there is no valid move.
allmoves = [1,2,3,4,5,6,7,8,9]
movesList = list(set(allmoves) - set(takenmoves))
return random.choice(movesList)
def updateMove(board, letter , move):
board[move] = letter
def getPlayerMove(board):
# Let the player type in their move.
move = ' '
while move not in '1 2 3 4 5 6 7 8 9'.split() or not isSpaceFree(board, int(move)):
print('What is your next move? (1-9)')
move = input()
return int(move)
def getComputersMove(board, computerletter, playerletter, dict ,list, computer_turn_counter):
global magicboard
checkforplayer = 0
print("Comp Turns Completed : ",computer_turn_counter)
#If First Time
if (computer_turn_counter == 0):
move = my_custom_random(list )
computer_turn_counter=+1
return move , computer_turn_counter
#Second Time
if (computer_turn_counter == 1):
computer_turn_counter= 2
#try to get middle element
if (isSpaceFree(board,5)):
move = 5
return 5 , computer_turn_counter
else:
checkforplayer = 1
#Checks self wining chances
if (computer_turn_counter and len(dict[computerletter]) > 1 ):
print("|| In self wining chances ")
computer_turn_counter=3
length = len(dict[computerletter])
for i in range(0,length - 1):
#Formula
diff = 15 - (magicboard[ dict[computerletter][i] -1 ] + magicboard[ dict[computerletter][length - 1] - 1 ])
print("The Diff: 15 - (",magicboard[ dict[computerletter][i] -1 ] ," + ",magicboard[ dict[computerletter][length - 1] - 1 ],"): " ,diff)
if(diff in magicboard):
checkforplayer = 1
else:
checkforplayer = 1
continue
index = magicboard.index(diff) + 1
print("The Index to be placed at is : ", index )
if (index <= 9 and index > 0):
if isSpaceFree(board, index) :
print("Returned the Difference in self win")
return index , computer_turn_counter
else:
print("Cant Add , Position is Taken Already")
checkforplayer = 1
else :
checkforplayer = 1
#Checks to Defeat the Player
if checkforplayer == 1:
print("|| In Defeat the Player ")
length = len(dict[playerletter])
for i in range(0,length - 1):
#Formula
diff = 15 - (magicboard[ dict[playerletter][i] -1 ] + magicboard[ dict[playerletter][length - 1] - 1 ] )
print("The Diff: 15 - (",magicboard[ dict[playerletter][i] -1 ] ," + ",magicboard[ dict[playerletter][length - 1] - 1 ],"): " ,diff)
if(magicboard.index(diff)):
if(magicboard.index(diff) > 9 or magicboard.index(diff) <= 0):
checkforplayer = 1
continue
index = magicboard.index(diff) + 1
print("The Index to be placed at is : ", index )
if (index <= 9 and index > 0):
if isSpaceFree(board, index) :
print("Returned the Difference defeat player")
return index , computer_turn_counter
else:
print("Cant Add , Position is Taken Already")
checkforplayer = 1
else :
checkforplayer = 1
computer_turn_counter = computer_turn_counter + 1
return my_custom_random(list) , computer_turn_counter
def isWinner(bo, le):
# Given a board and a player’s letter, this function returns True if that player has won.
# We use bo instead of board and le instead of letter so we don’t have to type as much.
return ((bo[7] == le and bo[8] == le and bo[9] == le) or # across the top
(bo[4] == le and bo[5] == le and bo[6] == le) or # across the middle
(bo[1] == le and bo[2] == le and bo[3] == le) or # across the bottom
(bo[7] == le and bo[4] == le and bo[1] == le) or # down the left side
(bo[8] == le and bo[5] == le and bo[2] == le) or # down the middle
(bo[9] == le and bo[6] == le and bo[3] == le) or # down the right side
(bo[7] == le and bo[5] == le and bo[3] == le) or # diagonal
(bo[9] == le and bo[5] == le and bo[1] == le)) # diagonal
def makechoice():
#TicTacToe()
x = 1
while(x):
choice = input("Choose your Player X or O : ")
if choice =="x" or choice=="X":
print("Player has chosen X and will go First\n")
x = 0
playerletter = "X"
computerletter = "O"
turn = "player"
elif choice =="O" or choice=="o":
print("Player has chosen O , Bot will go First\n")
x = 0
playerletter = "O"
turn = "computer"
computerletter = "X"
else:
print("Not an option, IDIOT. Choose again.\n")
return playerletter,computerletter,turn
def isBoardFull(board):
# Return True if every space on the board has been taken. Otherwise return False.
for i in range(1, 10):
if isSpaceFree(board, i):
return False
return True
magicboard = [8,3,4,
1,5,9,
6,7,2]
dict = {
"X":[],
"O":[]
}
LIST = []
board = [' '] * 10
playerletter,computerletter,turn = "X", "O", "player"
#playerletter,computerletter,turn = makechoice()
gamePlaying = True
while gamePlaying:
if turn =="player":
#Player’s turn.
printBoard(board)
move = getPlayerMove(board)
dict[playerletter].append(move )
LIST.append(move)
print(dict)
updateMove(board,playerletter,move)
if isWinner(board, playerletter):
printBoard(board)
print('****************** Hooray! You have won the game! ******************')
sys.exit("Thank You For Playing!")
gameIsPlaying = False
else:
if isBoardFull(board):
print('****************** The game is a tie! ****************** ')
printBoard(board)
sys.exit("Thank You For Playing!")
break
else:
turn = 'computer'
else:
#Computer's Turn
move , computer_turn_counter = getComputersMove(board,computerletter ,playerletter, dict ,LIST , computer_turn_counter)
dict[computerletter].append(move )
LIST.append(move)
print(dict)
updateMove(board,computerletter,move)
if isWinner(board, computerletter):
printBoard(board)
print('You Lost to a bOt! .')
sys.exit("Thank You For Playing!")
gameIsPlaying = False
else:
if isBoardFull(board):
board(board)
print('The game is a tie!')
break
else:
turn = 'player'
Tic Tac Minimax
PLAYER_CHARACTER = 'O'
COMPUTER_CHARACTER = 'X'
board = {
1: ' ', 2: ' ', 3: ' ',
4: ' ', 5: ' ', 6: ' ',
7: ' ', 8: ' ', 9: ' '
}
def display_board(board):
print(
board[1].center(5, ' ') + '|' +
board[2].center(5, ' ') + '|' +
board[3].center(5, ' ')
)
print('-' * 17)
print(
board[4].center(5, ' ') + '|' +
board[5].center(5, ' ') + '|' +
board[6].center(5, ' ')
)
print('-' * 17)
print(
board[7].center(5, ' ') + '|' +
board[8].center(5, ' ') + '|' +
board[9].center(5, ' ')
)
print('\n')
def is_free(pos):
return board[pos] == ' '
def is_draw():
for key in board.keys():
if board[key] == ' ':
return False
return True
def somebody_won():
# Row check
if board[1] == board[2] and board[1] == board[3] and board[1] != ' ':
return True
elif board[4] == board[5] and board[4] == board[6] and board[4] != ' ':
return True
elif board[7] == board[8] and board[7] == board[9] and board[7] != ' ':
return True
# Column check
elif board[1] == board[4] and board[1] == board[7] and board[1] != ' ':
return True
elif board[2] == board[5] and board[2] == board[8] and board[2] != ' ':
return True
elif board[3] == board[6] and board[3] == board[9] and board[3] != ' ':
return True
# Diagonal check
elif board[1] == board[5] and board[1] == board[9] and board[1] != ' ':
return True
elif board[7] == board[5] and board[7] == board[3] and board[7] != ' ':
return True
else:
return False
def has_won(mark):
# Row check
if board[1] == board[2] and board[1] == board[3] and board[1] == mark:
return True
elif board[4] == board[5] and board[4] == board[6] and board[4] == mark:
return True
elif board[7] == board[8] and board[7] == board[9] and board[7] == mark:
return True
# Column check
elif board[1] == board[4] and board[1] == board[7] and board[1] == mark:
return True
elif board[2] == board[5] and board[2] == board[8] and board[2] == mark:
return True
elif board[3] == board[6] and board[3] == board[9] and board[3] == mark:
return True
# Diagoanl check
elif board[1] == board[5] and board[1] == board[9] and board[1] == mark:
return True
elif board[7] == board[5] and board[7] == board[3] and board[7] == mark:
return True
else:
return False
def putchar(character, position):
if is_free(position):
board[position] = character
display_board(board)
if is_draw():
print("\nIt was a tie 👨🤝💻")
exit()
if somebody_won():
if character == COMPUTER_CHARACTER:
print("\nThe computer wins 💻")
else:
print("\nThe player wins 🤴")
exit()
else:
print("This slot is already used!")
position = int(input("Enter new position: "))
putchar(character, position)
def get_player_move():
print(" Player's Turn ".center(40, '='))
pos = int(input("Enter the position for 'O': "))
putchar(PLAYER_CHARACTER, pos)
def get_computer_move():
print(" Computer's Turn ".center(40, '='))
best_score = -100
best_move = 0
for key in board.keys():
if board[key] != ' ':
continue
board[key] = COMPUTER_CHARACTER
score = minimax(board, False)
board[key] = ' '
if score > best_score:
best_score = score
best_move = key
print(f"Computer found move {best_move} with score {best_score}")
print(f"Computer picks move {best_move} with score {best_score}")
putchar(COMPUTER_CHARACTER, best_move)
def minimax(board, maximising):
if has_won(COMPUTER_CHARACTER):
return 10
if has_won(PLAYER_CHARACTER):
return -10
elif is_draw():
return 0
if maximising:
bestScore = -1000
for key in board.keys():
if board[key] == ' ':
board[key] = COMPUTER_CHARACTER
score = minimax(board, False)
board[key] = ' '
if score > bestScore:
bestScore = score
return bestScore
else:
bestScore = 1000
for key in board.keys():
if board[key] == ' ':
board[key] = PLAYER_CHARACTER
score = minimax(board, True)
board[key] = ' '
if score < bestScore:
bestScore = score
return bestScore
def main():
display_board(board)
first = input("Do you wish to play first (Y/N): ").strip().upper()
turn = first == 'Y'
while not somebody_won():
if turn:
get_player_move()
else:
get_computer_move()
turn = not turn
if __name__ == '__main__':
main()
N queens
STUCK = 0
DONE = 1
steps = 0
def generate(size):
square = []
# Generate a square of dimensions `size`
for _ in range(size):
listofzeros = [0] * size
square.append(listofzeros)
#print(square)
return square
# This function reutrns true if given position is safe for new queen
def is_safe(board: list, row: int, col: int) -> bool:
# Check the row
for i in range(len(board)):
if board[row][i] == 1:
return False
# Check the column
for i in range(len(board)):
if board[i][col] == 1:
return False
# Check the left diagonal
for difference in range(1, len(board)):
_row = row - difference
_col = col - difference
if _row < 0 or _col < 0:
break
if board[_row][_col] == 1:
return False
for difference in range(1, len(board)):
_row = row + difference
_col = col + difference
if _row >= len(board) or _col >= len(board):
break
if board[_row][_col] == 1:
return False
# Check the right diagonal
for difference in range(1, len(board)):
_row = row + difference
_col = col - difference
if _row >= len(board) or _col < 0:
break
if board[_row][_col] == 1:
return False
for difference in range(1, len(board)):
_row = row - difference
_col = col + difference
if _row < 0 or _col >= len(board):
break
if board[_row][_col] == 1:
return False
return True
# This function takes board and row and returns all safe column spots of that row
def get_all_safe_spots(board: list, row: int) -> list:
spots = []
for column in range(len(board)):
if is_safe(board, row, column):
spots.append(column)
return spots
def solve(board, row: int = 0):
if row >= len(board):
return DONE
global steps
print("\n", f" Step {steps} ".center(20, '='))
display_board(board)
steps += 1
spots = get_all_safe_spots(board, row)
if len(spots) == 0:
print("No safe spots left for current queen!")
return STUCK
for i in range(len(board)):
if i not in spots:
print(f"Skipping column {i + 1} because it is not safe")
continue
spot = i
board[row][spot] = 1
# If we can't place next queen then backtrack and select next position for current queen
if solve(board, row + 1) == STUCK:
print(f"Backtracking from row {row + 1} column {spot + 1} ..")
board[row][spot] = 0
else:
# If every position of queen is filled then its done
if sum([sum(row) for row in board]) == len(board):
print("\n", f" Step {steps} ".center(20, '='))
display_board(board)
exit()
# If every position of queen is filled then its done
if sum([sum(row) for row in board]) == len(board):
return DONE
else:
# Otherwise backtrack
print(f"Backtracking from row {row + 1} column {spot + 1} ...")
return STUCK
def display_board(board: list, special: bool = True) -> None:
if not special:
for i in range(len(board)):
for j in range(len(board)):
if board[i][j] == 1:
print('{:3}'.format(1), end='')
else:
print('{:>3}'.format('.'), end='')
print()
return
for i in range(len(board)):
solid = i % 2 == 0
for j in range(len(board)):
if board[i][j] == 1:
print('{:>3}'.format('👑'), end='')
else:
if solid:
print('{:>3}'.format('⬛'), end='')
else:
print('{:>3}'.format('⬜'), end='')
solid = not solid
print()
def main():
size = int(input("Enter size of chessboard: "))
board = generate(size)
if solve(board) == STUCK:
print("\nThere are no valid queen positions for given dimensions of board")
return
print("\n", " Finally ".center(20, '='))
display_board(board, True)
if __name__ == '__main__':
main()
Magic Square
def generate(size: int) -> list:
EMPTY = 0
square = []
# Generate a square of dimensions `size`
for _ in range(size):
buffer = []
for _ in range(size):
buffer.append(EMPTY)
square.append(buffer)
counter = 1
row = 0
col = len(square) // 2
# Pick middle of first row as 1
square[row][col] = counter
while counter != size ** 2:
print("\n", f" Step {counter} ".center(20, '='), sep='')
display(square)
counter += 1
cyclic_row = row - 1
cyclic_column = col + 1
cyclic_row = cycle(cyclic_row, size)
cyclic_column = cycle(cyclic_column, size)
# Check if North-East slot is available
if square[cyclic_row][cyclic_column] == 0:
square[cyclic_row][cyclic_column] = counter
row, col = cyclic_row, cyclic_column
else:
# Otherwise go South
row += 1
square[row][col] = counter
return square
# For verbosity ... The entire thing could've been a single % opeartion lmao
def cycle(number: int, size: int) -> int:
if number == size:
return 0
elif number > size:
return number % size
elif number < 0:
number *= -1
number %= size
number = size - number
return number
else:
return number
def display(board: list) -> None:
for row in board:
for col in row:
print('{:5}'.format(col), end='')
print()
def main():
while True:
size = int(input("Enter size of square: "))
if size % 2 == 0:
print("Please type an odd number!")
continue
board = generate(size)
print("\n", f" Step {size ** 2} ".center(20, '='))
display(board)
break
if __name__ == '__main__':
main()
Assn2
Water Jug BFS
LEFT_BUCKET_CAPACITY = 4
RIGHT_BUCKET_CAPACITY = 3
GOAL = (0, 2)
RESULT = []
def move_left_to_right(jug):
allowed_space = min(RIGHT_BUCKET_CAPACITY - jug[1], jug[0])
return (jug[0] - allowed_space, jug[1] + allowed_space)
def move_right_to_left(jug):
allowed_space = min(LEFT_BUCKET_CAPACITY - jug[0], jug[1])
return (jug[0] + allowed_space, jug[1] - allowed_space)
def empty_left(jug):
return (0, jug[1])
def empty_right(jug):
return (jug[0], 0)
def fill_left(jug):
return (LEFT_BUCKET_CAPACITY, jug[1])
def fill_right(jug):
return (jug[0], RIGHT_BUCKET_CAPACITY)
def get_available_operations(jug):
operations = {
move_left_to_right,
move_right_to_left,
empty_left,
empty_right,
fill_left,
fill_right
}
# if left jug is empty
if jug[0] == 0:
operations.remove(empty_left)
operations.remove(move_left_to_right)
# if left jug is full
elif jug[0] == LEFT_BUCKET_CAPACITY:
operations.remove(fill_left)
operations.remove(move_right_to_left)
# if right jug is empty
if jug[1] == 0:
operations.remove(empty_right)
try: operations.remove(move_right_to_left)
except KeyError: pass
# if right jug is full
elif jug[1] == RIGHT_BUCKET_CAPACITY:
operations.remove(fill_right)
try: operations.remove(move_left_to_right)
except KeyError: pass
return operations
def get_operation_name(operation) -> str:
return {
fill_left: 'fill left jug',
fill_right: 'fill right jug',
empty_left: 'empty left jug',
empty_right: 'empty right jug',
move_left_to_right: 'pour left jug into right jug',
move_right_to_left: 'pour right jug into left jug',
}[operation]
class Node:
def __init__(self, jug: tuple[int, int], parent = None, operation_name: str = None) -> None:
self.jug = jug
self.parent = parent
self.operation_name = operation_name
def grow_tree(parent: Node, previous = {(0, 0)}) -> bool:
queue = [parent]
opened = []
closed = []
level = 1
while len(queue) != 0:
node = queue.pop(0) # Remove first elemnt from queue
opened.append(node.jug)
operations = get_available_operations(node.jug)
# Iterate over all operations for current node
# Assign the child nodes to parent
for op in operations:
child_jug = op(node.jug)
child = Node(child_jug, node, get_operation_name(op))
closed.append(child_jug)
if child_jug == GOAL:
RESULT.append(child)
return True
if child_jug in previous:
continue
else:
previous.add(child_jug)
queue.append(child)
print(f" At breadth level {level} ".center(40, '='))
print("Opened list: ", opened)
print("Closed list: ", closed, end='\n\n')
level += 1
return False
def main():
seed = Node((0, 0))
if grow_tree(seed):
print("=" * 40)
print("The full path is")
for endpoint in RESULT:
path = []
operations: list[str] = []
while endpoint.parent:
path.append(endpoint.jug)
operations.append(endpoint.operation_name)
endpoint = endpoint.parent
path = list(reversed(path))
operations = list(reversed(operations))
print("From (0, 0)")
for i, _ in enumerate(path):
print(f'Step {i + 1} {operations[i].ljust(30)} => {path[i]}')
else:
print("Could not reach the goal", GOAL)
if __name__ == '__main__':
main()
# For (0, 2)
# (0, 0) -> (0, 3) -> (3, 0) -> (3, 3) -> (4, 2) -> (0, 2)
Water Jug DFS
def LevelOrderTraversal(root):
if (root == None):
return;
# Standard level order traversal code
# using queue
q = [] # Create a queue
q.append(root); # Enqueue root
while (len(q) != 0):
n = len(q);
# If this node has children
while (n > 0):
# Dequeue an item from queue and print it
p = q[0]
q.pop(0)
print(p.list, end=' ')
# Enqueue all children of the dequeued item
for i in range(len(p.child)):
q.append(p.child[i]);
n -= 1
print() # Print new line between two levels
print("-------")
def fill4LitreJug(list):
if list[0] == 4:
return False
list[0] = 4
return list
def fill3LitreJug(list):
if list[1] == 3:
return False
list[1] = 3
return list
def empty3(list):
listt = list.copy()
if listt[1] == 0:
return False
listt[1] = 0
return listt
def empty4(list):
listt = list.copy()
if listt[0] == 0:
return False
listt[0] = 0
return listt
def transferFrom_3to4(listt):
if listt[0]==4:
return False
elif listt[1] == 0:
return False
elif listt[0] < 4:
if (4 - listt[0]) >= listt[1]:
listt[0] = listt[0] + listt[1]
listt[1] = 0
else:
#avail = 2 , hai second mei 3
emptySpace = 4 - listt[0]
listt[1] = listt[1] - emptySpace
listt[0] = listt[0] + emptySpace
return listt
def transferFrom_4to3(listt):
if listt[1]==3:
return False
elif listt[0] == 0:
return False
elif listt[1] < 3:
if (3 - listt[1]) >= listt[0]:
listt[1] = listt[1] + listt[0]
listt[0] = 0
else:
#avail = 2 , hai second mei 3
emptySpace = 3 - listt[1]
listt[0] = listt[0] - emptySpace
listt[1] = listt[1] + emptySpace
return listt
GOAL = [2,0]
class Node:
def __init__(self, list):
self.list = list
self.child = []
self.myParents = []
# Utility function to create a new tree node
def newNode(key):
temp = Node(key)
return temp
# Prints the n-ary tree level wise
answerslist = []
#list = [0 , 0]
#-------------------------------------------------------------------
def findOptimalPath(node):
if node.myParents:
if node.myParents[-1] ==GOAL:
#print("--",node.myParents[-1],"--")
answerslist.append(node.myParents)
if node.list in node.myParents:
return
childrenlist = []
#print("Passed Node: ",node.list)
list1 = empty4((node.list).copy())
list2 = empty3((node.list).copy())
list3 = transferFrom_3to4((node.list).copy())
list4 = transferFrom_4to3((node.list).copy())
list5 = fill4LitreJug((node.list).copy())
list6 = fill3LitreJug((node.list).copy())
#print("lists: ", list1, list2 ,list3, list4 , list5 , list6)
childrenlist.extend((list1, list2, list3,list4,list5,list6))
childrenlist = [x for x in childrenlist if x is not False]
#print("Childrenlist: ",childrenlist)
#print("\n")
for i in range(0,len(childrenlist)):
(node.child).append(newNode(childrenlist[i]))
node.child[i].myParents.extend(node.myParents)
node.child[i].myParents.append(node.list)
if node.child[i].myParents[-1] ==GOAL:
answerslist.append(node.child[i].myParents)
return
#print("myParents: ", i +1," : ", node.child[i].myParents )
findOptimalPath(node.child[i])
return
#-------------------------------------------------------------------
# WE START HERE ! -----
mainlist= [0, 0]
root = newNode(mainlist)
findOptimalPath(root)
#print("AnswersList: ", answerslist)
print("AnswersList")
print('\n'.join(map(str, answerslist)))
smallest = []
for i in answerslist:
smallest.append(len(i))
print("\nThe Most Optimal Path to this Water Jug Problem is :\n", answerslist[smallest.index(min(smallest))])
#---
# RunTime Values
# Infinity to 0.9s to 0.5s to 0.1s
LevelOrderTraversal(root)
Rush dfs
LEFT_BUCKET_CAPACITY = 4
RIGHT_BUCKET_CAPACITY = 3
GOAL = (0, 2)
RESULT = []
def move_left_to_right(jug) -> tuple[int, int]:
allowed_space = min(RIGHT_BUCKET_CAPACITY - jug[1], jug[0])
return (jug[0] - allowed_space, jug[1] + allowed_space)
def move_right_to_left(jug) -> tuple[int, int]:
allowed_space = min(LEFT_BUCKET_CAPACITY - jug[0], jug[1])
return (jug[0] + allowed_space, jug[1] - allowed_space)
def empty_left(jug) -> tuple[int, int]:
return (0, jug[1])
def empty_right(jug) -> tuple[int, int]:
return (jug[0], 0)
def fill_left(jug) -> tuple[int, int]:
return (LEFT_BUCKET_CAPACITY, jug[1])
def fill_right(jug) -> tuple[int, int]:
return (jug[0], RIGHT_BUCKET_CAPACITY)
def get_available_operations(jug):
operations = {
move_left_to_right,
move_right_to_left,
empty_left,
empty_right,
fill_left,
fill_right
}
# if left jug is empty
if jug[0] == 0:
operations.remove(empty_left)
operations.remove(move_left_to_right)
# if left jug is full
elif jug[0] == LEFT_BUCKET_CAPACITY:
operations.remove(fill_left)
operations.remove(move_right_to_left)
# if right jug is empty
if jug[1] == 0:
operations.remove(empty_right)
try: operations.remove(move_right_to_left)
except KeyError: pass
# if right jug is full
elif jug[1] == RIGHT_BUCKET_CAPACITY:
operations.remove(fill_right)
try: operations.remove(move_left_to_right)
except KeyError: pass
return operations
def get_operation_name(operation) -> str:
return {
fill_left: 'fill left jug',
fill_right: 'fill right jug',
empty_left: 'empty left jug',
empty_right: 'empty right jug',
move_left_to_right: 'pour left jug into right jug',
move_right_to_left: 'pour right jug into left jug',
}[operation]
class Node:
def __init__(self, jug: tuple[int, int], operation_name: str = None) -> None:
self.jug = jug
self.children: list[Node] = []
self.operation_name = operation_name
def __eq__(self, other):
if isinstance(other, Node):
return self.jug == other.jug
elif isinstance(other, tuple):
return self.jug == other
else:
return False
def __repr__(self) -> str:
return str(self.jug)
def grow_tree(node: Node, previous = {(0, 0)}, maxdepth = 10,
opened: list[tuple[int, int]] = [], closed: list[tuple[int, int]] = []
) -> bool:
if maxdepth == 0:
return False
operations = get_available_operations(node.jug)
for _op in operations:
closed.append(_op(node.jug))
opened.append(node.jug)
closed = [val for i, val in enumerate(closed) if val not in opened and val not in closed[:i]]
print("\n", f" At depth level {10 - maxdepth} ".center(40, '='), sep='')
print("Opened list: ", opened)
print("Closed list: ", closed, end='\n\n')
for op in operations:
child = op(node.jug)
if child == GOAL:
RESULT.append(Node(GOAL, get_operation_name(op)))
return True
if child in previous:
continue
else:
previous.add(child)
node.children.append(Node(child, get_operation_name(op)))
if grow_tree(node.children[-1], previous, maxdepth - 1, opened, closed):
RESULT.append(node.children[-1])
return True
else:
node.children.pop()
return False
# This function prunes the nodes which can be reached directly by root (0, 0)
def optimise(result: list[Node]) -> list[Node]:
operations = get_available_operations((0, 0))
for op in operations:
child = op((0, 0))
try:
index = result.index(child)
result = result[index:]
except ValueError:
pass
return result
def main():
seed = Node((0, 0))
if grow_tree(seed):
print('=' * 40)
print("The full path is")
print("From (0, 0)".rjust(49))
result = list(reversed(RESULT))
result = optimise(result)
for i, node in enumerate(result):
print(f'Step {i + 1} {node.operation_name.ljust(30)} => {node.jug}')
else:
print("Could not reach the goal", GOAL)
if __name__ == '__main__':
main()
# For (0, 2)
# (0, 0) -> (0, 3) -> (3, 0) -> (3, 3) -> (4, 2) -> (0, 2)
Assn3
8 puzzle Hill Climbing
GOAL = {
1: 1, 2: 2, 3: 3,
4: 8, 5: -1, 6: 4,
7: 7, 8: 6, 9: 5,
}
t_board = dict[int, int]
# ============================= Helper Functions ==================================
class Node:
def __init__(self, board: dict[int, int], steps: int, prev=None, op=None):
self.board = board.copy()
self.steps = steps
self.prev = prev
self.operation = op
def __eq__(self, other):
return self.board == other.board
def __hash__(self) -> int:
return heuristic(self.board)
def __repr__(self):
return str(heuristic(self.board))
def heu(self) -> int:
return self.steps + heuristic(self.board)
def get_empty_pos(puzzle: t_board) -> int:
for key in puzzle:
if puzzle[key] == -1:
return key
def move_up(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos - 3] = puzzle[pos - 3], puzzle[pos]
return puzzle
def move_down(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos + 3] = puzzle[pos + 3], puzzle[pos]
return puzzle
def move_right(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos + 1] = puzzle[pos + 1], puzzle[pos]
return puzzle
def move_left(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos - 1] = puzzle[pos - 1], puzzle[pos]
return puzzle
def get_available_operations(puzzle: t_board):
operations = {
move_up,
move_left, move_right,
move_down,
}
empty_pos = get_empty_pos(puzzle)
if empty_pos in {1, 2, 3}:
operations.remove(move_up)
if empty_pos in {7, 8, 9}:
operations.remove(move_down)
if empty_pos in {3, 6, 9}:
operations.remove(move_right)
if empty_pos in {1, 4, 7}:
operations.remove(move_left)
return operations
def get_operation_name(op):
return {
move_up: "Move the empty slot above",
move_left: "Move the empty slot left",
move_right: "Move the empty slot right",
move_down: "Move the empty slot down",
}[op]
# This function takes a board and returns heuristic value of it
# Heuristic is how many board are misplaced
def heuristic(puzzle: t_board) -> int:
score = 0
for key in puzzle:
if puzzle[key] != GOAL[key]:
score += 1
return score
def safe_get(puzzle: t_board, key: int) -> str:
if puzzle[key] != -1:
return puzzle[key]
return '@'
def display_board(puzzle: dict[int, int]):
print(
str(safe_get(puzzle, 1)).center(3, ' ') +
str(safe_get(puzzle, 2)).center(3, ' ') +
str(safe_get(puzzle, 3)).center(3, ' ')
)
print('-' * 10)
print(
str(safe_get(puzzle, 4)).center(3, ' ') +
str(safe_get(puzzle, 5)).center(3, ' ') +
str(safe_get(puzzle, 6)).center(3, ' ')
)
print('-' * 10)
print(
str(safe_get(puzzle, 7)).center(3, ' ') +
str(safe_get(puzzle, 8)).center(3, ' ') +
str(safe_get(puzzle, 9)).center(3, ' ')
)
# ========================= Actual Hill Climbing Logic ====================================
def start(puzzle) -> Node:
root = Node(puzzle, 0)
queue = [root]
steps = 0
previous = set()
best_score = 1000
while len(queue) > 0:
steps += 1
queue.sort(key=lambda n: n.heu())
current = queue.pop(0)
score = heuristic(current.board)
if score == 0:
return current
if current in previous:
# print("Skipping current state because it was repeated previously ...")
continue
else:
previous.add(current)
if score < best_score:
best_score = score
for operation in get_available_operations(current.board):
child = Node(operation(current.board.copy()), steps, current, operation)
queue.append(child)
return current
def main():
puzzle = {
1: 1, 2: -1, 3: 3,
4: 8, 5: 2, 6: 6,
7: 7, 8: 5, 9: 4,
}
print("The initial board configuration is ")
display_board(puzzle)
print(f"The heuristic of this board is {heuristic(puzzle)}")
node = start(puzzle)
path: list[Node] = []
while node:
path.append(node)
node = node.prev
path = path[::-1]
path = path[1:]
for i, node in enumerate(path):
print(f" Step {i + 1} ".center(40, '='))
if node.operation:
print(get_operation_name(node.operation))
display_board(node.board)
print(f"The heuristic of this board is {heuristic(node.board)}")
# print(f"The f(x) of this board is {node.heu()}")
if heuristic(path[-1].board) != 0:
print("Not found ideal board position")
else:
print("This is the required solution✅")
if __name__ == '__main__':
main()
Assn4
Astar
Astar - Robot Nav
# Board Contents
EMPTY = 0
WALL = 1
SELECTED = 2
class Node:
def __init__(self, x, y, prev = None):
self.x = x
self.y = y
self.prev = prev
def __eq__(self, other):
if isinstance(other, Node):
return self.x == other.x and self.y == other.y
elif isinstance(other, tuple):
return self.x == other[0] and self.y == other[1]
def __repr__(self):
return str((self.x, self.y))
def __hash__(self):
return (self.x, self.y).__hash__()
def man_dist(self, to) -> int:
return abs(self.x - to.x) + abs(self.y - to.y)
def heu(self, goal) -> int:
return self.man_dist(goal)
def neighbours(self, board) -> list:
neigh = [
# No diagonal neighbours
(self.x, self.y - 1), # Top
(self.x - 1, self.y), # Left
(self.x + 1, self.y), # Right
(self.x, self.y + 1), # Bottom
]
ugly_neighbours = []
for n in neigh:
x, y = n
if x < 0 or y < 0:
ugly_neighbours.append((x, y))
elif x >= len(board) or y >= len(board[0]):
ugly_neighbours.append((x, y))
elif board[x][y] == WALL:
ugly_neighbours.append((x, y))
for ugly in ugly_neighbours:
neigh.remove(ugly)
return [Node(n[0], n[1], self) for n in neigh]
# Defining custom type
Board = list[list[Node]]
def display_board(board, initial: Node, goal: Node) -> None:
print('', '-' * len(board[0] * 3))
for i, row in enumerate(board):
print(end='|')
for j, col in enumerate(row):
if Node(i, j) == initial:
ch='S'
elif Node(i, j) == goal:
ch='E'
elif col == EMPTY:
ch=' '
elif col == WALL:
ch='#'
elif col == SELECTED:
ch='.'
print("{:>3}".format(ch), end='')
print(end='|\n')
print('', '-' * len(board[0]) * 3)
def start(board, initial: Node, goal: Node) -> Node:
opened = []
closed = [Node(initial.x, initial.y)]
# Create a copy of board to display the steps of A*
d_board = []
for row in board:
d_board.append([])
for col in row:
d_board[-1].append(col)
# Actual A* Logic
previous = set()
while len(closed) > 0:
closed.sort(key = lambda n: n.heu(initial) + n.heu(goal))
current = closed.pop(0)
opened.append(current)
if current in previous:
continue
else:
previous.add(current)
d_board[current.x][current.y] = SELECTED
print('=' * 40)
print("Open List:", opened)
print("Close List:", closed)
print("\n\nCurrently selected ({}, {})".format(current.x, current.y))
display_board(d_board, initial, goal)
if current == goal:
print("Reached goal!")
break
for neighbour in current.neighbours(board):
if neighbour not in previous:
closed.append(neighbour)
return current
# For test case validity check
def safety_check(board, initial: tuple[int, int], goal: tuple[int, int]):
if board[initial[0]][initial[1]] == WALL:
raise ValueError("Initial Position cannot be a wall")
if board[goal[0]][goal[1]] == WALL:
raise ValueError("Goal Position cannot be a wall")
def main():
board = [
[0, 0, 0, 0, 0, 0, 0, 0, 0,],
[0, 1, 0, 0, 1, 1, 1, 1, 0,],
[0, 1, 0, 0, 0, 0, 0, 1, 0,],
[0, 0, 1, 1, 1, 1, 1, 1, 0,],
[0, 0, 0, 0, 0, 0, 0, 0, 0,],
]
initial = (3, 0)
goal = (2, 5)
safety_check(board, initial, goal)
end = start(board, Node(initial[0], initial[1]), Node(goal[0], goal[1]))
print("\nThe best path is ")
path = []
while end:
path.append((end.x, end.y))
end = end.prev
# Create a copy of board
d_board = []
for row in board:
d_board.append([])
for col in row:
d_board[-1].append(col)
# mutate it with path found
for (x, y) in path:
d_board[x][y] = SELECTED
display_board(d_board, initial, goal)
if __name__ == '__main__':
main()
Astar - City Dist
SOURCE = "Latur"
DESTINATION = "Navi Mumbai"
DISTANCE_FROM_DESTINATION = {
"Solapur": 340,
"Satara": 177,
"Navi Mumbai": 0,
"Nashik": 133,
"Ahmednagar": 180,
"Aurangabad": 265,
"Nanded": 451,
"Latur": 373,
"Pune": 105,
}
# A weighted undirected map of cities
GRAPH = {}
def put_city(city1: str, city2: str, weight: int) -> None:
global GRAPH
if city1 in GRAPH:
GRAPH[city1].append((city2, weight))
else:
GRAPH[city1] = [(city2, weight)]
if city2 in GRAPH:
GRAPH[city2].append((city1, weight))
else:
GRAPH[city2] = [(city1, weight)]
def generate_map() -> None:
put_city("Pune", "Navi Mumbai", 106)
put_city("Pune", "Satara", 112)
put_city("Pune", "Solapur", 234)
put_city("Solapur", "Satara", 203)
put_city("Solapur", "Latur", 104)
put_city("Nanded", "Latur", 113)
put_city("Nanded", "Aurangabad", 221)
put_city("Nashik", "Aurangabad", 159)
put_city("Nanded", "Ahmednagar", 267)
put_city("Pune", "Ahmednagar", 120)
put_city("Nashik", "Pune", 165)
put_city("Nashik", "Navi Mumbai", 136)
def distance(from_: str, to: str) -> int:
if from_ == to:
return 0
array = GRAPH[from_]
for name, weight in array:
if name == to:
return weight
return 10 ** 10
class Node:
def __init__(self, city: str, prev: object, weight: int) -> None:
self.city = city
self.prev = prev
self.weight = weight
def __repr__(self) -> str:
return self.city
def __eq__(self, other) -> bool:
if isinstance(other, Node):
return self.city == other.city
elif isinstance(other, str):
return self.city == other
else:
return False
def objective(city, parent) -> int:
return distance(city, parent) + DISTANCE_FROM_DESTINATION[city]
def main() -> None:
generate_map()
print('=' * 40)
print("Starting city is Latur")
print("Destination city is Navi Mumbai")
# from pprint import pprint
# pprint(GRAPH)
current = Node(SOURCE, None, 0)
opened = []
closed = [current]
while len(closed) > 0:
print('=' * 40)
print("Opened list:", opened)
print("Closed list:", closed)
closed.sort(
key=lambda node: objective(node.city, current),
reverse=True
)
current = closed.pop()
if current not in opened:
opened.append(current)
if current == DESTINATION:
print("🎉We got there!🎉".center(40, ' '))
break
print("Selected", current)
# Add all neighbours of current city into queue
print(f"The neighbouring cities of {current} are")
for name, dist in GRAPH[current.city]:
print(f"{name:>12} with distance {dist}")
if name not in opened:
closed.append(Node(name, current, dist))
print()
path = []
while current:
path.append(current)
current = current.prev
print("=" * 40)
print("The full path is ")
for city in path[::-1]:
if city.prev:
print(f"{city.prev.city} to {city.city} with distance {city.weight}")
if __name__ == '__main__':
main()
Astar - 8puzzle
GOAL = {
1: 1, 2: 2, 3: 3,
4: 8, 5: -1, 6: 4,
7: 7, 8: 6, 9: 5,
}
t_board = dict[int, int]
# ============================= Helper Functions ==================================
class Node:
def __init__(self, board: dict[int, int], steps: int, prev=None, op=None):
self.board = board.copy()
self.steps = steps
self.prev = prev
self.operation = op
def __eq__(self, other):
return self.board == other.board
def __hash__(self) -> int:
return heuristic(self.board)
def __repr__(self):
return str(self.heu())
def heu(self) -> int:
return self.steps + heuristic(self.board)
def get_empty_pos(puzzle: t_board) -> int:
for key in puzzle:
if puzzle[key] == -1:
return key
def move_up(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos - 3] = puzzle[pos - 3], puzzle[pos]
return puzzle
def move_down(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos + 3] = puzzle[pos + 3], puzzle[pos]
return puzzle
def move_right(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos + 1] = puzzle[pos + 1], puzzle[pos]
return puzzle
def move_left(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos - 1] = puzzle[pos - 1], puzzle[pos]
return puzzle
def get_available_operations(puzzle: t_board):
operations = {
move_up,
move_left, move_right,
move_down,
}
empty_pos = get_empty_pos(puzzle)
if empty_pos in {1, 2, 3}:
operations.remove(move_up)
if empty_pos in {7, 8, 9}:
operations.remove(move_down)
if empty_pos in {3, 6, 9}:
operations.remove(move_right)
if empty_pos in {1, 4, 7}:
operations.remove(move_left)
return operations
def get_operation_name(op):
return {
move_up: "Move the empty slot above",
move_left: "Move the empty slot left",
move_right: "Move the empty slot right",
move_down: "Move the empty slot down",
}[op]
# This function takes a board and returns heuristic value of it
# Heuristic is how many board are misplaced
def heuristic(puzzle: t_board) -> int:
score = 0
for key in puzzle:
if puzzle[key] != GOAL[key]:
score += 1
return score
def safe_get(puzzle: t_board, key: int) -> str:
if puzzle[key] != -1:
return puzzle[key]
return '@'
def display_board(puzzle: dict[int, int]):
print(
str(safe_get(puzzle, 1)).center(3, ' ') +
str(safe_get(puzzle, 2)).center(3, ' ') +
str(safe_get(puzzle, 3)).center(3, ' ')
)
print('-' * 10)
print(
str(safe_get(puzzle, 4)).center(3, ' ') +
str(safe_get(puzzle, 5)).center(3, ' ') +
str(safe_get(puzzle, 6)).center(3, ' ')
)
print('-' * 10)
print(
str(safe_get(puzzle, 7)).center(3, ' ') +
str(safe_get(puzzle, 8)).center(3, ' ') +
str(safe_get(puzzle, 9)).center(3, ' ')
)
# ========================= Actual Hill Climbing Logic ====================================
def start(puzzle) -> Node:
root = Node(puzzle, 0)
opened = []
closed = [root]
steps = 0
previous = set()
best_score = 1000
while len(closed) > 0:
print("=" * 40)
print("Opened:", opened)
print("Closed:", closed)
steps += 1
closed.sort(key=lambda n: n.heu())
current = closed.pop(0)
opened.append(current)
score = heuristic(current.board)
if score == 0:
return current
if current in previous:
# print("Skipping current state because it was repeated previously ...")
continue
else:
previous.add(current)
if score < best_score:
best_score = score
for operation in get_available_operations(current.board):
child = Node(operation(current.board.copy()), steps, current, operation)
closed.append(child)
return current
def main():
puzzle = {
1: 1, 2: -1, 3: 3,
4: 8, 5: 2, 6: 6,
7: 7, 8: 5, 9: 4,
}
print("The initial state is ")
display_board(puzzle)
print("The goal state is ")
display_board(GOAL)
node = start(puzzle)
path: list[Node] = []
while node:
path.append(node)
node = node.prev
path = path[::-1]
path = path[1:]
for i, node in enumerate(path):
print(f" Step {i + 1} ".center(40, '='))
if node.operation:
print(get_operation_name(node.operation))
display_board(node.board)
print(f"The objective function score of this board is {heuristic(node.board) + i + 1}")
if heuristic(path[-1].board) != 0:
print("Not found ideal board position")
else:
print("This is the required solution✅")
if __name__ == '__main__':
main()
Best First
Best First - Robot Nav
# Board Contents
EMPTY = 0
WALL = 1
SELECTED = 2
class Node:
def __init__(self, x, y, prev = None):
self.x = x
self.y = y
self.prev = prev
def __eq__(self, other):
if isinstance(other, Node):
return self.x == other.x and self.y == other.y
elif isinstance(other, tuple):
return self.x == other[0] and self.y == other[1]
def __repr__(self):
return str((self.x, self.y))
def __hash__(self):
return (self.x, self.y).__hash__()
def man_dist(self, to) -> int:
return abs(self.x - to.x) + abs(self.y - to.y)
def heu(self, goal) -> int:
return self.man_dist(goal)
def neighbours(self, board) -> list:
neigh = [
# No diagonal neighbours
(self.x, self.y - 1), # Top
(self.x - 1, self.y), # Left
(self.x + 1, self.y), # Right
(self.x, self.y + 1), # Bottom
]
ugly_neighbours = []
for n in neigh:
x, y = n
if x < 0 or y < 0:
ugly_neighbours.append((x, y))
elif x >= len(board) or y >= len(board[0]):
ugly_neighbours.append((x, y))
elif board[x][y] == WALL:
ugly_neighbours.append((x, y))
for ugly in ugly_neighbours:
neigh.remove(ugly)
return [Node(n[0], n[1], self) for n in neigh]
# Defining custom type
Board = list[list[Node]]
def display_board(board, initial: Node, goal: Node) -> None:
print('', '-' * len(board[0] * 3))
for i, row in enumerate(board):
print(end='|')
for j, col in enumerate(row):
if Node(i, j) == initial:
ch='S'
elif Node(i, j) == goal:
ch='E'
elif col == EMPTY:
ch=' '
elif col == WALL:
ch='#'
elif col == SELECTED:
ch='.'
print("{:>3}".format(ch), end='')
print(end='|\n')
print('', '-' * len(board[0]) * 3)
def start(board, initial: Node, goal: Node) -> Node:
opened = []
closed = [Node(initial.x, initial.y)]
# Create a copy of board to display the steps of Best First Search
d_board = []
for row in board:
d_board.append([])
for col in row:
d_board[-1].append(col)
# Actual Best First Logic
previous = set()
while len(closed) > 0:
closed.sort(key = lambda n: n.heu(goal))
current = closed.pop(0)
opened.append(current)
if current in previous:
continue
else:
previous.add(current)
d_board[current.x][current.y] = SELECTED
print('=' * 40)
print("Open List:", opened)
print("Close List:", closed)
print("\n\nCurrently selected ({}, {})".format(current.x, current.y))
display_board(d_board, initial, goal)
if current == goal:
print("Reached goal!")
break
for neighbour in current.neighbours(board):
if neighbour not in previous:
closed.append(neighbour)
return current
# For test case validity check
def safety_check(board, initial: tuple[int, int], goal: tuple[int, int]):
if board[initial[0]][initial[1]] == WALL:
raise ValueError("Initial Position cannot be a wall")
if board[goal[0]][goal[1]] == WALL:
raise ValueError("Goal Position cannot be a wall")
def main():
board = [
[0, 0, 0, 0, 0, 0, 0, 0, 0,],
[0, 1, 0, 0, 1, 1, 1, 1, 0,],
[0, 1, 0, 0, 0, 0, 0, 1, 0,],
[0, 0, 1, 1, 1, 1, 1, 1, 0,],
[0, 0, 0, 0, 0, 0, 0, 0, 0,],
]
initial = (3, 0)
goal = (2, 5)
safety_check(board, initial, goal)
end = start(board, Node(initial[0], initial[1]), Node(goal[0], goal[1]))
print("\nThe best path is ")
path = []
while end:
path.append((end.x, end.y))
end = end.prev
# Create a copy of board
d_board = []
for row in board:
d_board.append([])
for col in row:
d_board[-1].append(col)
# mutate it with path found
for (x, y) in path:
d_board[x][y] = SELECTED
display_board(d_board, initial, goal)
if __name__ == '__main__':
main()
Best First - City Dist
SOURCE = "Latur"
DESTINATION = "Navi Mumbai"
DISTANCE_FROM_DESTINATION = {
"Solapur": 340,
"Satara": 177,
"Navi Mumbai": 0,
"Nashik": 133,
"Ahmednagar": 180,
"Aurangabad": 265,
"Nanded": 451,
"Latur": 373,
"Pune": 105,
}
# A weighted undirected map of cities
GRAPH = {}
def put_city(city1: str, city2: str, weight: int) -> None:
global GRAPH
if city1 in GRAPH:
GRAPH[city1].append((city2, weight))
else:
GRAPH[city1] = [(city2, weight)]
if city2 in GRAPH:
GRAPH[city2].append((city1, weight))
else:
GRAPH[city2] = [(city1, weight)]
def generate_map() -> None:
put_city("Pune", "Navi Mumbai", 106)
put_city("Pune", "Satara", 112)
put_city("Pune", "Solapur", 234)
put_city("Solapur", "Satara", 203)
put_city("Solapur", "Latur", 104)
put_city("Nanded", "Latur", 113)
put_city("Nanded", "Aurangabad", 221)
put_city("Nashik", "Aurangabad", 159)
put_city("Nanded", "Ahmednagar", 267)
put_city("Pune", "Ahmednagar", 120)
put_city("Nashik", "Pune", 165)
put_city("Nashik", "Navi Mumbai", 136)
def distance(from_: str, to: str) -> int:
if from_ == to:
return 0
array = GRAPH[from_]
for name, weight in array:
if name == to:
return weight
return 10 ** 10
class Node:
def __init__(self, city: str, prev: object, weight: int) -> None:
self.city = city
self.prev = prev
self.weight = weight
def __repr__(self) -> str:
return self.city
def __eq__(self, other) -> bool:
if isinstance(other, Node):
return self.city == other.city
elif isinstance(other, str):
return self.city == other
else:
return False
def objective(city) -> int:
return DISTANCE_FROM_DESTINATION[city]
def main() -> None:
generate_map()
print('=' * 40)
print("Starting city is Latur")
print("Destination city is Navi Mumbai")
# from pprint import pprint
# pprint(GRAPH)
current = Node(SOURCE, None, 0)
opened = []
closed = [current]
while len(closed) > 0:
print('=' * 40)
print("Opened list:", opened)
print("Closed list:", closed)
closed.sort(
key=lambda node: objective(node.city),
reverse=True
)
current = closed.pop()
if current not in opened:
opened.append(current)
if current == DESTINATION:
print("🎉We got there!🎉".center(40, ' '))
break
print("Selected", current)
# Add all neighbours of current city into queue
print(f"The neighbouring cities of {current} are")
for name, dist in GRAPH[current.city]:
print(f"{name:>12} with distance {dist}")
if name not in opened:
closed.append(Node(name, current, dist))
print()
path = []
while current:
path.append(current)
current = current.prev
print("=" * 40)
print("The full path is ")
for city in path[::-1]:
if city.prev:
print(f"{city.prev.city} to {city.city} with distance {city.weight}")
if __name__ == '__main__':
main()
Best First - 8puzzle
GOAL = {
1: 1, 2: 2, 3: 3,
4: 8, 5: -1, 6: 4,
7: 7, 8: 6, 9: 5,
}
t_board = dict[int, int]
# ============================= Helper Functions ==================================
class Node:
def __init__(self, board: dict[int, int], steps: int, prev=None, op=None):
self.board = board.copy()
self.steps = steps
self.prev = prev
self.operation = op
def __eq__(self, other):
return self.board == other.board
def __hash__(self) -> int:
return heuristic(self.board)
def __repr__(self):
return str(self.heu())
def heu(self) -> int:
return self.steps + heuristic(self.board)
def get_empty_pos(puzzle: t_board) -> int:
for key in puzzle:
if puzzle[key] == -1:
return key
def move_up(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos - 3] = puzzle[pos - 3], puzzle[pos]
return puzzle
def move_down(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos + 3] = puzzle[pos + 3], puzzle[pos]
return puzzle
def move_right(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos + 1] = puzzle[pos + 1], puzzle[pos]
return puzzle
def move_left(puzzle: t_board):
pos = get_empty_pos(puzzle)
puzzle[pos], puzzle[pos - 1] = puzzle[pos - 1], puzzle[pos]
return puzzle
def get_available_operations(puzzle: t_board):
operations = {
move_up,
move_left, move_right,
move_down,
}
empty_pos = get_empty_pos(puzzle)
if empty_pos in {1, 2, 3}:
operations.remove(move_up)
if empty_pos in {7, 8, 9}:
operations.remove(move_down)
if empty_pos in {3, 6, 9}:
operations.remove(move_right)
if empty_pos in {1, 4, 7}:
operations.remove(move_left)
return operations
def get_operation_name(op):
return {
move_up: "Move the empty slot above",
move_left: "Move the empty slot left",
move_right: "Move the empty slot right",
move_down: "Move the empty slot down",
}[op]
def heuristic(puzzle: t_board) -> int:
# This function takes a board and returns heuristic value of it
# Heuristic is how many board are misplaced
score = 0
for key in puzzle:
if puzzle[key] != GOAL[key]:
score += 1
return score
def safe_get(puzzle: t_board, key: int) -> str:
if puzzle[key] != -1:
return puzzle[key]
return '@'
def display_board(puzzle: dict[int, int]):
print(
str(safe_get(puzzle, 1)).center(3, ' ') +
str(safe_get(puzzle, 2)).center(3, ' ') +
str(safe_get(puzzle, 3)).center(3, ' ')
)
print('-' * 10)
print(
str(safe_get(puzzle, 4)).center(3, ' ') +
str(safe_get(puzzle, 5)).center(3, ' ') +
str(safe_get(puzzle, 6)).center(3, ' ')
)
print('-' * 10)
print(
str(safe_get(puzzle, 7)).center(3, ' ') +
str(safe_get(puzzle, 8)).center(3, ' ') +
str(safe_get(puzzle, 9)).center(3, ' ')
)
# ========================= Actual Hill Climbing Logic ====================================
def start(puzzle) -> Node:
root = Node(puzzle, 0)
opened = []
closed = [root]
steps = 0
previous = set()
best_score = 1000
while len(closed) > 0:
print("=" * 40)
print("Opened:", opened)
print("Closed:", closed)
steps += 1
closed.sort(key=lambda n: heuristic(n.board))
current = closed.pop(0)
opened.append(current)
score = heuristic(current.board)
if score == 0:
return current
if current in previous:
# print("Skipping current state because it was repeated previously ...")
continue
else:
previous.add(current)
if score < best_score:
best_score = score
for operation in get_available_operations(current.board):
child = Node(operation(current.board.copy()),
steps, current, operation)
closed.append(child)
return current
def main():
puzzle = {
1: 1, 2: 3, 3: -1,
4: 8, 5: 2, 6: 6,
7: 7, 8: 5, 9: 4,
}
print("The initial board state is ")
display_board(puzzle)
print("The goal state is ")
display_board(GOAL)
print(f"The objective function score of this board is {heuristic(puzzle)}")
node = start(puzzle)
path: list[Node] = []
while node:
path.append(node)
node = node.prev
path = path[::-1]
path = path[1:]
for i, node in enumerate(path):
print(f" Step {i + 1} ".center(40, '='))
if node.operation:
print(get_operation_name(node.operation))
display_board(node.board)
print(f"The objective function score of this board is {heuristic(node.board)}")
if heuristic(path[-1].board) != 0:
print("Not found ideal board position")
else:
print("This is the required solution✅")
if __name__ == '__main__':
main()
Assn5
Crossword
def check_right(i, j, grid) -> tuple[int, int, int]:
counter = 0
while (counter + j) < len(grid[i]):
if grid[i][j + counter] == ' ':
counter += 1
else:
break
if counter < 2:
return None
else:
return (i, j, counter)
def check_down(i, j, grid) -> tuple[int, int, int]:
counter = 0
while (counter + i) < len(grid):
if grid[i + counter][j] == ' ':
counter += 1
else:
break
if counter < 2:
return None
else:
return (i, j, counter)
def get_across_slots(grid: list[str]):
accross_slots = []
i = 0
while i < len(grid):
j = 0
while j < len(grid[i]):
if grid[i][j] == ' ':
if slot := check_right(i, j, grid):
accross_slots.append(slot)
j += slot[2]
j += 1
i += 1
return accross_slots
def get_down_slots(grid: list[str]):
t_grid = []
# Get transpose of grid
for i in range(len(grid)):
string = ''.join([row[i] for row in grid])
t_grid.append(string)
down_slots = get_across_slots(t_grid)
# The down slots are for the transposed grid,
# so we need to convert them to our original grid's coordinates
down_slots = [(slot[1], slot[0], slot[2]) for slot in down_slots]
return down_slots
def start(across_words: list[str], down_words: list[str], grid: list[str]) -> None:
across_slots = get_across_slots(grid)
down_slots = get_down_slots(grid)
# We need a mutable grid, so we use list[list[str]]
mut_grid = []
for i in range(len(grid)):
arr = []
for j in range(len(grid[i])):
arr.append([grid[i][j]])
mut_grid.append(arr)
# Start filling the across words
i = 0
while len(across_words):
used = False
if used:
across_slots.pop(i)
else:
i = (i + 1) % len(down_slots)
if len(across_words[0]) == across_slots[i][2]:
x, y, _ = across_slots[i]
for counter, letter in enumerate(across_words[0]):
mut_grid[x][y + counter] = [letter]
else:
used = True
across_words.pop(0)
# Start filling the down words
i = 0
while len(down_words):
used = False
if used:
down_slots.pop(i)
else:
i = (i + 1) % len(down_slots)
if len(down_words[0]) == down_slots[i][2]:
x, y, _ = down_slots[i]
for counter, letter in enumerate(down_words[0]):
mut_grid[x + counter][y] = [letter]
else:
used = True
down_words.pop(0)
# Convert list[list[str]] to list[str]
grid = []
for i in range(len(mut_grid)):
string = ""
for j in range(len(mut_grid[i])):
for k in range(len(mut_grid[i][j])):
string += mut_grid[i][j][k][0]
grid.append(string)
return grid
def display_grid(grid: list[str]) -> None:
for row in grid:
for col in row:
print(f"{col:>2}", end='')
print()
def main() -> None:
ACROSS = ['HYBRID', 'EARTH']
DOWN = ['BREAD', 'HELMET']
grid = [
"########",
"# #",
"# # ####",
"# # ",
"# # ####",
"# # ####",
"# ######",
]
print(" The Initial Crossword is ".center(40, '='))
display_grid(grid)
print("The across words are:", ', '.join(ACROSS))
print("The down words are:", ', '.join(DOWN))
result = start(ACROSS, DOWN, grid)
print('\n\n')
print(" The Final Crossword is ".center(40, '='))
display_grid(result)
if __name__ == '__main__':
main()
Crypt on Git
https://github.com/Ishgar14/Artificial-Intelligence
MapColoring
Graph = dict[str, list[str]]
COLORS = ["red", "green", "blue", "yellow"]
def generate_graph() -> dict:
graph = {}
def put_neighbours(n1: str, n2: str):
if n1 in graph:
graph[n1].append(n2)
else:
graph[n1] = [n2]
if n2 in graph:
graph[n2].append(n1)
else:
graph[n2] = [n1]
put_neighbours('a', 'b')
put_neighbours('a', 'c')
put_neighbours('b', 'c')
put_neighbours('b', 'f')
put_neighbours('c', 'e')
put_neighbours('c', 'd')
put_neighbours('d', 'e')
put_neighbours('e', 'f')
put_neighbours('f', 'a')
put_neighbours('f', 'b')
put_neighbours('f', 'c')
put_neighbours('g', 'e')
return graph
def neighbour_colors(node: str, graph: Graph, node_colors: dict[str, str]) -> list[str]:
# This function returns colours of neighbours of `node`
neighbours = graph[node]
return { n:node_colors[n] for n in neighbours if n in node_colors }
def start(graph: Graph, node_colors: dict[str, str] = {}):
for key in graph:
if key in node_colors:
continue
nc = neighbour_colors(key, graph, node_colors)
available_colours = [color for color in COLORS if color not in nc.values()]
print(f"Colours available for {key} are", available_colours)
for c in available_colours:
node_colors[key] = c
print(f"Applying colour {c} to node {key}\n")
if color_list := start(graph, node_colors):
if len(color_list) == len(graph):
return color_list
return node_colors
def main() -> None:
graph = generate_graph()
print(" Structure of Graph ".center(40, '='))
for node, neighbours in graph.items():
print(f"Node {node} has neighbours:", neighbours)
print('\n', ' Applying Colours '.center(40, '='), sep='')
colors = start(graph)
print('\n', " After colouring ".center(40, '='), sep='')
for node, color in colors.items():
print(f"Node {node} gets {color:<6} colour")
if __name__ == '__main__':
main()
Sam map coloring
import random
map = {"A": ['B','C','D'],
"B": ['A','C','D'],
"C": ['A',"B"],
"D": ['A',"B"] }
colors = ["red","blue","green"]
dictcolor ={
"A":"None" ,
"B":"None" ,
"C":"None" ,
"D":"None"
}
# first color allot random
dictcolor["A"] = random.choice(colors)
flag = 0
print(dictcolor)
for key, value in map.items():
if flag ==0:
flag = 1
continue
print(key)
print(value)
tempdict = {}
for i in value:
tempdict.update( {i: dictcolor[i]})
print("incolors taken:",set(tempdict.values()))
incolors2 = set(colors) - set(tempdict.values())
dictcolor[key] = random.choice(tuple(incolors2))
print("available:", incolors2)
print(dictcolor)
San map coloring
graph = {'A': ['B', 'C', 'D'],
'B': ['A', 'C', 'D'],
'C': ['A', 'B'],
'D': ['A', 'B']}
colors = ["red", "blue", "green"]
coloredNodes = {}
def avail(graph, colors, node, coloredNodes):
neighbourcolor = []
for neighbour in graph[node]:
if coloredNodes.get(neighbour, None):
neighbourcolor.append(coloredNodes[neighbour])
available = set(colors) - set(neighbourcolor)
return list(available)[0]
def main():
for node in graph:
coloredNodes[node] = avail(graph, colors, node, coloredNodes)
print(coloredNodes)
if __name__ == "__main__":
main()
Assn6 Prolog
Sam
/* Facts */
male(jack).
male(oliver).
male(ali).
male(james).
male(simon).
male(harry).
female(helen).
female(sophie).
female(jess).
female(lily).
parent(jack,jess).
parent(jack,lily).
parent(helen, jess).
parent(helen, lily).
parent(oliver,james).
parent(sophie, james).
parent(jess, simon).
parent(ali, simon).
parent(lily, harry).
parent(james, harry).
husband(jack, helen).
husband(oliver, sophie).
husband(ali, jess).
husband(james, lily).
/* Rules */
father(X,Y):- male(X), parent(X,Y).
mother(X,Y):- female(X),parent(X,Y).
son(X,Y,Z) :- male(X),father(Y,X),mother(Z,X).
daughter(X,Y,Z) :- female(X),father(Y,X),mother(Z,X).
brother(X,Y):- male(X), father(Z, Y), father(Z,X),X \= Y.
sister(X,Y):- female(X), father(F, Y), father(F,X),X \= Y.
cousin(X,Y) :- father(Z,X),brother(Z,W),father(W,Y).
grandfather(X,Y):- male(X), parent(X,Z), parent(Z,Y).
grandmother(X,Y):- female(X), parent(X,Z), parent(Z,Y).
uncle(X,Y):- parent(Z,Y), brother(Z,X).
aunt(X,Y):- female(X), parent(Z,Y), sister(Z,X), !.
chacha(X,Y) :- father(Z,Y) , brother(X,Z).
chachi(X,Y) :- female(X), father(Z,Y) , brother(Z,W) , husband(W,X).
mama(X,Y) :- mother(Z,Y) , brother(X,Z).
mami(X,Y) :- female(X), mother(Z,Y) , brother(W,Z) , husband(W,X).
stepbrother_commonmother(X,Y) :- male(X) , mother(M,X) , mother(M,Y) , father(L,X), father(N,Y) , L \= N.
stepbrother_commonfather(X,Y) :- male(X) , father(F,X) , father(F,Y) , mother(L,X), mother(N,Y) , L \= N.
stepsister_commonmother(X,Y) :- male(X) , mother(M,X) , mother(M,Y) , father(L,X), father(N,Y) , L \= N.
stepsister_commonfather(X,Y) :- male(X) , father(F,X) , father(F,Y) , mother(L,X), mother(N,Y) , L \= N.
ancestor(X,Y):- parent(X,Y).
ancestor(X,Y):- parent(X,Z),ancestor_of(Z,Y).