co2114 2025.2.2__py3-none-any.whl → 2026.0.2__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.
- co2114/agent/environment.py +300 -134
- co2114/agent/things.py +34 -10
- co2114/engine.py +114 -37
- co2114/search/graph.py +278 -129
- co2114/search/things.py +38 -14
- co2114/util/__init__.py +35 -11
- {co2114-2025.2.2.dist-info → co2114-2026.0.2.dist-info}/METADATA +4 -3
- {co2114-2025.2.2.dist-info → co2114-2026.0.2.dist-info}/RECORD +11 -11
- {co2114-2025.2.2.dist-info → co2114-2026.0.2.dist-info}/WHEEL +1 -1
- {co2114-2025.2.2.dist-info → co2114-2026.0.2.dist-info/licenses}/LICENSE +0 -0
- {co2114-2025.2.2.dist-info → co2114-2026.0.2.dist-info}/top_level.txt +0 -0
co2114/agent/things.py
CHANGED
|
@@ -2,67 +2,91 @@
|
|
|
2
2
|
|
|
3
3
|
Contains class definitions for some things
|
|
4
4
|
"""
|
|
5
|
-
from collections.abc import
|
|
5
|
+
from collections.abc import Callable, Collection, Iterable
|
|
6
|
+
from typing import override, Union
|
|
6
7
|
|
|
7
8
|
from ..util.fonts import platform
|
|
8
9
|
|
|
10
|
+
Action = Union[str, Iterable[str]]
|
|
11
|
+
|
|
9
12
|
class Thing:
|
|
10
13
|
"""The base class for all things"""
|
|
11
|
-
|
|
14
|
+
@override
|
|
15
|
+
def __repr__(self) -> str:
|
|
12
16
|
return "❓" if platform != "darwin" else "?"
|
|
13
17
|
|
|
14
18
|
## Some physical things
|
|
15
19
|
|
|
16
20
|
class Obstacle(Thing):
|
|
17
|
-
|
|
21
|
+
@override
|
|
22
|
+
def __repr__(self) -> str:
|
|
18
23
|
return "🚧" if platform != "darwin" else "X"
|
|
19
24
|
|
|
20
25
|
|
|
21
26
|
class Food(Thing):
|
|
22
|
-
|
|
27
|
+
@override
|
|
28
|
+
def __repr__(self) -> str:
|
|
23
29
|
return "🍔" if platform != "darwin" else "f"
|
|
24
30
|
|
|
25
31
|
|
|
26
32
|
class Water(Thing):
|
|
27
|
-
|
|
33
|
+
@override
|
|
34
|
+
def __repr__(self) -> str:
|
|
28
35
|
return "💧" if platform != "darwin" else "w"
|
|
29
36
|
|
|
30
37
|
|
|
31
38
|
class Animal(Thing):
|
|
39
|
+
""" A generic animal thing """
|
|
32
40
|
pass
|
|
33
41
|
|
|
34
42
|
|
|
35
43
|
class Dog(Animal):
|
|
36
44
|
"""If it looks like a dog and it barks like a dog ..."""
|
|
37
|
-
|
|
45
|
+
@override
|
|
46
|
+
def __repr__(self) -> str:
|
|
38
47
|
return "🐶" if platform != "darwin" else "d"
|
|
39
48
|
|
|
49
|
+
|
|
40
50
|
## Some agent things
|
|
41
51
|
|
|
42
52
|
class Agent(Thing):
|
|
53
|
+
""" Base class for all agents """
|
|
43
54
|
pass
|
|
44
55
|
|
|
45
56
|
|
|
46
57
|
class RationalAgent(Agent):
|
|
47
58
|
""" Base class for rational agent """
|
|
48
|
-
def __init__(self, program):
|
|
59
|
+
def __init__(self, program:Callable) -> None:
|
|
49
60
|
self.performance = 0
|
|
61
|
+
# Check that program is callable function
|
|
50
62
|
if program is None or not isinstance(program, Callable):
|
|
51
63
|
raise ValueError("No valid program provided")
|
|
52
|
-
self.program = program
|
|
64
|
+
self.program:Callable = program
|
|
53
65
|
|
|
54
66
|
|
|
55
67
|
class ModelBasedAgent(RationalAgent):
|
|
68
|
+
""" Base class for model based agents
|
|
69
|
+
|
|
70
|
+
Requires a program method to be defined
|
|
71
|
+
program(percepts:Collection[Thing]) -> Action
|
|
72
|
+
"""
|
|
56
73
|
def __init__(self):
|
|
74
|
+
""" Initialize the agent with its program """
|
|
57
75
|
super().__init__(self.program)
|
|
58
76
|
|
|
59
|
-
def program(self, percepts):
|
|
77
|
+
def program(self, percepts:Collection[Thing]) -> Action:
|
|
78
|
+
""" The agent program
|
|
79
|
+
Given a collection of percepts, return an action
|
|
80
|
+
"""
|
|
60
81
|
raise NotImplementedError
|
|
61
82
|
|
|
62
83
|
## State
|
|
63
84
|
class State(Thing):
|
|
64
|
-
|
|
85
|
+
""" Base class for states """
|
|
86
|
+
@override
|
|
87
|
+
def __repr__(self) -> str:
|
|
65
88
|
return self.__class__.__name__
|
|
66
89
|
|
|
67
90
|
class Bump(State):
|
|
91
|
+
""" A bump state """
|
|
68
92
|
pass
|
co2114/engine.py
CHANGED
|
@@ -4,64 +4,107 @@ This contains the code for the base engine
|
|
|
4
4
|
import warnings
|
|
5
5
|
import time
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from collections.abc import
|
|
7
|
+
from collections.abc import Iterable, Callable
|
|
8
|
+
from typing import override
|
|
8
9
|
|
|
9
10
|
with warnings.catch_warnings():
|
|
10
11
|
warnings.simplefilter("ignore")
|
|
11
12
|
import pygame
|
|
12
13
|
|
|
13
|
-
if __name__ == "__main__":
|
|
14
|
+
if __name__ == "__main__": # being run as script
|
|
14
15
|
from co2114.util import colours, fonts
|
|
15
16
|
COLOR_BLACK, COLOR_WHITE = colours.COLOR_BLACK, colours.COLOR_WHITE
|
|
16
17
|
else:
|
|
17
18
|
from .util.colours import COLOR_BLACK, COLOR_WHITE
|
|
18
19
|
from .util import fonts
|
|
19
20
|
|
|
20
|
-
TEXT_FONT = fonts._get_text_font_unsafe()
|
|
21
|
+
TEXT_FONT = fonts._get_text_font_unsafe() # default text font
|
|
21
22
|
|
|
22
23
|
## GLOBALS
|
|
23
24
|
DEFAULT_FPS:int = 30 # Render frames per second
|
|
24
25
|
DEFAULT_LPS:int = 2 # Environment steps per second
|
|
25
26
|
|
|
26
27
|
class BaseEngine:
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
""" Base Engine Class
|
|
29
|
+
|
|
30
|
+
Provides basic PyGame setup and event loop
|
|
31
|
+
"""
|
|
32
|
+
size = width, height = 600, 400 # default window size
|
|
33
|
+
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
""" Initialise Base Engine """
|
|
29
36
|
print(f"Creating instance of {self.name} ({self.__class__.__name__})")
|
|
30
|
-
|
|
37
|
+
|
|
38
|
+
pygame.init() # initialise pygame
|
|
39
|
+
|
|
31
40
|
self.screen = pygame.display.set_mode(self.size)
|
|
32
41
|
pygame.display.set_caption(f"{self.name} (co2114)")
|
|
33
|
-
self._font = pygame.font.SysFont(TEXT_FONT, 28) #
|
|
42
|
+
self._font = pygame.font.SysFont(TEXT_FONT, 28) # default system font
|
|
34
43
|
self._running:bool = True
|
|
44
|
+
|
|
45
|
+
|
|
35
46
|
@property
|
|
36
47
|
def name(self) -> str:
|
|
48
|
+
""" Get engine name """
|
|
37
49
|
return self.__class__.__name__
|
|
50
|
+
|
|
38
51
|
@property
|
|
39
52
|
def isrunning(self) -> bool:
|
|
53
|
+
""" Check if engine is running """
|
|
40
54
|
return self._running
|
|
41
|
-
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def on_event(self, event:pygame.event.Event) -> None:
|
|
58
|
+
""" Event handler """
|
|
42
59
|
if event.type == pygame.QUIT:
|
|
43
60
|
self._running = False
|
|
44
|
-
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def cleanup(self) -> None:
|
|
64
|
+
""" Cleanup engine on exit """
|
|
45
65
|
print(f"Quiting {self.name}")
|
|
46
66
|
pygame.quit()
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _run(self, *render_sequence:Callable) -> None:
|
|
70
|
+
""" Main event loop
|
|
71
|
+
|
|
72
|
+
:param *render_sequence: sequence of functions to call each loop
|
|
73
|
+
"""
|
|
74
|
+
while self.isrunning:
|
|
49
75
|
for event in pygame.event.get():
|
|
50
|
-
self.on_event(event)
|
|
51
|
-
for fcn in render_sequence:
|
|
76
|
+
self.on_event(event) # handle events
|
|
77
|
+
for fcn in render_sequence: # call each function
|
|
52
78
|
fcn()
|
|
53
|
-
self.cleanup()
|
|
79
|
+
self.cleanup() # cleanup on exit
|
|
80
|
+
|
|
54
81
|
|
|
55
82
|
class Engine(BaseEngine):
|
|
56
|
-
"""Engine
|
|
57
|
-
|
|
83
|
+
"""Engine Class
|
|
84
|
+
Provides basic framerate and looprate control"""
|
|
85
|
+
|
|
86
|
+
def __init__(self,
|
|
87
|
+
fps:int = DEFAULT_FPS,
|
|
88
|
+
lps:int = DEFAULT_LPS,
|
|
89
|
+
dims:tuple[int,int]|None = None) -> None:
|
|
90
|
+
""" Initialise Engine
|
|
91
|
+
|
|
92
|
+
:param fps: frames per second (render rate)
|
|
93
|
+
:param lps: loops per second (process rate)
|
|
94
|
+
:param dims: optional dimensions tuple (width, height)
|
|
95
|
+
"""
|
|
96
|
+
# set dimensions if provided
|
|
58
97
|
if dims and isinstance(dims, Iterable) and len(dims)==2:
|
|
59
98
|
self.size = self.width, self.height = dims
|
|
60
|
-
|
|
99
|
+
|
|
100
|
+
super().__init__() # initialise base engine
|
|
101
|
+
|
|
61
102
|
self._framerate:int = fps if isinstance(fps, int) else DEFAULT_FPS
|
|
62
103
|
self._looprate:int = lps if isinstance(lps, int) else DEFAULT_LPS
|
|
63
104
|
self._t0, self._l0 = time.time(), time.time() # timing count
|
|
64
|
-
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _update(self) -> None:
|
|
65
108
|
""" Processing loop internals """
|
|
66
109
|
# frame limiter
|
|
67
110
|
if self._framerate is None or not isinstance(self._framerate, int):
|
|
@@ -70,6 +113,7 @@ class Engine(BaseEngine):
|
|
|
70
113
|
if (time.time() - self._t0) < mspf: # if faster than framerate
|
|
71
114
|
time.sleep((mspf - (time.time()-self._t0))/1000) # sleep
|
|
72
115
|
self._t0 = time.time() # update timer
|
|
116
|
+
|
|
73
117
|
# main render loop
|
|
74
118
|
if self._looprate is None or not isinstance(self._looprate, int):
|
|
75
119
|
self._looprate = DEFAULT_LPS # corrective measures
|
|
@@ -77,30 +121,55 @@ class Engine(BaseEngine):
|
|
|
77
121
|
if(time.time() - self._l0) > spf: # if enough time passed
|
|
78
122
|
self._l0 = time.time() # update timer
|
|
79
123
|
self.update() # run main process update
|
|
80
|
-
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _render(self) -> None:
|
|
81
127
|
""" Render loop internals """
|
|
82
128
|
self.screen.fill(COLOR_BLACK) # write black to buffer
|
|
83
129
|
self.render() # run main render loop
|
|
84
130
|
pygame.display.flip() # flip buffer
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def update(self) -> None:
|
|
134
|
+
""" Main process loop, needs override """
|
|
135
|
+
raise NotImplementedError
|
|
136
|
+
|
|
137
|
+
def render(self) -> None:
|
|
138
|
+
""" Main render loop, needs override """
|
|
139
|
+
raise NotImplementedError
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def run(self) -> None:
|
|
143
|
+
""" Run the engine """
|
|
144
|
+
super()._run(self._update, self._render)
|
|
91
145
|
|
|
92
146
|
|
|
93
147
|
class App(Engine):
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
148
|
+
""" Base App Class
|
|
149
|
+
|
|
150
|
+
Provides default name and run method for GUI apps
|
|
151
|
+
"""
|
|
152
|
+
def __init__(self, *args, name:str | None = None, **kwargs) -> None:
|
|
153
|
+
""" Initialise App
|
|
154
|
+
|
|
155
|
+
:param *args: positional arguments for Engine
|
|
156
|
+
:param name: optional name for the app
|
|
157
|
+
:param **kwargs: keyword arguments for Engine
|
|
158
|
+
"""
|
|
159
|
+
if name is not None: self._name = name # set name if provided
|
|
160
|
+
super().__init__(*args, **kwargs) # initialise engine
|
|
161
|
+
|
|
162
|
+
|
|
98
163
|
@classmethod
|
|
99
|
-
def run_default(
|
|
100
|
-
app
|
|
164
|
+
def run_default(cls) -> None:
|
|
165
|
+
""" Run the app with default parameters """
|
|
166
|
+
app = cls()
|
|
101
167
|
app.run()
|
|
168
|
+
|
|
169
|
+
|
|
102
170
|
@property
|
|
103
171
|
def name(self) -> str:
|
|
172
|
+
""" Get app name """
|
|
104
173
|
return self._name if hasattr(self, "_name") else super().name
|
|
105
174
|
|
|
106
175
|
|
|
@@ -114,26 +183,33 @@ class ClockApp(App):
|
|
|
114
183
|
|
|
115
184
|
# size = width, height = 600, 400 # uncomment to override
|
|
116
185
|
|
|
117
|
-
def __init__(self):
|
|
118
|
-
|
|
186
|
+
def __init__(self) -> None:
|
|
187
|
+
""" Initialise Clock App """
|
|
188
|
+
super().__init__(fps=60, lps=60) # initialise app
|
|
119
189
|
self._font = pygame.font.SysFont(TEXT_FONT, 128) # override font
|
|
120
|
-
self.t = datetime.now()
|
|
190
|
+
self.t = datetime.now() # current time
|
|
121
191
|
|
|
122
|
-
|
|
192
|
+
# @override
|
|
193
|
+
def update(self) -> None:
|
|
123
194
|
""" Main process loop
|
|
195
|
+
|
|
124
196
|
Gets current system time
|
|
125
197
|
"""
|
|
126
|
-
self.t = datetime.now()
|
|
198
|
+
self.t = datetime.now() # get current time
|
|
127
199
|
|
|
128
|
-
|
|
200
|
+
# @override
|
|
201
|
+
def render(self) -> None:
|
|
129
202
|
""" Main render loop
|
|
203
|
+
|
|
130
204
|
Writes time to screen
|
|
131
205
|
"""
|
|
206
|
+
# render time string
|
|
132
207
|
renderTime = self._font.render(
|
|
133
208
|
self.t.strftime("%H:%M:%S.%f")[:-5],
|
|
134
209
|
True,
|
|
135
210
|
COLOR_WHITE)
|
|
136
211
|
rect = renderTime.get_rect()
|
|
212
|
+
# blit to screen centered
|
|
137
213
|
self.screen.blit(
|
|
138
214
|
renderTime,
|
|
139
215
|
(self.width//2 - rect.width//2,
|
|
@@ -142,5 +218,6 @@ class ClockApp(App):
|
|
|
142
218
|
|
|
143
219
|
####################################
|
|
144
220
|
if __name__ == "__main__":
|
|
221
|
+
""" Run ClockApp if engine.py is run as script """
|
|
145
222
|
print("Running engine.py as script.")
|
|
146
223
|
ClockApp.run_default()
|