mirror of
https://github.com/bendtherules/pyFun.git
synced 2026-08-18 13:52:46 +00:00
summer update
This commit is contained in:
+539
@@ -0,0 +1,539 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user