Files
pyFun/base_old.py
T
2014-07-07 18:18:56 -07:00

540 lines
20 KiB
Python

# only for python 2.x
# VERY IMP: Use weakrefs instead of list for fun_Game.list_class,\
# fun_Class.list_instance and fun_Class.list_update_func
from uniquelist import uniquelist # imports uniquelist
import pygame
from pygame.locals import * # imports the constants
import math
from utils import register_class, meta_accessor_class, register_event, selfie, KW_EVENTS_IB, sort_events, append_user_events, fuzzy_match_event_name, KW_EVENTS_ALL, rect_to_pygame_rect
# import utils
from functools import partial
from sprite_types import Circle, Rect
import logging
from copy import deepcopy
from math_utils import atan2_inv
logging.basicConfig(level=logging.DEBUG)
IS_TEST_MODE = True
# Mouse constants
B_LEFT_CLICK = 1
B_RIGHT_CLICK = 3
B_MIDDLE_CLICK = 2
B_SCROLLUP = 4
B_SCROLLDOWN = 5
# Direction constants
TOP = 1
LEFT = 2
BOTTOM = 3
RIGHT = 4
class fun_Game(object):
list_class = uniquelist()
list_event = [] # event_list of this step
list_event_old = [] # event_list of the previous step
# A sequence of boolean representing the state of every key
list_state_all_buttons = []
# A sequence of boolean representing the state of every key of previous
# step
list_state_all_buttons_old = []
def __init__(self, width, height, fps=30):
pygame.init()
self.fps = fps
self.clock = pygame.time.Clock()
self.width = width
self.height = height
self.test_init()
@property
def w(self):
return self.width
@w.setter
def w(self, val):
self.width = val
@property
def h(self):
return self.height
@h.setter
def h(self, val):
self.height = val
# @classmethod
# def _register_class_(cls, class_to_register):
# ''' Registers class_to_register to its list_class '''
# cls.list_class.append(class_to_register)
@classmethod
def register_class(cls, cls_to_register):
cls.list_class.append(cls_to_register)
@classmethod
def update_all_class(cls):
for tmp_cls in cls.list_class:
tmp_cls.update_instances(cls.list_event)
@classmethod
def process_event_list(cls, list_event_):
append_user_events(list_event_)
cls.list_event.sort(sort_events)
@classmethod
def _cache_event_list(cls):
''' Cache event_list in every step.
All events in fun_Game.list_event have a "type" property which are the usual constants. '''
cls.list_event_old = cls.list_event # move current list to old list
cls.list_state_all_buttons_old = cls.list_state_all_buttons
cls.list_event = pygame.event.get()
cls.process_event_list(cls.list_event)
# Returns a sequence of boolean representing the state of every key.
# Use key constant values for indexing.
cls.list_state_all_buttons = pygame.key.get_pressed()
# print cls.list_event # debug message
# control access to list_event
# with getter, do something crazy for filtering
# or a filtering function
@classmethod
def for_all_instance(cls, func_to_run, flatten):
''' Note: flatten can be "level-instance" or "level-class" or False'''
return_list = []
for tmp_cls in cls.list_class:
return_item = tmp_cls.for_all_instance(
func_to_run, flatten=flatten)
if flatten == "level-instance" or flatten == "level-class":
return_list.extend(return_item)
else:
return_list.append(return_item)
return return_list
@classmethod
def for_all_class(cls, func_to_run, flatten):
''' Note: flatten can be True or False'''
return_list = []
for tmp_class in cls.list_class:
return_item = func_to_run(tmp_class)
if flatten:
return_list.extend(return_item)
else:
return_list.append(return_item)
return return_list
@staticmethod
def collect_self_clear_rect(self):
_list_clear_rect = []
_list_rendered_sprite = []
# perf hog: search for better way to remove list items iteratively
for tmp_old_sprite in self.list_rendered_sprite:
if (not tmp_old_sprite.get("still_valid")):
# returns bbox of all list_rendered_sprite that are not still_valid, after removing them from that list
# so, list now contains only of the older sprites still_valid
tmp_clear_rect_bbox = tmp_old_sprite["bbox"]
_list_clear_rect.append(tmp_clear_rect_bbox)
else:
_list_rendered_sprite.append(tmp_old_sprite)
if ("still_valid" in tmp_old_sprite):
tmp_old_sprite.pop("still_valid")
# in next step :
# _list_rendered_sprite.append(self.list_dirty_sprite)
# self.list_dirty_sprite=[]
self.list_rendered_sprite = _list_rendered_sprite
return _list_clear_rect
@staticmethod
def collect_self_rendered_sprite(self):
return self.list_rendered_sprite
@staticmethod
def collect_self_dirty_sprite(self):
return self.list_dirty_sprite
@staticmethod
def move_dirty_to_rendered(self):
self.list_rendered_sprite.extend(self.list_dirty_sprite)
self.list_dirty_sprite = []
return None
def blit_all_class(self):
# step 1: collect all clear rects
# i.e. remove last_rendered_rects which are not still_valid and return them
# also move to_be_rendered_sprites into that list and clear dirty list
list_clear_rect = self.for_all_instance(
self.collect_self_clear_rect, flatten="level-instance")
list_rendered_sprite = self.for_all_instance(
self.collect_self_rendered_sprite, flatten="level-instance")
list_dirty_sprite = self.for_all_instance(
self.collect_self_dirty_sprite, flatten="level-instance")
# step 2: check if any clear_rect intersects with last_rendered_rects,
# return a list of dirty overlay for those last_rendered_rects.
# also if it is contained (totally within), mark clear_rect for removal
list_dirty_overlay_sprite = []
list_cancelled_clear_rect = []
for tmp_old_sprite in list_rendered_sprite:
for tmp_clear_rect in list_clear_rect:
tmp_intersect_rect = tmp_clear_rect.clip(
tmp_old_sprite["bbox"])
if tmp_intersect_rect.w > 0 and tmp_intersect_rect.h > 0:
# make blit funcs capable of taking a clip rect, will be appplied before blitting
## print("explicit blit")
copy_old_sprite = deepcopy(tmp_old_sprite)
copy_old_sprite["clip_rect"] = tmp_intersect_rect
list_dirty_overlay_sprite.append(copy_old_sprite)
if tmp_old_sprite["bbox"].contains(tmp_clear_rect):
# Cancel clear rect
list_cancelled_clear_rect.append(tmp_clear_rect)
else:
# No explicit blit
pass
# step 3: for every clear_rect not marked for removal, do the
# clear_rects.
# print "Clear_rects= ",len(list_clear_rect)-len(list_cancelled_clear_rect)
i = 0
for tmp_clear_rect in list_clear_rect:
# convert to pygame rect
if not (tmp_clear_rect in list_cancelled_clear_rect):
self.screen.fill(pygame.Color(0, 0, 0, 1), tmp_clear_rect)
i += 1
print "i=", i
# step 4: do blit for all dirty_sprite and dirty_overlay
for tmp_dirty_sprite in list_dirty_sprite:
tmp_dirty_sprite["sprite"].blit(self.screen)
# print "Dirty_overlays= ",len(list_dirty_overlay_sprite)
# print list_dirty_overlay_sprite
for tmp_overlay_sprite in list_dirty_overlay_sprite:
new_overlay_sprite = tmp_overlay_sprite[
"sprite"].__class__(**tmp_overlay_sprite["config"])
new_overlay_sprite.blit(
self.screen, clip_rect=tmp_overlay_sprite["clip_rect"])
# step 5: update display
list_display_update = [tmp_dirty_sprite["bbox"]
for tmp_dirty_sprite in list_dirty_sprite] + list_clear_rect
pygame.display.update(list_display_update)
# if not (list_dirty_sprite[0]["sprite"].bbox in list_display_update):
# print("problem")
# step 6: cleaning jobs
self.for_all_instance(self.move_dirty_to_rendered, flatten=False)
# pygame.display.flip()
def test_init(self):
''' Empty function to be overloaded for extra init on test mode. '''
pass
def test_update(self):
''' Empty function to be overloaded for extra draw on test mode. '''
pass
def run(self):
self.screen = pygame.display.set_mode([self.width, self.height])
frame_no = 0
while True:
# Todo: Always exit on event_close : make it default behavior,
# although override-able
frame_no += 1
# print frame_no
self._cache_event_list()
self.update_all_class()
self.blit_all_class()
self.test_update()
# VERY IMPORTANT: Do a spare first step so that every variable / list gets initialized well.
# Start instant creation and execution from second step.
# IMPORTANT: First do all fun_Game related stuff before dealing
# with game objects.
self.clock.tick(self.fps)
# Todo - Add crazy functions to allow other objects and instances finding
# like GM
# Todo - Easy way to take screenshot and save it as pic
# Todo: Game testing module. Lets you compare screenshots and compare values with pixelarray or surface.get_at
# Also allows accessing different config values.
# class fun_Game ends here
# print(fun_Game)
# global register_event cached,as it will also be used as classmethod
# should use register_event=_register_event at end of all class defn
meta_fun_Class = meta_accessor_class
@register_class(fun_Game)
class fun_Class(object):
# think about this: priority_order and depth
# by default, depends on "when class was defined"
# cls.priority_order = cls.depth = len(fun_Game.list_class)
__metaclass__ = meta_fun_Class
# Below lines moved to meta_fun_Class
# list_instance = uniquelist()
# list_check_func = uniquelist()
# dict_action_func -> dict of action funcs, key=event_numb like KEYDOWN
# dict_action_func = {}
sprite_defaults = {
"type": Rect,
"params": {
"size": (40, 40),
"color": None
}
}
def __init__(self, x=None, y=None):
# use sprite as a general term, img->one kind of sprite, rect, circle
self.x_start = x
self.y_start = y
self.x = x
self.y = y
self.x_prev = x
self.y_prev = y
self.vel_x, self.vel_y, self.accln_x, self.accln_y = 0, 0, 0, 0
if (self.sprite_defaults and self.sprite_defaults.get("type")):
if (x is None) or (y is None):
raise AttributeError(
"fun_Class with sprite_defaults must have x,y")
self.sprite = self.sprite_defaults.get("type").from_container_obj(
container_obj=self, **self.sprite_defaults["params"])
self.list_dirty_sprite = []
# list_rendered_sprite => sprites that are effectively on the
# screen now
self.list_rendered_sprite = []
else:
self.sprite = None
self.list_instance.append(self)
# think about self.action_create()
@property
def vel(self):
return math.sqrt(self.vel_x ** 2 + self.vel_y ** 2)
@vel.setter
def vel(self, val):
self.vel_x = val * math.cos(math.radians(self.vel_direction))
self.vel_y = val * math.sin(math.radians(self.vel_direction))
@property
def vel_direction(self):
return atan2_inv(self.vel_y, self.vel_x)
@vel_direction.setter
def vel_direction(self, val):
self.vel_x = self.vel * math.cos(math.radians(val))
self.vel_y = self.vel * math.sin(math.radians(val))
@property
def accln(self):
return math.sqrt(self.accln_x ** 2 + self.accln_y ** 2)
@accln.setter
def accln(self, val):
self.accln_x = val * math.cos(math.radians(self.accln_direction))
self.accln_y = val * math.sin(math.radians(self.accln_direction))
@property
def accln_direction(self):
return atan2_inv(self.accln_y, self.accln_x)
@accln_direction.setter
def accln_direction(self, val):
self.accln_x = self.accln * math.cos(math.radians(val))
self.accln_y = self.accln * math.sin(math.radians(val))
@classmethod
def update_instances(cls, list_event):
'''calls update_self for all instances.'''
for tmp_instance in cls.list_instance:
tmp_instance.update_self(list_event)
def update_self(self, list_event):
# print list_event
for each_ev in list_event:
# print self.dict_action_func
if each_ev.type in self.dict_action_func:
for each_action in self.dict_action_func[each_ev.type]:
# similar as self.action_step()
each_action(self, each_ev)
@classmethod
def _check_event(cls, ev_name_or_type, new_or_old):
''' ev_name_or_type => name_or_type(numb)_of_event
new_or_old => current_event_list or old_event_list '''
matched_events = []
if new_or_old == "new":
list_to_check = cls.game_class.list_event
elif new_or_old == "old":
list_to_check = cls.game_class.list_event_old
else:
raise AttributeError("new_or_old must be 'new' or 'old'.")
if type(ev_name_or_type) is int:
ev_type = ev_name_or_type
elif type(ev_name_or_type) is str:
ev_name = fuzzy_match_event_name(ev_name_or_type, "event")
ev_type = KW_EVENTS_ALL[ev_name]
for ev in list_to_check:
if ev.type == ev_type:
matched_events.append(ev)
return matched_events
@classmethod
def check_event(cls, ev_name_or_type):
return cls._check_event(ev_name_or_type, "new")
@classmethod
def check_event_old(cls, ev_name_or_type):
return cls._check_event(ev_name_or_type, "old")
@classmethod
def register_event(cls, event_name=None, func_to_register=None):
global register_event
register_event(event_name, func_to_register, cls)
@classmethod
def for_all_instance(cls, func_to_run, flatten):
return_list = []
for tmp_instance in cls.list_instance:
return_item = func_to_run(tmp_instance)
if flatten == "level-instance":
return_list.extend(return_item)
else:
return_list.append(return_item)
return return_list
def collide(self, *args, **kwargs):
return self.sprite.collide(*args, **kwargs)
# always use selfie,not selfie_depreceated most compatible
# make this classmethod,selfie-ridden register_events as test
# @register_event()
def action_end_step_default(self, ev):
if not(self.x is None) and not(self.y is None):
# Set x_prev, y_prev
self.x_prev = self.x
self.y_prev = self.y
# Accelerate velocity
self.vel_x += self.accln_x
self.vel_y += self.accln_y
# Move x and y
self.x += self.vel_x
self.y += self.vel_y
def action_begin_step(self, ev):
# print "Calling begin_step"
pass
def action_JOY_BUTTON_UP(self, ev):
print "calling JOY_BUTTON_UP"
def action_key_down(self, ev):
print "Calling key_down"
def action_end_step(self, ev):
# print "Calling end step"
pass
def action_draw_default(self, ev):
# print "Drawing"
self.sprite.draw()
# end of fun_Class
# Other classes
# functions outside all classes
# list_to_get is attached to a constant list here
def get_event_list(event_types=None, further_check_variable_name=None, further_check_value=None, list_to_get=None):
''' Get the required events from the event_list.
get_event_list([event_types],[further_check_variable_name],further_check_value=None,list_to_get=fun_Game.list_event):
event_types (optional): event_name|list[event_name]
get_event_list() -> returns whole event_list(to be precise, list_to_get)
get_event_list(events) -> returns event_list(to be precise, list_to_get) filtered to contain only "events"
get_event_list([events],further_check_variable_name="var_name",further_check_value=value) ->
returns sub-list containing those elements of get_event_list([events]) for which var_name=value
Potential candidates for list_to_get ->
fun_Game.list_event, fun_Game.list_event_old, fun_Game.list_state_all_buttons, fun_Game.list_state_all_buttons_old'''
if list_to_get is None:
list_to_get = fun_Game.list_event_old
if further_check_variable_name:
further_check_enabled = True
else:
further_check_enabled = False
if event_types:
if not hasattr(event_types, "__iter__"):
# if not iterable, make a list out of it
event_types = [event_types]
def func_filter():
list_return = []
if not event_types:
return list_to_get
# all events in fun_Game.list_event have a "type" property
for temp_1 in list_to_get:
for temp_2 in event_types:
if temp_1.type == temp_2:
list_return.append(temp_1)
return list_return
# returns part of temp_list which passes further_check
def further_check_filter(temp_list):
list_return = []
if further_check_enabled:
for temp in temp_list:
# may raise error if further_check_variable_name is not present
# for all members of the list
if getattr(temp, further_check_variable_name) == further_check_value:
list_return.append(temp)
else:
list_return = temp_list
return list_return
# returns elements common in fun_Game.list_events and events which are
# accepted after further_check
return further_check_filter(func_filter())
# end of get_event_list()
# Todo: see below
# Collision helper event_collision funcs (Done, for circle)
# Allow collision with "all"
# More collision func like GM
# More extended draw funcs
# Make img sprite
# Use rects retuned from blit to display.update
#
# 1. (Think) Allow rect-like and circle-like constructs wherever rect and circle are expected.
# 2. Do all sort of type-checking.
# 3. Use sprites and groups in pygame as containers
# Imp: Allow debugging with event handlers and others with @debug
# Design tips I will use later
# use weakrefs as much as possible
# IMP: always derive classes from object. Else, in python 2.x, they become old-style class which sucks.
# use tuples instead of lists wherver possible because they are usually faster to create
# learn about generators
# named tuples, arrays are better. use more of them. named tuples can be accessed.
# refactor code into proper modules as-well-as use better named functions and variables
# use enumerate() for getting the index and value of elements while looping through lists. This mistake done atleast 1 time.
# use xrange instead of range. It produces one at a time. range is replaced by xrange in python 3.x
# use reversed() for backward loops, sorted() for sorted order.
# instead of zip(), use izip().
# use key instead of comparator
# use iter(iter_type,sentinel_value) instead of for loops
# partial() makes a function with more number of argument to less number of argument
# use for/else instead of exit based on flags
# look at dict.iteritems() and dict.setdefaults
# learn defaultdicts and collections module