Coverage for src/gncpy/game_engine/components.py: 0%

98 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-13 06:15 +0000

1"""Defines components for use by game entities.""" 

2import os 

3import pathlib 

4import numpy as np 

5import pygame 

6 

7os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" 

8import serums.models as smodels # noqa 

9 

10 

11library_asset_dir = os.path.join( 

12 pathlib.Path(__file__).parent.parent.resolve(), "games", "assets" 

13) 

14"""Directory containing common game assets.""" 

15 

16 

17class CShape: 

18 """Contains properties related to the drawn shape. 

19 

20 Attributes 

21 ---------- 

22 type : string 

23 Type of shape to create. 

24 shape : pygame object 

25 Shape to render. 

26 color : tuple 

27 RGB triplet for the color in range [0, 255]. 

28 zorder : int 

29 Determines draw order, lower numbers are drawn first. 

30 """ 

31 

32 __slots__ = "type", "shape", "color", "zorder" 

33 

34 def __init__( 

35 self, s_type=None, w=None, h=None, color=None, zorder=None, fpath=None 

36 ): 

37 """Initialize an object. 

38 

39 Parameters 

40 ---------- 

41 s_type : string 

42 Type of shape to create. Options are :code:`'rect'`, or 

43 :code:`'sprite'`. 

44 w : int 

45 Width in pixels. Must be specified if type :code:`'rect'`, optional 

46 for sprites (will scale image to this width). 

47 h : int 

48 Height in pixels. Must be specified if type :code:`'rect'`, optional 

49 for sprites (will sacle image to this height). 

50 color : tuple 

51 RGB triplet for the color in range [0, 255]. Only used by type 

52 :code:`'rect'`. 

53 zorder : int 

54 Determines draw order, lower numbers are drawn first. 

55 """ 

56 self.type = s_type.lower() 

57 if self.type == "rect": 

58 self.shape = pygame.Rect((0, 0), (w, h)) 

59 

60 elif self.type == "sprite": 

61 if fpath is None: 

62 raise RuntimeError( 

63 "File path cannot be None for type {}".format(s_type) 

64 ) 

65 succ = os.path.isfile(fpath) 

66 if not succ: 

67 fp = os.path.join(library_asset_dir, fpath) 

68 succ = os.path.isfile(fp) 

69 else: 

70 fp = fpath 

71 

72 if not succ: 

73 raise FileNotFoundError("Failed to find file {}".format(fpath)) 

74 

75 img = pygame.image.load(fp) 

76 if fp[-3:] == "png": 

77 img = img.convert_alpha() 

78 else: 

79 img = img.convert() 

80 

81 if w is not None and h is not None: 

82 self.shape = pygame.transform.scale(img, (w, h)) 

83 else: 

84 self.shape = img 

85 

86 else: 

87 raise NotImplementedError("Shape type {} not implemented".format(s_type)) 

88 

89 self.color = color 

90 self.zorder = zorder 

91 

92 

93class CTransform: 

94 """Contains properties relating to the spatial components. 

95 

96 Attributes 

97 ---------- 

98 pos : 2 x 1 numpy array 

99 position of the center in pixels 

100 last_pos : 2 x 1 numpy array 

101 last position of the center in pixels. 

102 vel 2 x 1 numpy array 

103 velocity of the center in pixels per timestep 

104 """ 

105 

106 __slots__ = "pos", "last_pos", "vel" 

107 

108 def __init__(self): 

109 self.pos = np.nan * np.ones((2, 1)) 

110 self.last_pos = np.nan * np.ones((2, 1)) 

111 self.vel = np.nan * np.ones((2, 1)) 

112 

113 

114class CCollision: 

115 """Handles the bounding box used for collisions. 

116 

117 Attributes 

118 --------- 

119 aabb : pygame Rect 

120 Rectangle representing the axis aligned bounding box. 

121 """ 

122 

123 __slots__ = "aabb" 

124 

125 def __init__(self, w=None, h=None): 

126 if w is not None and h is not None: 

127 self.aabb = pygame.Rect((0, 0), (w, h)) 

128 else: 

129 self.aabb = None 

130 

131 

132class CBirth: 

133 """Handles the birth model properties. 

134 

135 Also handles the generation of the birth location through the use of 

136 a distribution object from SERUMS. 

137 

138 Attributes 

139 ---------- 

140 loc : numpy array 

141 Location parameter for distribution 

142 randomize : bool 

143 Flag indicating if the state should be randomly sampled. 

144 """ 

145 

146 __slots__ = "_model", "loc", "_rng", "randomize" 

147 

148 def __init__( 

149 self, b_type=None, loc=None, scale=None, params=None, rng=None, randomize=True 

150 ): 

151 """Initializes an object. 

152 

153 Parameters 

154 ---------- 

155 b_type : string 

156 Model type. Options are :code:`'gaussian'`. 

157 loc : numpy array 

158 location parameter of the model. 

159 scale : N x N numpy array 

160 scale parameter of the model. 

161 params : dict 

162 additional parameters for the model. 

163 """ 

164 if b_type.lower() == "gaussian": 

165 if len(scale.shape) == 1: 

166 std = np.diag(scale) 

167 else: 

168 std = scale 

169 self._model = smodels.Gaussian(mean=loc, covariance=std ** 2) 

170 else: 

171 self._model = None 

172 self.loc = loc 

173 self._rng = rng 

174 self.randomize = randomize 

175 

176 def sample(self): 

177 """Draw a sample from the distribution. 

178 

179 Will provide the location parameter if not randomizing. 

180 

181 Returns 

182 ------- 

183 N x 1 numpy array 

184 """ 

185 if self.randomize: 

186 return self._model.sample(rng=self._rng).reshape((-1, 1)) 

187 else: 

188 return self._model.location.reshape((-1, 1)).copy() 

189 

190 

191class CEvents: 

192 """Holds the events properties. 

193 

194 Attributes 

195 ---------- 

196 events : list 

197 Each element is a tuple with the first being an event identifier and 

198 the second a dict with extra info. 

199 """ 

200 

201 __slots__ = "events" 

202 

203 def __init__(self): 

204 self.events = [] 

205 

206 

207class CHazard: 

208 """Hold the properties for hazard info. 

209 

210 Attributes 

211 ---------- 

212 prob_of_death : float 

213 Probability of death at each timestep in the hazard. Must be in the 

214 range (0, 1]. 

215 entrance_times : dict 

216 mapping of player id to entrance time. 

217 """ 

218 

219 __slots__ = "prob_of_death", "entrance_times" 

220 

221 def __init__(self, prob_of_death=None): 

222 """Initialize an object. 

223 

224 Parameters 

225 ---------- 

226 prob_of_death : float 

227 Probability of death at each timestep. 

228 """ 

229 self.prob_of_death = prob_of_death 

230 self.entrance_times = {} 

231 

232 

233class CCapabilities: 

234 """Hold properties about player/target capabilities. 

235 

236 Attributes 

237 ---------- 

238 capabilities : list 

239 Each element defines a capability. The :code:`in` keyword must work 

240 for chcking if an item is in the list. 

241 """ 

242 

243 __slots__ = "capabilities" 

244 

245 def __init__(self, capabilities=None): 

246 """Initialize an object. 

247 

248 Parameters 

249 ---------- 

250 capabilities : list 

251 List of capabilities. 

252 """ 

253 if capabilities is None: 

254 capabilities = [] 

255 self.capabilities = capabilities 

256 

257 

258class CPriority: 

259 """Hold properties about the priority. 

260 

261 Attributes 

262 ---------- 

263 priority : float 

264 Priority value. 

265 """ 

266 

267 __slots__ = "priority" 

268 

269 def __init__(self, priority=None): 

270 """Initialize an object. 

271 

272 Parameters 

273 ---------- 

274 priority : float 

275 priority of the object. 

276 """ 

277 self.priority = priority 

278 

279 

280class CDynamics: 

281 """Handles all the properties relaing to the dynamics. 

282 

283 Also implements the logic for propagating the state via a dynamics 

284 object. 

285 

286 Attributes 

287 ---------- 

288 dynObj : :class:`gncpy.dynamics.DynamicsBase` 

289 Implements the dynamics equations, control, and state constraints. 

290 last_state : numpy array 

291 Last state for the dynamics. 

292 pos_inds : list 

293 Indices of the state vector containing the position info. 

294 vel_inds : list 

295 Indices of the state vector containing the velocity info. 

296 state_args : tuple 

297 Additional arguments for propagating the state. 

298 ctrl_args : tuple 

299 Additional arguments for propagating the state. 

300 state_low : numpy array 

301 Lower limit of each state. 

302 state_high : numpy array 

303 Upper limit of each state. 

304 """ 

305 

306 __slots__ = ( 

307 "dynObj", 

308 "last_state", 

309 "state", 

310 "pos_inds", 

311 "vel_inds", 

312 "state_args", 

313 "ctrl_args", 

314 "state_low", 

315 "state_high", 

316 ) 

317 

318 def __init__( 

319 self, 

320 dynObj=None, 

321 pos_inds=None, 

322 vel_inds=None, 

323 state_args=None, 

324 ctrl_args=None, 

325 state_low=None, 

326 state_high=None, 

327 ): 

328 """Initialize an object. 

329 

330 Parameters 

331 ---------- 

332 dynObj : :class:`gncpy.dynamics.DynamicsBase` 

333 Implements the dynamics equations, control, and state constraints. 

334 pos_inds : list 

335 Indices of the state vector containing the position info. 

336 vel_inds : list 

337 Indices of the state vector containing the velocity info. 

338 state_args : tuple 

339 Additional arguments for propagating the state. 

340 ctrl_args : tuple 

341 Additional arguments for propagating the state. 

342 state_low : numpy array 

343 Lower limit of each state. 

344 state_high : numpy array 

345 Upper limit of each state. 

346 """ 

347 self.dynObj = dynObj 

348 if self.dynObj is not None: 

349 n_states = len(self.dynObj.state_names) 

350 self.last_state = np.nan * np.ones((n_states, 1)) 

351 self.state = np.zeros((n_states, 1)) 

352 

353 self.pos_inds = pos_inds 

354 self.vel_inds = vel_inds 

355 if state_args is None: 

356 state_args = () 

357 self.state_args = state_args 

358 if ctrl_args is None: 

359 ctrl_args = () 

360 self.ctrl_args = ctrl_args 

361 self.state_low = state_low 

362 self.state_high = state_high