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.
@@ -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()