ai-practical-kit 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ai_practical_kit/__init__.py +22 -0
- ai_practical_kit/cli.py +157 -0
- ai_practical_kit/experiments/__init__.py +0 -0
- ai_practical_kit/experiments/alpha_beta_pruning.py +91 -0
- ai_practical_kit/experiments/astar_8_puzzle.py +116 -0
- ai_practical_kit/experiments/eight_queens.py +62 -0
- ai_practical_kit/experiments/local_beam_tsp.py +116 -0
- ai_practical_kit/experiments/maze_dfs.py +92 -0
- ai_practical_kit/experiments/robot_path_bfs.py +98 -0
- ai_practical_kit/experiments/rule_chatbot.py +47 -0
- ai_practical_kit/experiments/sudoku_solver.py +136 -0
- ai_practical_kit/experiments/tictactoe_minimax.py +148 -0
- ai_practical_kit/experiments/tower_of_hanoi_bfs.py +100 -0
- ai_practical_kit/experiments/vacuum_cleaner.py +70 -0
- ai_practical_kit/gui.py +173 -0
- ai_practical_kit-1.1.0.dist-info/METADATA +189 -0
- ai_practical_kit-1.1.0.dist-info/RECORD +21 -0
- ai_practical_kit-1.1.0.dist-info/WHEEL +5 -0
- ai_practical_kit-1.1.0.dist-info/entry_points.txt +5 -0
- ai_practical_kit-1.1.0.dist-info/licenses/LICENSE +21 -0
- ai_practical_kit-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Experiment 7: BFS for Robot Path Planning on a grid."""
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def solve_robot_bfs(maze):
|
|
7
|
+
"""Find a shortest path in a grid containing S, G, # and . characters."""
|
|
8
|
+
if not maze or not maze[0]:
|
|
9
|
+
raise ValueError("Maze cannot be empty.")
|
|
10
|
+
rows = len(maze)
|
|
11
|
+
cols = len(maze[0])
|
|
12
|
+
if any(len(row) != cols for row in maze):
|
|
13
|
+
raise ValueError("All maze rows must have the same length.")
|
|
14
|
+
|
|
15
|
+
start = None
|
|
16
|
+
goal = None
|
|
17
|
+
for r in range(rows):
|
|
18
|
+
for c in range(cols):
|
|
19
|
+
if maze[r][c] == "S":
|
|
20
|
+
if start is not None:
|
|
21
|
+
raise ValueError("Maze must contain exactly one S.")
|
|
22
|
+
start = (r, c)
|
|
23
|
+
elif maze[r][c] == "G":
|
|
24
|
+
if goal is not None:
|
|
25
|
+
raise ValueError("Maze must contain exactly one G.")
|
|
26
|
+
goal = (r, c)
|
|
27
|
+
if start is None or goal is None:
|
|
28
|
+
raise ValueError("Maze must contain one S (start) and one G (goal).")
|
|
29
|
+
|
|
30
|
+
queue = deque([start])
|
|
31
|
+
parent = {start: None}
|
|
32
|
+
distance = {start: 0}
|
|
33
|
+
# Same direction order as the practical: Down, Right, Up, Left.
|
|
34
|
+
directions = [(1, 0), (0, 1), (-1, 0), (0, -1)]
|
|
35
|
+
|
|
36
|
+
while queue:
|
|
37
|
+
current = queue.popleft()
|
|
38
|
+
if current == goal:
|
|
39
|
+
break
|
|
40
|
+
for dr, dc in directions:
|
|
41
|
+
nr = current[0] + dr
|
|
42
|
+
nc = current[1] + dc
|
|
43
|
+
next_cell = (nr, nc)
|
|
44
|
+
if nr < 0 or nr >= rows or nc < 0 or nc >= cols:
|
|
45
|
+
continue
|
|
46
|
+
if maze[nr][nc] == "#" or next_cell in parent:
|
|
47
|
+
continue
|
|
48
|
+
parent[next_cell] = current
|
|
49
|
+
distance[next_cell] = distance[current] + 1
|
|
50
|
+
queue.append(next_cell)
|
|
51
|
+
|
|
52
|
+
if goal not in parent:
|
|
53
|
+
return {"found": False, "path": [], "moves": None, "maze": list(maze)}
|
|
54
|
+
|
|
55
|
+
path = []
|
|
56
|
+
cell = goal
|
|
57
|
+
while cell is not None:
|
|
58
|
+
path.append(cell)
|
|
59
|
+
cell = parent[cell]
|
|
60
|
+
path.reverse()
|
|
61
|
+
|
|
62
|
+
marked = [list(row) for row in maze]
|
|
63
|
+
for r, c in path:
|
|
64
|
+
if marked[r][c] == ".":
|
|
65
|
+
marked[r][c] = "*"
|
|
66
|
+
return {"found": True, "path": path, "moves": distance[goal],
|
|
67
|
+
"maze": ["".join(row) for row in marked]}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def run_cli():
|
|
71
|
+
print("\nEXPERIMENT 7: BFS ROBOT PATH PLANNING")
|
|
72
|
+
try:
|
|
73
|
+
rows, cols = [int(x) for x in input("Enter rows and columns: ").split()]
|
|
74
|
+
if rows < 1 or cols < 1 or rows > 50 or cols > 50:
|
|
75
|
+
raise ValueError("Rows and columns must be between 1 and 50.")
|
|
76
|
+
print("Enter each maze row using S=start, G=goal, #=wall, .=free:")
|
|
77
|
+
maze = [input().strip() for _ in range(rows)]
|
|
78
|
+
if any(len(row) != cols for row in maze):
|
|
79
|
+
raise ValueError("Each row must contain exactly the given number of columns.")
|
|
80
|
+
result = solve_robot_bfs(maze)
|
|
81
|
+
except ValueError as error:
|
|
82
|
+
print("Input error:", error)
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
if not result["found"]:
|
|
86
|
+
print("No path exists from S to G.")
|
|
87
|
+
return
|
|
88
|
+
print("\nShortest path (1-based coordinates):")
|
|
89
|
+
print(" -> ".join("(" + str(r + 1) + "," + str(c + 1) + ")"
|
|
90
|
+
for r, c in result["path"]))
|
|
91
|
+
print("Shortest path length:", result["moves"], "moves")
|
|
92
|
+
print("\nFinal path grid:")
|
|
93
|
+
for row in result["maze"]:
|
|
94
|
+
print(" ".join(row))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
run_cli()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Experiment 10: Rule-based chatbot with regular-expression matching."""
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
RULES = [
|
|
7
|
+
(r"\b(hi|hello|hey)\b", ["Hello! How can I help you?", "Hi! Nice to meet you."]),
|
|
8
|
+
(r"\bhow are you\b", ["I am fine, thank you!", "I am doing great!"]),
|
|
9
|
+
(r"\b(admission|apply|application)\b", ["Please check the college admission portal."]),
|
|
10
|
+
(r"\b(fee|fees|tuition)\b", ["Please check the college fee portal."]),
|
|
11
|
+
(r"\b(library|books?)\b", ["Please check the college library."]),
|
|
12
|
+
(r"\b(hostel|accommodation)\b", ["Please contact the student affairs office."]),
|
|
13
|
+
(r"\b(thanks|thank you)\b", ["You are welcome!", "Happy to help!"]),
|
|
14
|
+
(r"\b(bye|goodbye|exit|quit)\b", ["Goodbye! Have a nice day!"]),
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def respond(text):
|
|
19
|
+
"""Return a rule-based response for a user message."""
|
|
20
|
+
text = text.strip()
|
|
21
|
+
|
|
22
|
+
# AND rule: both an admission topic and a fee topic must be present.
|
|
23
|
+
has_admission = re.search(r"\b(admission|apply|application)\b", text, re.I)
|
|
24
|
+
has_fees = re.search(r"\b(fee|fees|tuition)\b", text, re.I)
|
|
25
|
+
if has_admission and has_fees:
|
|
26
|
+
return "Check the admission portal AND the fee portal."
|
|
27
|
+
|
|
28
|
+
for pattern, replies in RULES:
|
|
29
|
+
if re.search(pattern, text, re.I):
|
|
30
|
+
return random.choice(replies)
|
|
31
|
+
|
|
32
|
+
return "Sorry, I do not understand your question."
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_cli():
|
|
36
|
+
print("\nEXPERIMENT 10: RULE-BASED CHATBOT")
|
|
37
|
+
print("Ask about greetings, admission, fees, library or hostel.")
|
|
38
|
+
print("Type bye to stop.")
|
|
39
|
+
while True:
|
|
40
|
+
user = input("You: ")
|
|
41
|
+
print("Bot:", respond(user))
|
|
42
|
+
if re.search(r"\b(bye|goodbye|exit|quit)\b", user, re.I):
|
|
43
|
+
break
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
run_cli()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Experiment 5: Sudoku solver using constraint propagation and backtracking."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def is_safe(grid, row, col, number):
|
|
5
|
+
"""Return True if number does not repeat in its row, column or 3x3 box."""
|
|
6
|
+
for i in range(9):
|
|
7
|
+
if grid[row][i] == number:
|
|
8
|
+
return False
|
|
9
|
+
if grid[i][col] == number:
|
|
10
|
+
return False
|
|
11
|
+
|
|
12
|
+
start_row = (row // 3) * 3
|
|
13
|
+
start_col = (col // 3) * 3
|
|
14
|
+
for r in range(start_row, start_row + 3):
|
|
15
|
+
for c in range(start_col, start_col + 3):
|
|
16
|
+
if grid[r][c] == number:
|
|
17
|
+
return False
|
|
18
|
+
return True
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def valid_initial_grid(grid):
|
|
22
|
+
if len(grid) != 9 or any(len(row) != 9 for row in grid):
|
|
23
|
+
return False
|
|
24
|
+
for row in range(9):
|
|
25
|
+
for col in range(9):
|
|
26
|
+
number = grid[row][col]
|
|
27
|
+
if not isinstance(number, int) or number < 0 or number > 9:
|
|
28
|
+
return False
|
|
29
|
+
if number != 0:
|
|
30
|
+
grid[row][col] = 0
|
|
31
|
+
safe = is_safe(grid, row, col, number)
|
|
32
|
+
grid[row][col] = number
|
|
33
|
+
if not safe:
|
|
34
|
+
return False
|
|
35
|
+
return True
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def propagate(grid):
|
|
39
|
+
"""Repeatedly fill empty cells having exactly one legal candidate."""
|
|
40
|
+
changed = True
|
|
41
|
+
while changed:
|
|
42
|
+
changed = False
|
|
43
|
+
for row in range(9):
|
|
44
|
+
for col in range(9):
|
|
45
|
+
if grid[row][col] == 0:
|
|
46
|
+
candidates = []
|
|
47
|
+
for number in range(1, 10):
|
|
48
|
+
if is_safe(grid, row, col, number):
|
|
49
|
+
candidates.append(number)
|
|
50
|
+
if len(candidates) == 0:
|
|
51
|
+
return False
|
|
52
|
+
if len(candidates) == 1:
|
|
53
|
+
grid[row][col] = candidates[0]
|
|
54
|
+
changed = True
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def solve_sudoku(puzzle):
|
|
59
|
+
"""Return a solved 9x9 grid, or None when no solution exists."""
|
|
60
|
+
if not valid_initial_grid(puzzle):
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
def solve(current_grid):
|
|
64
|
+
# Work on a copy so a failed guess cannot damage the previous state.
|
|
65
|
+
grid = [row[:] for row in current_grid]
|
|
66
|
+
|
|
67
|
+
if not propagate(grid):
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
empty_row = -1
|
|
71
|
+
empty_col = -1
|
|
72
|
+
for row in range(9):
|
|
73
|
+
for col in range(9):
|
|
74
|
+
if grid[row][col] == 0:
|
|
75
|
+
empty_row = row
|
|
76
|
+
empty_col = col
|
|
77
|
+
break
|
|
78
|
+
if empty_row != -1:
|
|
79
|
+
break
|
|
80
|
+
|
|
81
|
+
if empty_row == -1:
|
|
82
|
+
return grid
|
|
83
|
+
|
|
84
|
+
for number in range(1, 10):
|
|
85
|
+
if is_safe(grid, empty_row, empty_col, number):
|
|
86
|
+
next_grid = [row[:] for row in grid]
|
|
87
|
+
next_grid[empty_row][empty_col] = number
|
|
88
|
+
answer = solve(next_grid)
|
|
89
|
+
if answer is not None:
|
|
90
|
+
return answer
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
return solve(puzzle)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def print_grid(grid):
|
|
97
|
+
for row in range(9):
|
|
98
|
+
if row in (3, 6):
|
|
99
|
+
print("------+-------+------")
|
|
100
|
+
for col in range(9):
|
|
101
|
+
if col in (3, 6):
|
|
102
|
+
print("|", end=" ")
|
|
103
|
+
print(grid[row][col], end=" ")
|
|
104
|
+
print()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def run_cli():
|
|
108
|
+
print("\nEXPERIMENT 5: SUDOKU (CONSTRAINT PROPAGATION + BACKTRACKING)")
|
|
109
|
+
print("Enter 9 rows, using 0 for empty cells. Example: 530070000")
|
|
110
|
+
grid = []
|
|
111
|
+
try:
|
|
112
|
+
for _ in range(9):
|
|
113
|
+
line = input().strip()
|
|
114
|
+
if " " in line:
|
|
115
|
+
row = [int(x) for x in line.split()]
|
|
116
|
+
else:
|
|
117
|
+
row = [int(ch) for ch in line]
|
|
118
|
+
if len(row) != 9:
|
|
119
|
+
raise ValueError("Each row must contain 9 digits.")
|
|
120
|
+
grid.append(row)
|
|
121
|
+
except ValueError as error:
|
|
122
|
+
print("Input error:", error)
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
print("\nInitial Sudoku:")
|
|
126
|
+
print_grid(grid)
|
|
127
|
+
answer = solve_sudoku(grid)
|
|
128
|
+
if answer is None:
|
|
129
|
+
print("No solution exists or the starting grid is invalid.")
|
|
130
|
+
else:
|
|
131
|
+
print("\nSolved Sudoku:")
|
|
132
|
+
print_grid(answer)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
run_cli()
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Experiment 11: Tic-Tac-Toe game-playing agent using Minimax."""
|
|
2
|
+
|
|
3
|
+
WINNING_LINES = [
|
|
4
|
+
(0, 1, 2), (3, 4, 5), (6, 7, 8),
|
|
5
|
+
(0, 3, 6), (1, 4, 7), (2, 5, 8),
|
|
6
|
+
(0, 4, 8), (2, 4, 6),
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def check_winner(board):
|
|
11
|
+
for a, b, c in WINNING_LINES:
|
|
12
|
+
if board[a] != " " and board[a] == board[b] and board[b] == board[c]:
|
|
13
|
+
return board[a]
|
|
14
|
+
if " " in board:
|
|
15
|
+
return " "
|
|
16
|
+
return "D"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def minimax(board, is_max):
|
|
20
|
+
result = check_winner(board)
|
|
21
|
+
if result == "X":
|
|
22
|
+
return 1
|
|
23
|
+
if result == "O":
|
|
24
|
+
return -1
|
|
25
|
+
if result == "D":
|
|
26
|
+
return 0
|
|
27
|
+
|
|
28
|
+
if is_max:
|
|
29
|
+
best = -2
|
|
30
|
+
for i in range(9):
|
|
31
|
+
if board[i] == " ":
|
|
32
|
+
board[i] = "X"
|
|
33
|
+
score = minimax(board, False)
|
|
34
|
+
board[i] = " "
|
|
35
|
+
if score > best:
|
|
36
|
+
best = score
|
|
37
|
+
return best
|
|
38
|
+
|
|
39
|
+
best = 2
|
|
40
|
+
for i in range(9):
|
|
41
|
+
if board[i] == " ":
|
|
42
|
+
board[i] = "O"
|
|
43
|
+
score = minimax(board, True)
|
|
44
|
+
board[i] = " "
|
|
45
|
+
if score < best:
|
|
46
|
+
best = score
|
|
47
|
+
return best
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def find_best_move(board):
|
|
51
|
+
"""Return the zero-based best move for X, or None if game is over."""
|
|
52
|
+
if len(board) != 9:
|
|
53
|
+
raise ValueError("Board must contain 9 cells.")
|
|
54
|
+
if check_winner(board) != " ":
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
best_score = -2
|
|
58
|
+
move = None
|
|
59
|
+
for i in range(9):
|
|
60
|
+
if board[i] == " ":
|
|
61
|
+
board[i] = "X"
|
|
62
|
+
score = minimax(board, False)
|
|
63
|
+
board[i] = " "
|
|
64
|
+
if score > best_score:
|
|
65
|
+
best_score = score
|
|
66
|
+
move = i
|
|
67
|
+
return move
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def display_board(board):
|
|
71
|
+
for row in range(3):
|
|
72
|
+
cells = []
|
|
73
|
+
for col in range(3):
|
|
74
|
+
index = row * 3 + col
|
|
75
|
+
if board[index] == " ":
|
|
76
|
+
cells.append(str(index + 1))
|
|
77
|
+
else:
|
|
78
|
+
cells.append(board[index])
|
|
79
|
+
print(" | ".join(cells))
|
|
80
|
+
if row < 2:
|
|
81
|
+
print("---------")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def run_demo():
|
|
85
|
+
board = ["X", "X", " ", "O", "O", " ", " ", " ", " "]
|
|
86
|
+
print("\nInitial board (X = AI, O = opponent):")
|
|
87
|
+
display_board(board)
|
|
88
|
+
move = find_best_move(board)
|
|
89
|
+
if move is None:
|
|
90
|
+
print("No move available; the game has ended.")
|
|
91
|
+
return
|
|
92
|
+
print("\nBest move for AI:", move + 1)
|
|
93
|
+
board[move] = "X"
|
|
94
|
+
print("\nBoard after AI move:")
|
|
95
|
+
display_board(board)
|
|
96
|
+
print("Minimax score:", minimax(board, True))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def play_game():
|
|
100
|
+
board = [" "] * 9
|
|
101
|
+
print("\nTic-Tac-Toe: AI = X, You = O")
|
|
102
|
+
while True:
|
|
103
|
+
move = find_best_move(board)
|
|
104
|
+
if move is not None:
|
|
105
|
+
board[move] = "X"
|
|
106
|
+
print("\nAI chooses:", move + 1)
|
|
107
|
+
display_board(board)
|
|
108
|
+
result = check_winner(board)
|
|
109
|
+
if result == "X":
|
|
110
|
+
print("AI wins!")
|
|
111
|
+
break
|
|
112
|
+
if result == "D":
|
|
113
|
+
print("Draw!")
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
while True:
|
|
117
|
+
try:
|
|
118
|
+
position = int(input("Enter your move (1-9): "))
|
|
119
|
+
except ValueError:
|
|
120
|
+
print("Enter a number from 1 to 9.")
|
|
121
|
+
continue
|
|
122
|
+
if position >= 1 and position <= 9 and board[position - 1] == " ":
|
|
123
|
+
break
|
|
124
|
+
print("Invalid or occupied position. Try again.")
|
|
125
|
+
board[position - 1] = "O"
|
|
126
|
+
display_board(board)
|
|
127
|
+
result = check_winner(board)
|
|
128
|
+
if result == "O":
|
|
129
|
+
print("You win!")
|
|
130
|
+
break
|
|
131
|
+
if result == "D":
|
|
132
|
+
print("Draw!")
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def run_cli():
|
|
137
|
+
print("\nEXPERIMENT 11: TIC-TAC-TOE USING MINIMAX")
|
|
138
|
+
print("1. Demonstrate the best move (no human input)")
|
|
139
|
+
print("2. Play against the AI")
|
|
140
|
+
choice = input("Choose 1 or 2 [1]: ").strip()
|
|
141
|
+
if choice == "2":
|
|
142
|
+
play_game()
|
|
143
|
+
else:
|
|
144
|
+
run_demo()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
run_cli()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Experiment 4: Tower of Hanoi solved by BFS over legal states."""
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
|
|
5
|
+
PEG_NAMES = "ABC"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def solve_hanoi_bfs(number_of_disks):
|
|
9
|
+
"""Return shortest sequence of moves and the number of explored states.
|
|
10
|
+
|
|
11
|
+
State is a tuple where state[d] is the peg index (0=A, 1=B, 2=C)
|
|
12
|
+
holding disk d. Disk 0 is the smallest disk.
|
|
13
|
+
"""
|
|
14
|
+
if not isinstance(number_of_disks, int) or number_of_disks < 1 or number_of_disks > 8:
|
|
15
|
+
raise ValueError("Number of disks must be between 1 and 8.")
|
|
16
|
+
|
|
17
|
+
start = tuple(0 for _ in range(number_of_disks))
|
|
18
|
+
goal = tuple(2 for _ in range(number_of_disks))
|
|
19
|
+
queue = deque([start])
|
|
20
|
+
parent = {start: None}
|
|
21
|
+
move_to_state = {}
|
|
22
|
+
|
|
23
|
+
while queue:
|
|
24
|
+
current = queue.popleft()
|
|
25
|
+
if current == goal:
|
|
26
|
+
break
|
|
27
|
+
|
|
28
|
+
# Find the top (smallest numbered) disk on each peg.
|
|
29
|
+
top = [-1, -1, -1]
|
|
30
|
+
for disk in range(number_of_disks):
|
|
31
|
+
peg = current[disk]
|
|
32
|
+
if top[peg] == -1:
|
|
33
|
+
top[peg] = disk
|
|
34
|
+
|
|
35
|
+
for source in range(3):
|
|
36
|
+
disk = top[source]
|
|
37
|
+
if disk == -1:
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
for destination in range(3):
|
|
41
|
+
if source == destination:
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
destination_top = top[destination]
|
|
45
|
+
# A larger disk cannot be placed on a smaller one.
|
|
46
|
+
if destination_top != -1 and disk > destination_top:
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
next_state = list(current)
|
|
50
|
+
next_state[disk] = destination
|
|
51
|
+
next_state = tuple(next_state)
|
|
52
|
+
|
|
53
|
+
if next_state not in parent:
|
|
54
|
+
parent[next_state] = current
|
|
55
|
+
move_to_state[next_state] = (
|
|
56
|
+
disk + 1, PEG_NAMES[source], PEG_NAMES[destination]
|
|
57
|
+
)
|
|
58
|
+
queue.append(next_state)
|
|
59
|
+
|
|
60
|
+
if goal not in parent:
|
|
61
|
+
return {"found": False, "moves": [], "states_explored": len(parent)}
|
|
62
|
+
|
|
63
|
+
states = []
|
|
64
|
+
moves = []
|
|
65
|
+
state = goal
|
|
66
|
+
while state is not None:
|
|
67
|
+
states.append(state)
|
|
68
|
+
if state in move_to_state:
|
|
69
|
+
moves.append(move_to_state[state])
|
|
70
|
+
state = parent[state]
|
|
71
|
+
states.reverse()
|
|
72
|
+
moves.reverse()
|
|
73
|
+
|
|
74
|
+
return {"found": True, "states": states, "moves": moves,
|
|
75
|
+
"number_of_moves": len(moves), "states_explored": len(parent)}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def run_cli():
|
|
79
|
+
print("\nEXPERIMENT 4: TOWER OF HANOI USING BFS")
|
|
80
|
+
try:
|
|
81
|
+
n = int(input("Enter number of disks (1-8): "))
|
|
82
|
+
result = solve_hanoi_bfs(n)
|
|
83
|
+
except ValueError as error:
|
|
84
|
+
print("Input error:", error)
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
if not result["found"]:
|
|
88
|
+
print("No solution found.")
|
|
89
|
+
return
|
|
90
|
+
if not result["moves"]:
|
|
91
|
+
print("No moves needed.")
|
|
92
|
+
for step, move in enumerate(result["moves"], 1):
|
|
93
|
+
disk, source, destination = move
|
|
94
|
+
print("Step", step, ": Move disk", disk, "from", source, "to", destination)
|
|
95
|
+
print("Total moves:", result["number_of_moves"])
|
|
96
|
+
print("States explored:", result["states_explored"])
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
run_cli()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Experiment 1: Simple Reflex Agent for the Vacuum Cleaner World.
|
|
2
|
+
|
|
3
|
+
Run standalone with: python -m ai_practical_kit.experiments.vacuum_cleaner
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def solve_vacuum(room_a, room_b, position):
|
|
8
|
+
"""Return the action sequence and final state for a two-room vacuum agent.
|
|
9
|
+
|
|
10
|
+
room_a / room_b accept 'D' or 'C' (also 1=dirty, 0=clean).
|
|
11
|
+
position accepts 'A' or 'B'.
|
|
12
|
+
"""
|
|
13
|
+
def clean_status(value):
|
|
14
|
+
value = str(value).strip().upper()
|
|
15
|
+
if value in ("D", "1"):
|
|
16
|
+
return "D"
|
|
17
|
+
if value in ("C", "0"):
|
|
18
|
+
return "C"
|
|
19
|
+
raise ValueError("Room status must be D/C or 1/0.")
|
|
20
|
+
|
|
21
|
+
room_a = clean_status(room_a)
|
|
22
|
+
room_b = clean_status(room_b)
|
|
23
|
+
position = str(position).strip().upper()
|
|
24
|
+
if position not in ("A", "B"):
|
|
25
|
+
raise ValueError("Position must be A or B.")
|
|
26
|
+
|
|
27
|
+
actions = []
|
|
28
|
+
while room_a == "D" or room_b == "D":
|
|
29
|
+
if position == "A":
|
|
30
|
+
if room_a == "D":
|
|
31
|
+
actions.append("SUCK Room A")
|
|
32
|
+
room_a = "C"
|
|
33
|
+
else:
|
|
34
|
+
actions.append("MOVE RIGHT to Room B")
|
|
35
|
+
position = "B"
|
|
36
|
+
else:
|
|
37
|
+
if room_b == "D":
|
|
38
|
+
actions.append("SUCK Room B")
|
|
39
|
+
room_b = "C"
|
|
40
|
+
else:
|
|
41
|
+
actions.append("MOVE LEFT to Room A")
|
|
42
|
+
position = "A"
|
|
43
|
+
|
|
44
|
+
return {"actions": actions, "room_a": room_a, "room_b": room_b,
|
|
45
|
+
"position": position}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def run_cli():
|
|
49
|
+
print("\nEXPERIMENT 1: VACUUM CLEANER AGENT")
|
|
50
|
+
print("Enter 1 for Dirty or 0 for Clean (D/C also accepted).")
|
|
51
|
+
a = input("Room A status: ")
|
|
52
|
+
b = input("Room B status: ")
|
|
53
|
+
pos = input("Agent starts in room A or B: ")
|
|
54
|
+
try:
|
|
55
|
+
result = solve_vacuum(a, b, pos)
|
|
56
|
+
except ValueError as error:
|
|
57
|
+
print("Input error:", error)
|
|
58
|
+
return
|
|
59
|
+
print("\nActions:")
|
|
60
|
+
if not result["actions"]:
|
|
61
|
+
print("No action needed.")
|
|
62
|
+
else:
|
|
63
|
+
for number, action in enumerate(result["actions"], 1):
|
|
64
|
+
print(str(number) + ". " + action)
|
|
65
|
+
print("Final state: A =", result["room_a"], ", B =", result["room_b"])
|
|
66
|
+
print("Both rooms are clean.")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
run_cli()
|