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/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
- def __repr__(self):
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
- def __repr__(self):
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
- def __repr__(self):
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
- def __repr__(self):
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
- def __repr__(self):
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
- def __repr__(self):
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
- size = width, height = 600, 400
28
- def __init__(self):
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
- pygame.init()
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) # Default system font
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
- def on_event(self, event):
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
- def cleanup(self):
61
+
62
+
63
+ def cleanup(self) -> None:
64
+ """ Cleanup engine on exit """
45
65
  print(f"Quiting {self.name}")
46
66
  pygame.quit()
47
- def run(self, *render_sequence):
48
- while (self.isrunning):
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
- def __init__(self, fps:int=DEFAULT_FPS, lps:int=DEFAULT_LPS, dims=None):
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
- super().__init__()
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
- def _update(self):
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
- def _render(self):
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
- def update(self):
86
- NotImplemented
87
- def render(self):
88
- NotImplemented
89
- def run(self):
90
- super().run(self._update, self._render)
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
- def __init__(self, *args, name=None, **kwargs):
95
- if name is not None:
96
- self._name = name
97
- super().__init__(*args, **kwargs)
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(App):
100
- app = 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
- super().__init__(fps=60, lps=60)
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
- def update(self):
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
- def render(self):
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()