I'm working on a mini project to force me to learn OOP in Python and expand my range of tools. Essentially, I just want to code a basic console application where I will have a 2D 'terrain' where there will be inanimate objects (e.g. consumables, trees, rocks) and animate objects (either player controlled by keyboard input or AI-controlled).
Before I go too far with this, I'd like to have a code review, mostly on the application of OOP here and where it could lead me to future headaches the way I coded this.
Some more specific questions:
- Is it standard to have many functions with @property decorator ? It feels.. odd ?
- What are some good practices in exception handling in Python ? I thought of using exceptions to ensure that animate actions such as moving are valid (e.g. not moving out of bounds or moving over a blocking inanimate object such as a tree).
- Is it worth it to split my code into packages / modules at this point ? I have never really felt the need to do it so far because my code is usually < 150 lines.
Currently, this code creates a 10x10 terrain, assigns inanimate object to roughly 10% of free spaces, then places two animate objects on random free spaces and prints the resulting terrain.
# Robots
import random
class Position:
def __init__(self, x, y):
self._x = x
self._y = y
@property
def x(self):
return self._x
@property
def y(self):
return self._y
class Inanimate:
def __init__(self, walkable):
self._walkable = walkable
@property
def walkable(self):
return self._walkable
class AnimateManager:
action_queue = []
def get_animate_actions(self, terrain):
for animate_elem in Animate.animate_list:
action_queue.append((animate_elem, animate_elem.get_action(terrain)))
def initialize_animates(self, terrain):
free_placements = terrain.get_free_placements()
if len(free_placements) < len(Animate.animate_list):
raise Exception('NOT ENOUGH FREE PLACEMENTS')
for animate_elem in Animate.animate_list:
new_position = random.randint(0, len(free_placements))
coordinates = free_placements[new_position]
try:
terrain.place_element(animate_elem, coordinates.x, coordinates.y)
free_placements = free_placements[:max(new_position - 1, 0)] + free_placements[1 if new_position == 0 else new_position:]
except Exception as e:
print('Exception raised: ', e)
class Animate:
animate_list = []
new_animate_id = 0
def __init__(self):
self._name = 'BLAH'
self._animate_id = Animate.get_new_animate_id()
Animate.animate_list.append(self)
def __del__(self):
Animate.remove_animate(self._animate_id)
@staticmethod
def remove_animate(id):
for i, x in enumerate(Animate.animate_list):
if x.animate_id == id:
Animate.animate_list = Animate.animate_list[:max(i-1, 0)] + Animate.animate_list[1 if i == 0 else i:]
break
@staticmethod
def get_new_animate_id():
Animate.new_animate_id += 1
return Animate.new_animate_id - 1
@property
def animate_id(self):
return self._animate_id
def get_action(self, terrain):
return 'HELLO ' + str(self.animate_id)
class Terrain:
def __init__(self, width, height):
self.generate_terrain(width, height)
@property
def width(self):
return self._width
@property
def height(self):
return self._height
@property
def terrain(self):
return self._terrain
def generate_terrain(self, width, height):
inanimate_rate = 0.1
self._width = width
self._height = height
self._terrain = [[] for x in range(self._height)]
for i, line in enumerate(self._terrain):
self._terrain[i] = [[Inanimate(False)] if ((random.randint(0, 100) + 1) < (100 * inanimate_rate)) else [] for x in range(self._width)]
return self._terrain
def place_element(self, element, location_x, location_y):
if location_x >= self._width or location_y >= self._height:
raise Exception('OUT OF BOUNDS')
pass
elif self._terrain[location_x][location_y] != []:
raise Exception('OCCUPIED')
pass
else:
self._terrain[location_x][location_y] = element
def get_free_placements(self):
free_placements = []
for i in range(self._width):
for j in range(self._height):
if self._terrain[i][j] == []:
free_placements.append(Position(i, j))
return free_placements
terrain = Terrain(10, 10)
a = Animate()
b = Animate()
a_mgr = AnimateManager()
a_mgr.initialize_animates(terrain)
print(terrain.terrain)