co2114 2026.0.3__tar.gz → 2026.1.1__tar.gz

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.
Files changed (37) hide show
  1. {co2114-2026.0.3 → co2114-2026.1.1}/PKG-INFO +1 -1
  2. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/agent/environment.py +13 -0
  3. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/constraints/csp/__init__.py +4 -0
  4. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/search/graph.py +25 -7
  5. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/search/maze.py +166 -38
  6. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/search/things.py +2 -2
  7. co2114-2026.1.1/co2114/search/util.py +49 -0
  8. {co2114-2026.0.3 → co2114-2026.1.1}/co2114.egg-info/PKG-INFO +1 -1
  9. {co2114-2026.0.3 → co2114-2026.1.1}/setup.py +1 -1
  10. co2114-2026.0.3/co2114/search/util.py +0 -27
  11. {co2114-2026.0.3 → co2114-2026.1.1}/LICENSE +0 -0
  12. {co2114-2026.0.3 → co2114-2026.1.1}/README.md +0 -0
  13. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/__init__.py +0 -0
  14. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/agent/__init__.py +0 -0
  15. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/agent/things.py +0 -0
  16. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/constraints/__init__.py +0 -0
  17. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/constraints/csp/util.py +0 -0
  18. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/constraints/magic.py +0 -0
  19. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/constraints/sudoku.py +0 -0
  20. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/engine.py +0 -0
  21. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/optimisation/__init__.py +0 -0
  22. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/optimisation/minimax.py +0 -0
  23. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/optimisation/planning.py +0 -0
  24. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/optimisation/things.py +0 -0
  25. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/reasoning/__init__.py +0 -0
  26. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/reasoning/cluedo.py +0 -0
  27. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/reasoning/inference.py +0 -0
  28. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/reasoning/logic.py +0 -0
  29. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/search/__init__.py +0 -0
  30. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/util/__init__.py +0 -0
  31. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/util/colours.py +0 -0
  32. {co2114-2026.0.3 → co2114-2026.1.1}/co2114/util/fonts.py +0 -0
  33. {co2114-2026.0.3 → co2114-2026.1.1}/co2114.egg-info/SOURCES.txt +0 -0
  34. {co2114-2026.0.3 → co2114-2026.1.1}/co2114.egg-info/dependency_links.txt +0 -0
  35. {co2114-2026.0.3 → co2114-2026.1.1}/co2114.egg-info/requires.txt +0 -0
  36. {co2114-2026.0.3 → co2114-2026.1.1}/co2114.egg-info/top_level.txt +0 -0
  37. {co2114-2026.0.3 → co2114-2026.1.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: co2114
3
- Version: 2026.0.3
3
+ Version: 2026.1.1
4
4
  Summary: codebase for co2114
5
5
  Author: wil ward
6
6
  Requires-Python: >=3.12
@@ -26,10 +26,21 @@ class BaseEnvironment:
26
26
  """ Base Environment
27
27
  Adapted from AIMI code
28
28
  """
29
+ def __init__(self):
30
+ """ Base constructor """
31
+ self.__counter: int = 0
32
+
29
33
  def __repr__(self) -> str:
30
34
  """ Base string representation """
31
35
  return self.__class__.__name__
32
36
 
37
+ @property
38
+ def counter(self) -> int:
39
+ """ Tracker for number of iterations completed.
40
+ :return counter: Number of iterations in runtime simulation
41
+ """
42
+ return self.__counter
43
+
33
44
  @property
34
45
  def is_done(self) -> bool:
35
46
  """ Property indicating completion of simulation, needs overriding
@@ -54,6 +65,7 @@ class BaseEnvironment:
54
65
  print(f"{self}: Running for {steps} iterations.")
55
66
 
56
67
  for i in range(steps):
68
+ self.__counter += 1
57
69
  if self.is_done: # print terination message and exit
58
70
  print(f"{self}: Simulation complete after {i} of {steps} iterations.")
59
71
  return
@@ -67,6 +79,7 @@ class Environment(BaseEnvironment):
67
79
  """
68
80
  def __init__(self):
69
81
  """ Initialises set of things and agents """
82
+ super().__init__()
70
83
  self.things = set() # all things in environment
71
84
  self.agents = set() # all agents in environment
72
85
 
@@ -31,6 +31,10 @@ class ConstraintSatisfactionProblem:
31
31
 
32
32
  @property
33
33
  def variables(self):
34
+ """ List of variables in the CSP.
35
+
36
+ :return: List of variables.
37
+ """
34
38
  return self.__variables
35
39
 
36
40
  @property
@@ -18,7 +18,7 @@ Label = str
18
18
  # Weight = Numeric
19
19
 
20
20
  # Example graph dictionary representing UK cities and distances between them
21
- UK_CITIES_GRAPH:dict[str, Iterable[str | tuple[str,str] | int]] = {
21
+ UK_CITIES_GRAPH:dict[str, Iterable[str | tuple[str,str] | tuple[Numeric, Numeric] | int]] = {
22
22
  "nodes": ["Edinburgh", "Glasgow", "Manchester", "Liverpool",
23
23
  "Newcastle", "York", "Sheffield", "Leicester",
24
24
  "London", "Bath", "Bristol", "Exeter", "Cardiff", "Birmingham"],
@@ -32,8 +32,13 @@ UK_CITIES_GRAPH:dict[str, Iterable[str | tuple[str,str] | int]] = {
32
32
  ("Birmingham", "Cardiff"), ("Leicester", "London"),
33
33
  ("Birmingham", "Leicester"),
34
34
  ("Birmingham", "London"), ("Birmingham", "Bristol"),
35
- ("London", "Bristol"), ("Cardiff", "Bristol")],
36
- "weights": [2,4,1,1,4,1,1,2,1,3,3,2,1,2,5,1,2,2,3,2,1]
35
+ ("London", "Bristol"), ("Cardiff", "Bristol"), ("Bristol", "Bath"), ("Exeter", "Bristol"), ("Exeter", "London")],
36
+ "weights": [2,4,1,1,4,1,1,2,1,3,3,2,1,2,5,1,2,2,3,2,1,1,2,5],
37
+ "locations": [(55.9533, -3.1883), (55.8617, -4.2583), (53.4808, -2.2426),
38
+ (53.4084, -2.9916), (54.9783, -1.6178), (53.9614, -1.0739),
39
+ (53.3811, -1.4701), (52.6369, -1.1398), (51.5072, -0.1276),
40
+ (51.3781, -2.3597), (51.4545, -2.5879), (50.7260, -3.5275),
41
+ (51.4837, -3.1681), (52.4823, -1.8900)]
37
42
  }
38
43
 
39
44
 
@@ -188,18 +193,25 @@ class GraphEnvironment(Environment):
188
193
  super().__init__(*args, **kwargs) # initialize base Environment
189
194
  self.graph = graph if isinstance(graph, Graph) else Graph()
190
195
 
191
-
192
- def add_node(self, node:Node) -> None:
196
+
197
+ def add_node(self, node:Node, location:None | tuple = None) -> None:
193
198
  """ Add a node to the graph environment.
194
199
 
195
200
  :param node: Node to be added to the graph.
201
+ :param location: Optional location information of node, default None
196
202
  """
197
203
  self.graph.add_node(node)
204
+ if location is not None:
205
+ node.location = location
198
206
 
199
207
 
200
208
  @override
201
209
  def percept(self, agent:Agent) -> list[tuple[Node, Numeric]]:
202
- """ Return the percepts for the agent based on its location in the graph."""
210
+ """ Return the percepts for the agent based on its location in the graph.
211
+
212
+ :param agent: Agent for which to get percepts.
213
+ :return: List of tuples (neighbouring node, edge weight). Weight is 1 if unweighted
214
+ """
203
215
  node:Node = agent.location # type: ignore
204
216
  if len(node.weights) == 0:
205
217
  return [(n, 1) for n in node.neighbours]
@@ -246,7 +258,7 @@ class GraphEnvironment(Environment):
246
258
  super().add_agent(agent)
247
259
 
248
260
 
249
- def show(self, *args, **kwargs):
261
+ def show(self, *args, **kwargs) -> Figure:
250
262
  """ Show a plot of the graph environment. """
251
263
  with warnings.catch_warnings():
252
264
  warnings.simplefilter("ignore")
@@ -286,6 +298,12 @@ class GraphEnvironment(Environment):
286
298
  a, b = edge # type: ignore
287
299
  nodes[a].add_neighbour(nodes[b], weight) # add edge with weight
288
300
 
301
+ if 'locations' in graph_dict:
302
+ locations = graph_dict['locations']
303
+ else:
304
+ locations = [(0,0)]*len(nodes)
305
+ for key, location in zip(nodes, locations):
306
+ nodes[key].location = location # type: ignore
289
307
 
290
308
  # create environment and add nodes
291
309
  environment = cls()
@@ -2,6 +2,8 @@ from collections.abc import Iterable
2
2
  import warnings
3
3
  from matplotlib import pyplot as plt
4
4
  from collections import deque
5
+ from typing import override
6
+ from matplotlib.figure import Figure # for type hinting
5
7
 
6
8
  with warnings.catch_warnings():
7
9
  warnings.simplefilter("ignore")
@@ -34,6 +36,22 @@ PRESET_MAZES = {
34
36
  "x x xx xx ",
35
37
  "xxx x xxxx ",
36
38
  " xx "],
39
+ 3: ["xxx xxxxxxxxx",
40
+ "x xxxxxxxxxxxxxxxxxxx x x",
41
+ "x xxxx x x x x",
42
+ "x xxxxxxxxxxxxxxxxxxx x x x x",
43
+ "x x x x x",
44
+ "xxxxxxxxxxxxxxxxxxxxx x x x x",
45
+ "x xx x x x x",
46
+ "x x xx xxx xx xxxxxxxxx x x x",
47
+ "x x x xxox x x x",
48
+ "x x xx xxxxxxxxxxxxxxxx x x x",
49
+ "xxx xx xxxx x x x",
50
+ "xxx xxxxxxxxxxxxxx xx x x x x",
51
+ "xxx xx x x x x",
52
+ "xxxxxx xxxxxxxx xxxxxxx x x x",
53
+ "xxxxxx xxxx x x",
54
+ " xxxxxxxxxxxxxxxxxxxxxx"],
37
55
  4: ["xxx xxxxxxxxx",
38
56
  "x xxxxxxxxxxxxxxxxxxx x x",
39
57
  "x xxxx x x x x",
@@ -56,27 +74,39 @@ PRESET_STARTS = {
56
74
  0: (3, 0),
57
75
  1: (3, 0),
58
76
  2: (7, 0),
77
+ 3: (1, 27),
59
78
  4: (15, 0)
60
79
  }
61
80
 
81
+
62
82
  class MazeTile(Node):
63
- def __init__(self, passable:bool):
83
+ def __init__(self, passable:bool) -> None:
84
+ """ MazeTile - A tile in a maze, either passable or not.
85
+
86
+ :param passable: Whether the tile is passable.
87
+ """
64
88
  super().__init__()
65
- self.is_passable = passable
66
- self.is_goal = False
67
- self.visited = False
89
+ self.is_passable:bool = passable
90
+ self.is_goal:bool = False
91
+ self.visited:bool = False
68
92
 
69
- def __repr__(self):
93
+ def __repr__(self) -> str:
94
+ """ Str representation of the MazeTile. """
70
95
  repr = "⬜" if self.is_passable else "⬛"
71
96
  if hasattr(self, "location"):
72
97
  repr += str(self.location)
73
98
  return repr
74
99
 
75
100
 
101
+
76
102
  class Maze(Graph):
77
- def __init__(self, template=None, size=None):
103
+ def __init__(self, template:list[str]|None=None) -> None:
104
+ """ Maze class: a grid-based maze structure.
105
+
106
+ :param template: Optional template to generate the maze from.
107
+ """
78
108
  super().__init__()
79
- if template is not None:
109
+ if template is not None: # generate from template
80
110
  if not self._is_valid_template(template):
81
111
  raise ValueError(f"{self}: template provided is not valid")
82
112
  self.template = template
@@ -84,11 +114,21 @@ class Maze(Graph):
84
114
  self.size = self.width, self.height
85
115
  self._generate_from_template()
86
116
 
87
- def __repr__(self):
117
+ @override
118
+ def __repr__(self) -> str:
119
+ """ String representation of the Maze. """
88
120
  return "ꡙ‍"
89
121
 
90
- def _is_valid_template(self, template):
122
+ def _is_valid_template(self, template:list[str]) -> bool:
123
+ """ Checks if the provided template is valid.
124
+
125
+ A valid template is a list of strings, all of the same length, containing only " ", "o", or "x".
126
+
127
+ :param template: The template to validate.
128
+ :return valid: Whether the template is valid.
129
+ """
91
130
  def valid(row, size):
131
+ """ Internal row validator. """
92
132
  return False if len(row) != size else not any(
93
133
  c not in (' ','o','x') for c in row)
94
134
 
@@ -98,7 +138,11 @@ class Maze(Graph):
98
138
  return all(valid(row,width) for row in template)
99
139
  return False
100
140
 
101
- def _generate_from_template(self):
141
+ def _generate_from_template(self) -> None:
142
+ """ Generates the maze from the provided template.
143
+
144
+ Uses internal self.template to build the maze grid.
145
+ """
102
146
  self.grid = []
103
147
  w,h = self.width, self.height
104
148
  print(f"{self}: generating {w}x{h} maze from template")
@@ -117,70 +161,127 @@ class Maze(Graph):
117
161
  self.grid.append(grid_row)
118
162
  self.add_node(self.grid[0][0]) # cascade adding nodes
119
163
 
120
-
121
- def plot_nodes(self, init=None):
164
+ @override
165
+ def plot_nodes(self) -> Figure:
166
+ """ Plots the maze nodes using matplotlib."""
122
167
  labels = {self.grid[i][j]: f"({i},{j})"
123
168
  for i in range(self.width)
124
169
  for j in range(self.height)}
125
170
  condition = lambda node: node.is_passable
126
- return super().plot_nodes(init=init, labels=labels, condition=condition)
171
+ return super().plot_nodes(init=None, labels=labels, condition=condition)
172
+
127
173
 
128
174
 
129
175
  class MazeEnvironment(GraphEnvironment):
130
- def __init__(self, maze, *args, **kwargs):
176
+ """ MazeEnvironment - An environment for maze navigation.
177
+
178
+ A type of GraphEnvironment specifically for Maze graphs.
179
+ """
180
+ def __init__(self, maze:Maze, *args, **kwargs) -> None:
181
+ """ Initialises the MazeEnvironment.
182
+
183
+ :param maze: The Maze to be used as the environment.
184
+ :param args: Additional positional arguments for GraphEnvironment.
185
+ :param kwargs: Additional keyword arguments for GraphEnvironment.
186
+ """
131
187
  if not isinstance(maze, Maze):
132
188
  raise TypeError(f"{self}: maze must be valid Maze")
133
189
  super().__init__(maze, *args, **kwargs)
134
190
  self.size = self.width, self.height = maze.size
135
191
  self.success = False # maze is solved
136
- def __repr__(self):
192
+
193
+ @override
194
+ def __repr__(self) -> str:
195
+ """ String representation of the MazeEnvironment. """
137
196
  return self.maze.__repr__()
138
197
 
139
198
  @property
140
- def maze(self):
199
+ def maze(self) -> Maze:
200
+ """ Returns the maze graph of the environment. """
141
201
  return self.graph
142
202
 
143
203
  @property
144
- def grid(self):
204
+ def grid(self) -> list[list[MazeTile]]:
205
+ """ Returns the maze grid as a nested list of MazeTiles. """
145
206
  return self.maze.grid
146
207
 
147
208
  @property
148
- def is_done(self):
209
+ def is_done(self) -> bool:
210
+ """ Returns whether the maze has been solved. """
149
211
  if len(self.agents) == 0: return True
150
212
  return self.success
151
213
 
152
214
  @property
153
- def goal(self):
215
+ def goal(self) -> MazeTile|None:
216
+ """ Returns the goal tile of the maze, if there is one.
217
+ """
154
218
  goal = {node for node in self.maze.nodes if node.is_goal}
155
219
  if len(goal) == 1:
156
220
  goal = next(iter(goal))
221
+ else:
222
+ goal = None
157
223
  return goal
158
224
 
159
- def show_graph(self, *args, **kwargs):
225
+
226
+ def show_graph(self, *args, **kwargs) -> Figure:
227
+ """ Wrapper for GraphEnvironment.show() """
160
228
  return self.show(*args, **kwargs)
161
229
 
162
- def percept(self, agent):
230
+
231
+ @override
232
+ def percept(self, agent:Agent) -> set[MazeTile]:
233
+ """ Returns the percepts for the agent: neighbouring nodes and their weights.
234
+
235
+ :param agent: The agent to get percepts for.
236
+ :return percepts: List of tuples of (neighbouring maze tile, weight).
237
+ """
163
238
  node = agent.location
164
239
  return node.neighbours
165
240
 
166
- def execute_action(self, agent, action):
167
- """ """
168
- command, node = action
241
+ @override
242
+ def execute_action(self, agent:Agent, action:tuple[str, MazeTile]) -> None:
243
+ """ Executes the agent's action in the environment.
244
+
245
+ :param agent: The agent performing the action.
246
+ :param action: The action to be performed, as a tuple of (command, MazeTile). Command should be "move".
247
+ """
248
+ command, node = action # e.g. ("move", node)
169
249
  if command == "move":
170
250
  if node in self.maze:
171
251
  self.success = agent.move_to(node)
172
252
 
173
- def run(self, track_agent=False, *args, **kwargs):
253
+ @override
254
+ def run(self, *args, **kwargs) -> None:
255
+ """ Run the environment for a large number of steps by default.
256
+
257
+ Non-graphical default.
258
+ """
174
259
  super().run(10000, *args, **kwargs)
175
260
 
176
261
  @classmethod
177
- def from_template(MazeEnvironment, template):
262
+ def from_template(MazeEnvironment, template:list[str]) -> "MazeEnvironment":
263
+ """ Creates a MazeEnvironment from a template.
264
+
265
+ Returns instance of MazeEnvironment built from the provided template.
266
+ :param template: The template to build the maze from.
267
+ :return environment: The MazeEnvironment instance.
268
+ """
178
269
  maze = Maze(template)
179
270
  return MazeEnvironment(maze)
180
271
 
181
272
 
182
273
  class GraphicMaze(MazeEnvironment):
183
- def run(self, graphical=True, lps=2, *args, **kwargs):
274
+ """ Graphical Maze Environment: A MazeEnvironment with graphical rendering capabilities. Uses MazeApp to render.
275
+ """
276
+ @override
277
+ def run(self, graphical=True, lps=2, *args, **kwargs) -> None:
278
+ """ Run the MazeEnvironment in graphical or non-graphical mode.
279
+
280
+ :param graphical: Whether to run in graphical mode.
281
+ :param lps: The number of logic updates per second in graphical mode.
282
+ :param args: Additional positional arguments for Engine.
283
+ :param kwargs: Additional keyword arguments for Engine.
284
+ """
184
285
  if graphical:
185
286
  MazeApp(self, *args, name=f"{self}", lps=lps, **kwargs).run()
186
287
  else:
@@ -189,8 +290,18 @@ class GraphicMaze(MazeEnvironment):
189
290
 
190
291
  class MazeApp(App):
191
292
  """ MazeApp
192
- Graphical version of Maze """
193
- def __init__(self, environment:MazeEnvironment=None, track_agent=False,**kwargs):
293
+ Graphical version of Maze
294
+ """
295
+ def __init__(self,
296
+ environment:MazeEnvironment|None = None,
297
+ track_agent = False,
298
+ **kwargs) -> None:
299
+ """ Construct MazeApp
300
+
301
+ :param environment: The MazeEnvironment to be rendered. If None, a default maze is created.
302
+ :param track_agent: Whether to visually track the agent's path, default False
303
+ :param kwargs: Additional keyword arguments for App.
304
+ """
194
305
  if environment is None:
195
306
  print(f"{self}: building new MazeEnvironment from template")
196
307
  environment = MazeEnvironment.from_template(PRESET_MAZES[0])
@@ -208,7 +319,8 @@ class MazeApp(App):
208
319
  self._flag = True
209
320
  self.track = track_agent
210
321
 
211
- def _fit_to_environment(self):
322
+
323
+ def _fit_to_environment(self) -> None:
212
324
  """ Fit width and height ratios to match environment """
213
325
  _flag = self.environment.width > self.environment.height
214
326
  if _flag:
@@ -225,8 +337,9 @@ class MazeApp(App):
225
337
  self.height, self.width = (a,b) if _flag else (b,a)
226
338
  self.size = self.width, self.height
227
339
 
228
-
229
- def update(self):
340
+ @override
341
+ def update(self) -> None:
342
+ """ Update MazeApp by iterating underlying simulation environment """
230
343
  if self.counter < 0:
231
344
  self.counter += 1
232
345
  return
@@ -237,12 +350,15 @@ class MazeApp(App):
237
350
  print(f"{self.environment}: Maze complete after {self.counter} iterations.")
238
351
  self._flag = False
239
352
 
240
- def render(self):
353
+ @override
354
+ def render(self) -> None:
355
+ """ Render frame of MazeApp """
241
356
  # self.screen.fill(self.environment.color)
242
357
  self.render_grid()
243
358
  self.render_things()
244
359
 
245
- def render_grid(self):
360
+ def render_grid(self) -> None:
361
+ """ Render maze grid showing passable and non-passable tiles in light and dark red respectively. If the agent is tracked, visited tiles are shown in light green. """
246
362
  nx,ny = self.environment.width, self.environment.height
247
363
  self.tile_size = self.width / nx
248
364
  tile_origin = (0,0)
@@ -274,7 +390,16 @@ class MazeApp(App):
274
390
  tiles.append(row)
275
391
  self.tiles = tiles
276
392
 
277
- def label_tile(self, node, string, color=COLOR_WHITE):
393
+ def label_tile(self,
394
+ node:MazeTile,
395
+ string:str,
396
+ color:tuple[int,int,int] = COLOR_WHITE) -> None:
397
+ """ Labels a tile with a string at the center of the tile.
398
+
399
+ :param node: The MazeTile to label.
400
+ :param string: The string to label the tile with.
401
+ :param color: The color of the string.
402
+ """
278
403
  i,j = node.location
279
404
  renderLoc = self.tiles[i][j].center
280
405
  thingRender = self.thing_font.render(string, True, color)
@@ -282,7 +407,11 @@ class MazeApp(App):
282
407
  thingRect.center = renderLoc
283
408
  self.screen.blit(thingRender, thingRect)
284
409
 
285
- def render_things(self):
410
+ def render_things(self) -> None:
411
+ """ Renders things to the maze environment.
412
+
413
+ Marks the goal tile with a crown emoji, agents with their string representation, and if tracking is enabled, marks frontier nodes with a question mark.
414
+ """
286
415
  locations = {}
287
416
  for node in self.environment.maze:
288
417
  if node.is_goal:
@@ -295,5 +424,4 @@ class MazeApp(App):
295
424
  if self.track:
296
425
  if hasattr(agent, "frontier"):
297
426
  for node in agent.frontier:
298
- self.label_tile(node, "?", COLOR_WHITE)
299
-
427
+ self.label_tile(node, "?", COLOR_WHITE)
@@ -14,7 +14,7 @@ class MazeRunner(Agent):
14
14
  class GoalBasedAgent(ModelBasedAgent):
15
15
  """ Base class for goal based agents
16
16
 
17
- Requires implemnentation of methods:
17
+ Requires implementation of methods:
18
18
  `program(percepts:Collection[Thing]) -> Action`
19
19
  `at_goal() -> bool`
20
20
  """
@@ -27,7 +27,7 @@ class GoalBasedAgent(ModelBasedAgent):
27
27
  class UtilityBasedAgent(GoalBasedAgent):
28
28
  """ Base class for utility based agents
29
29
 
30
- Requires implemnentation of methods:
30
+ Requires implementation of methods:
31
31
  `program(percepts:Collection[Thing]) -> Action`
32
32
  `at_goal() -> bool`
33
33
  `utility(action:Action) -> float`
@@ -0,0 +1,49 @@
1
+ from collections import deque
2
+ from typing import override, Iterator, TypeVar
3
+
4
+ GenericType = TypeVar('GenericType')
5
+ Numeric = int | float
6
+
7
+ class queue[GenericType]:
8
+ """ Wrapper for deque to provide FIFO queue functionality """
9
+ def __init__(self) -> None:
10
+ self.data:deque[GenericType] = deque() # internal deque storage
11
+
12
+ @override
13
+ def __repr__(self) -> str:
14
+ """ string representation of queue as sequence of elements """
15
+ return str(list(self.data))
16
+
17
+
18
+ def __iter__(self) -> Iterator[GenericType]:
19
+ """ Iterator over queue elements, allows in checks and looping """
20
+ return iter(self.data)
21
+
22
+
23
+ def push(self, x:GenericType) -> None:
24
+ """ Appends an item to the end of the queue
25
+
26
+ :param x: item to append
27
+ """
28
+ self.data.append(x)
29
+
30
+
31
+ def pop(self) -> GenericType:
32
+ """ Pops first element """
33
+ return self.data.popleft()
34
+
35
+
36
+
37
+ class stack[GenericType](queue):
38
+ """ Wrapper for deque to provide LIFO stack functionality """
39
+ @override
40
+ def pop(self) -> GenericType:
41
+ """ Pops last element """
42
+ return self.data.pop()
43
+
44
+
45
+ def manhattan(a: tuple[Numeric, Numeric], b:tuple[Numeric, Numeric]) -> Numeric:
46
+ """ Basic Manhattan distance calculation between two (x,y) points """
47
+ ax, ay = a # (x, y)
48
+ bx, by = b # (x, y)
49
+ return abs(bx-ax) + abs(by-ay)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: co2114
3
- Version: 2026.0.3
3
+ Version: 2026.1.1
4
4
  Summary: codebase for co2114
5
5
  Author: wil ward
6
6
  Requires-Python: >=3.12
@@ -2,7 +2,7 @@ from setuptools import setup
2
2
 
3
3
  setup(
4
4
  name="co2114",
5
- version="2026.0.3",
5
+ version="2026.1.1",
6
6
  description="codebase for co2114",
7
7
  author="wil ward",
8
8
  python_requires=">=3.12",
@@ -1,27 +0,0 @@
1
- from collections import deque
2
-
3
- class queue:
4
- def __init__(self):
5
- self.data = deque()
6
-
7
- def __repr__(self):
8
- return str(list(self.data))
9
-
10
- def __iter__(self):
11
- return iter(self.data)
12
-
13
- def push(self, x):
14
- self.data.append(x)
15
-
16
- def pop(self):
17
- return self.data.popleft()
18
-
19
- class stack(queue):
20
- def pop(self):
21
- return self.data.pop()
22
-
23
-
24
- def manhattan(a, b):
25
- ax, ay = a # (x, y)
26
- bx, by = b # (x, y)
27
- return abs(bx-ax) + abs(by-ay)
File without changes
File without changes
File without changes
File without changes
File without changes