Design Strategy - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: Game Development (https://python-forum.io/forum-11.html) +--- Thread: Design Strategy (/thread-11717.html) |
Design Strategy - microphone_head - Jul-23-2018 I'm working on refining my strategy for writing a game. I would like to know what you lot think about it. Hoping you'll point out the flaws, and let me know what you think. I'm working on a project like Asteroids, there's not much to the game, you can fly around, shoot at rocks and try not to get killed. If a rock hits the craft its game over. If the craft goes off the screen I want the autopilot to get the craft back on the screen. I been finding the process of implementing a realistic autopilot challenging. Here are some of the steps it goes through: - Turn the craft around in the opposite direction that its flying in. - Apply the thruster to come to a complete stop. - Turn the craft to point at the center of the screen. - Apply thrusters at the right time to glide the craft back on the screen. To make it more realistic the routines can't just keep looping until the task was done because I want the game to do other things like refresh the screen. I orginally achieved 65% of this with block if statements, but decided things were growing and it was starting to look untidy and hard to manage. I like to make things easy, so I thought what if I created a list of tasks to achieve the same thing, wouldn't that be easier? PS. The AutoPilot class is a work-in-progress, an idea I have for how I would like handle the steps needed to achieve a goal, like returning a craft that has gone off the screen and returning it the centre of the screen. def enum(*args): # --------------------------------------------------------------------------- # Used to create enumate values. # # Example: # fruits =enum("APPLES", "ORANGES", "PEARS") # if fruits.APPLES ==fruits.ORANGES: # print("they're the same.") # # --------------------------------------------------------------------------- enums =dict(zip(args, range(len(args)))) return type('Enum', (), enums) # enum() -------------------------------------------------------------------- class MemSpace: pass # MemSpace ------------------------------------------------------------------ class Task: _list =[] statuses =enum("WAITING", "INIT", "RUNNING", "COMPLETED") def __init__(self): self.var =MemSpace() self.status =Task.statuses.WAITING Task._list.append(self) # __init__() ------------------------------------------------------------ @staticmethod def all_routines(): # Debug information. print("\n\nTASK LIST INFORMATION") print("-------------------------") for cp in Task._list: print(" name:", cp.var.name) print(" - status =", status_text(cp.status)) print("...\n\n") # _. # Run all the active routines. for cp in Task._list: cp.routine() # _. # --- Delete Completed Tasks --- something_running =False index =0 while index <len(Task._list): deleted_a_task =False cp =Task._list[index] if cp.status ==Task.statuses.COMPLETED: # We don't need this Task # anymore, delete it from our list. Task._list.pop(index) # Reset the search to start from # the top again because we messed # with the ordinal positions of # the list. deleted_a_task =True elif not cp.status ==Task.statuses.WAITING: # We can presume that task must # be initialising or running. something_running =True if not deleted_a_task: index +=1 if not something_running: # Looks like there's nothing # running, start the first task # we come across. Task.start_next() # all_routines() ----------------------------------------------------- @staticmethod def start_next(): # Looks at the list and starts the next Task. if len(Task._list) >0: # There's a task in the list. Task._list[0].status =Task.statuses.INIT # _start_next() --------------------------------------------------------- def routine(self): const =Task.statuses if not self.status in (const.WAITING, const.COMPLETED): self.var.routine(self) # routine() --------------------------------------------------------- # Task ------------------------------------------------------------------- class AutoPilot: @staticmethod def turn_craft_oaf(active_Task): # Turn the craft in the opposite angle of flight. var =active_Task.var print("\nRoutine 'turn_craft_oaf' called.") active_Task.status =Task.statuses.COMPLETED active_Task.owner ="Alan" # turn_craft_oaf() ------------------------------------------------------ @staticmethod def stationary_position(active_Task): var =active_Task.var print("\nRoutine 'stationary_position' called.") try: if var.cntr >= 2: active_Task.status =Task.statuses.COMPLETED print("\nTask 'stationary_position' has changed its status to COMPLETED.") except AttributeError: # Assume the counter was never set, # start it off at 0. var.cntr =0 var.cntr +=1 # stationary_position() ------------------------------------------------- # AutoPilot ----------------------------------------------------------------- def status_text(status_id): if status_id ==Task.statuses.WAITING: status_msg ="WAITING" elif status_id ==Task.statuses.INIT: status_msg ="INIT" elif status_id ==Task.statuses.RUNNING: status_msg ="RUNNING" elif status_id ==Task.statuses.COMPLETED: status_msg ="COMPLETED" else: status_msg ="UnKnown" return status_msg # status_text() ------------------------------------------------------------- # Create a task to turn the craft. my_task =Task() my_task.var.name ="Turn" my_task.var.target_angle =90 my_task.var.current_angle =10 my_task.var.routine =AutoPilot.turn_craft_oaf my_task.status =Task.statuses.INIT # _. # Create a task to bring the craft to a complete stop. my_task =Task() my_task.var.name ="Stationary" my_task.var.routine =AutoPilot.stationary_position # _. # Simulate the routines being run inside a large # game routine that's moving sprites around and # updating the screen. for x in range(5): # Run any additional tasks that might have # been created. Task.all_routines() # Perform animation tasks. # ... # Refresh the screen image. # ...
RE: Design Strategy - Windspar - Jul-24-2018 Here a python enum. from enum import Enum class Status(Enum): waiting = 0 init = 1 running = 2 completed = 3 def main(): print(Status.waiting) print(Status.init) main() RE: Design Strategy - microphone_head - Jul-24-2018 Thanks for the enum code. I agree its a good way to create constants. But don't you like the enum routine i found? Took me ages to find it. I think the routine only gets called to register the attribute (like WAITING). I like the enum routine so much because there are times when I want to make a long list of (what I would regard as) constants. Most of the time I don't need to worry about the actual value of these constants. I can just write it out all in one line, or break it up over several lines. Then if I find that I need to amend it I just change the text and not worry about updating the values. RE: Design Strategy - Windspar - Jul-24-2018 You can also do it in a string. from enum import Enum def main(): Status = Enum("Status", "waiting init running completed", module=__name__) print(Status.init) main() or you can do it in a list from enum import Enum def main(): pylist = ["waiting", "init", "running", "completed"] Status = Enum("Status", pylist, module=__name__) print(Status.init) main() or use auto number example from enum model. from enum import Enum class AutoNumber(Enum): def __new__(cls): value = len(cls.__members__) + 1 obj = object.__new__(cls) obj._value_ = value return obj class Status(AutoNumber): waiting = () init = () running = () completed = () def main(): print(Status.waiting) print(Status.init.value == 2) main() RE: Design Strategy - microphone_head - Jul-27-2018 Thanks Windspar. I've had a little plaly around with the three examples and I think I'm convert. My gut tells me to go with example one (do it in a string), seems better than the stuff i've been using, and is low maintenance. RE: Design Strategy - Windspar - Jul-28-2018 You might find some code useful. example from enum import Enum Status = Enum("Status", "waiting, init, running, completed") class Task: queue = [] running = 0 def __init__(self, task, status): self.var = task self.status = status def routine(self): if self.status in (Status.init, Status.running): self.status = self.var.routine(self.status) @classmethod def add(cls, task, status=Status.waiting): cls.queue.append(cls(task, status)) @classmethod def empty(cls): return len(cls.queue) == 0 @classmethod def routines(cls): print("\nTask List") new_queue = [] cls.running = 0 for q in cls.queue: print("\n name:", q.var.name) print(" - status:", q.status, "\n") q.routine() if q.status in (Status.init, Status.running): cls.running += 1 if q.status != Status.completed: new_queue.append(q) cls.queue = new_queue if cls.running == 0: if len(cls.queue) > 0: cls.queue[0].status = Status.init class AutoPilot: def __init__(self): pass @classmethod def turn(cls, target_angle, current_angle): obj = cls() obj.name = "Turn" obj.target_angle = target_angle obj.current_angle = current_angle obj.routine = obj.turn_craft_oaf return obj @classmethod def stationary(cls): obj = cls() obj.name = "Stationary" obj.routine = obj.stationary_position return obj def turn_craft_oaf(self, status): print("Routine 'turn_craft_oaf' called") return Status.completed def stationary_position(self, status): print("Routine 'stationary' called") if status == Status.init: print("Routine 'stationary' init") self.center = 0 return Status.running else: self.center += 1 if(self.center >= 2): print("Routine 'stationary' completed") return Status.completed return status def main(): Task.add(AutoPilot.turn(90, 10), Status.init) Task.add(AutoPilot.stationary()) while not Task.empty(): Task.routines() main() another example. from enum import Enum Status = Enum("Status", "waiting, init, running, completed") class Task: queue = [] running = 0 def __init__(self, task, status): self.task = task self.status = status def routine(self): if self.status in (Status.init, Status.running): self.task.routine(self) @classmethod def add(cls, task, status=Status.waiting): cls.queue.append(cls(task, status)) @classmethod def empty(cls): return len(cls.queue) == 0 @classmethod def routines(cls): print("\nTask List") new_queue = [] cls.running = 0 for q in cls.queue: print("\n name:", q.task.name) print(" - status:", q.status, "\n") q.routine() if q.status in (Status.init, Status.running): cls.running += 1 if q.status != Status.completed: new_queue.append(q) cls.queue = new_queue if cls.running == 0: if len(cls.queue) > 0: cls.queue[0].status = Status.init class AutoPilot: def __init__(self, name, routine): self.name = name self.routine = routine @classmethod def turn_task(cls, target_angle, current_angle): obj = cls("Turn", cls.turn_craft_oaf) obj.target_angle = target_angle obj.current_angle = current_angle return obj @classmethod def stationary_task(cls): obj = cls("Stationary", cls.stationary_position) return obj @staticmethod def turn_craft_oaf(obj): print("Routine 'turn_craft_oaf' called") obj.status = Status.completed @staticmethod def stationary_position(obj): print("Routine 'stationary_position' called") if obj.status == Status.init: print("Routine 'stationary_position' init") obj.task.center = 0 obj.status = Status.running else: obj.task.center += 1 if(obj.task.center >= 2): print("Routine 'stationary_position' completed") obj.status = Status.completed def main(): Task.add(AutoPilot.turn_task(90, 10), Status.init) Task.add(AutoPilot.stationary_task()) while not Task.empty(): Task.routines() main() RE: Design Strategy - microphone_head - Jul-28-2018 Thanks for the examples above, that's great, you've given me a lot to think about. I haven't used "@classmethod" so I don't understand everything, but I like where its going. I'm going to go back to do some research on classes to clarify a few things about that statement in the class section. RE: Design Strategy - Windspar - Jul-28-2018 @classmethod passes the class @classmethod def add(cls, task, status=Status.waiting): cls.queue.append(cls(task, status))is equal to this @staticmethod def add(task, status=Status.waiting): Task.queue.append(Task(task, status)) RE: Design Strategy - microphone_head - Jul-28-2018 Thanks that's a great help. I'm gonna try to start using the @classmethod from now on. RE: Design Strategy - microphone_head - Aug-01-2018 I would like to here about anything that jumps out as not being Pythonic. I've been updating my code to use '@classmethod' where I could, trying to think more Phythonic. I've also been experimenting with the recommendation of putting our classes in seperate files. Here's what i've settled on, two class files that are referenced by a main project file. My idea is that I can create an instance of the Autopilot class for every space craft, and these would be able to created their own taskt. task_list.py """ source file: task_list.py """ from clsTask import * from clsAutopilot import * enterprise_ap =Autopilot() uss_enterprise_tasks =Task() # Add tasks to task queue. uss_enterprise_tasks.add(enterprise_ap.turn_craft_oaf(90, 10), STATUS.INIT) uss_enterprise_tasks.add(enterprise_ap.stationary_position()) uss_enterprise_tasks.add(enterprise_ap.turn_craft_oaf(110, 270)) # _. Task.print_queue_info(uss_enterprise_tasks) while not Task.empty_task_queues(): Task.process_task_list()clsAutopilot.py Note that this class imports the same class used in the main project file. """ source file: clsAutopilot.py """ from clsTask import * class Autopilot: parameters ={} variables ={} def turn_craft_oaf(self, target_angle, current_angle): 'Function - generates a task (definition) and registers it in the queue.' # Prepare parameter(s) for associated # class routine. obj =Task.definition() obj.name ="turn_craft_oaf" obj.linked_routine = self._turn_craft_oaf # Store parameter information in this # class. par ={} par["target_angle"] =target_angle par["current_angle"] =current_angle self.parameters["_turn_craft_oaf"] =par # _. return obj # turn_craft_oaf() ------------------------------------------------------ @staticmethod def _turn_craft_oaf(obj): 'Function - designed to be called from a taks queue.' print(" - running routine 'turn_craft_oaf'.") # Get a dictionary of all the parameters # for this routine. par =Autopilot.parameters["_turn_craft_oaf"] # _. # Turn the craft in the opposite angle of flight. target_angle =par["target_angle"] current_angle =par["current_angle"] obj.status =STATUS.COMPLETED # _turn_craft_oaf() ------------------------------------------------------ def stationary_position(self): 'Function - generates a task (definition) and registers it in the queue.' obj =Task.definition() obj.name ="stationary_position" obj.linked_routine =self._stationary_position # Prepare variables for the tasked linked routine. var ={} var["center"] =0 self.variables["_stationary_position"] =var return obj # stationary_position() ------------------------------------------------- @staticmethod def _stationary_position(obj): 'Function - designed to be called from a taks queue.' print(" - running routine 'stationary_position'.") # Get a dictionary of all the variables # for this routine. var =Autopilot.variables["_stationary_position"] # _. if obj.status == STATUS.INIT: # Setup some variables. var["center"] = 0 else: var["center"] += 1 if var["center"] >=2: obj.status = STATUS.COMPLETED # stationary_position() ------------------------------------------------- # Autopilot ----------------------------------------------------------------- """ source file: clsTask.py """ from enum import Enum STATUS = Enum("STATUS", "WAITING, INIT, RUNNING, COMPLETED") class Task: 'Common base class for managing task definitions in a queue.' # optional class documentation string. #_queue =[] _empty_task_queues =True _task_list =[] # All task objects are stored here. _running =0 # Number of tasks running in the queue. class _MemSpace: # Container for the linked routine and vars. pass # _MemSpace ------------------------------------------------------------- def __init__(self): self._queue =[] # All task definitions are stored here. self._add_task_list(self) # Adds the new task object to the list. # __init__() ------------------------------------------------------------ @classmethod def _add_task_list(cls, task_list): 'Class method - registers a new Task object with the class.' cls._task_list.append(task_list) # _add_task_list() ------------------------------------------------------- @classmethod def _reset_task_list_status(cls): 'Class method - resets the flag set when all queues are empty.' cls._empty_task_queues =False # _reset_task_list_status() --------------------------------------------- def add(self, task_definition, status=STATUS.WAITING): 'Function - adds a task definition to the queue.' task_definition.status =status self._queue.append(task_definition) self._reset_task_list_status() # add() ----------------------------------------------------------------- @staticmethod def definition(): 'Function - returns a new task definition object.' obj =Task._MemSpace() obj.status =STATUS.WAITING return obj # definition() ---------------------------------------------------------- def empty(self): 'Function to check if the queue is empty.' return len(self._queue) == 0 # empty() --------------------------------------------------------------- @classmethod def empty_task_queues(cls): 'Function - checks if all the queues are empty.' return cls._empty_task_queues # empty_task_queues() ---------------------------------------------------- @staticmethod def print_queue_info(task): ' Function - prints the name and status of items in a queue.' print(" | ------------------------------------------------- |") print(" | Task Queue Information |") print(" | ------------------------------------------------- |") for q in task._queue: print(" | ", q.name, " | ", q.status) print(" | ------------------------------------------------- |") print() # print_queue_info() -------------------------------------------------------- def process_task(self): 'Function - runs the routine described in the task definition.' if self.status in (STATUS.INIT, STATUS.RUNNING): self.task_definition.linked_routine(self) # process_task() --------------------------------------------------------- @classmethod def process_task_list(cls): 'Class method - processes the queues in all the tasks.' # process all the task processes in the list. task_queues_empty =True for tl in cls._task_list: cls.process_task_queue(tl) if not tl.empty(): # There's at least one task # remaining in a queue. task_queues_empty =False cls._empty_task_queues =task_queues_empty # process_task_list() --------------------------------------------------- def process_task_queue(self): 'Function - run task definitions listed in a queue.' print("\n\nStarting loop (task queue count:[{}])".format(len(self._queue))) self._running =0 new_queue = [] for q in self._queue: if q.status in (STATUS.INIT, STATUS.RUNNING): q.linked_routine(q) if q.status ==STATUS.INIT: # Only permit an active task to run # once as with the status of [STATUS.INIT]. q.status =STATUS.RUNNING print("[process_task_queue] Upgraded status to 'Running'.") if q.status in (STATUS.INIT, STATUS.RUNNING): # The task is still running. self._running += 1 #print_queue_info(self) # Debug information. Task.print_queue_info(self) # Debug information. if q.status != STATUS.COMPLETED: new_queue.append(q) self._queue = new_queue if self._running == 0: if len(self._queue) > 0: # Activate the next available task. for t in self._queue: if t.status ==STATUS.WAITING: t.status =STATUS.INIT print("Forced another task into action.") break # process_task_queue() ----------------------------------------------------- @classmethod def running(cls): 'Class method - returns the number of active tasks in a queue.' return cls._running # running() ------------------------------------------------------------- # Task ---------------------------------------------------------------------- |