Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Design Strategy
#1
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.
    #   ...
Output:
TASK LIST INFORMATION ------------------------- name: Turn - status = INIT name: Stationary - status = WAITING ... Routine 'turn_craft_oaf' called. TASK LIST INFORMATION ------------------------- name: Stationary - status = INIT ... Routine 'stationary_position' called. TASK LIST INFORMATION ------------------------- name: Stationary - status = INIT ... Routine 'stationary_position' called. TASK LIST INFORMATION ------------------------- name: Stationary - status = INIT ... Routine 'stationary_position' called. Task 'stationary_position' has changed its status to COMPLETED. TASK LIST INFORMATION ------------------------- ... >>>
Reply
#2
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()
99 percent of computer problems exists between chair and keyboard.
Reply
#3
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.
Reply
#4
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()
99 percent of computer problems exists between chair and keyboard.
Reply
#5
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.
Reply
#6
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()
99 percent of computer problems exists between chair and keyboard.
Reply
#7
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.
Reply
#8
@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))
99 percent of computer problems exists between chair and keyboard.
Reply
#9
Thanks that's a great help. I'm gonna try to start using the @classmethod from now on.
Reply
#10
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 ----------------------------------------------------------------------
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020