class TimerCounter(Customer, Subject): def __init__(self, waiter, end, font_size, pos, color): self.end = 0 self.font_size = font_size self.pos = pos self.color = color self.time = end Customer.__init__(self, waiter) self.timer = None Subject.__init__(self) subject_unit = SubjectUnit() self.add_subject_unit(subject_unit, 'time_up') def run(self): font = pygame.font.Font("resources/font.ttf", self.font_size) def work(): drawable_object = Drawable(font.render(str(self.time), True, self.color), self.pos, DRAWABLE_INDEX.TIMER_COUNTER) self.register_waiter(CUSTOMER_KEY.TIMER_COUNTER, drawable_object) drawable_object = Drawable(font.render('Time remaining:', True, (255, 0, 0)), (self.pos[0] - 280, self.pos[1]), DRAWABLE_INDEX.TIMER_COUNTER) self.register_waiter(CUSTOMER_KEY.TIMER_COUNTER+'sub', drawable_object) self.time -= 1 if self.time < self.end + 1: self.set_change('time_up', 'time_up') self.timer = Timer(1, work) self.timer.start() def close(self): if self.timer is not None: self.timer.close()
class Head(Customer): def __init__(self, name, waiter, stick_time): Customer.__init__(self, waiter) self.name = name self.appear_index = 0 self.die_index = 0 self.disappear_index = 0 self.stand_index = 0 self.appear_timer = None self.die_timer = None self.disappear_timer = None self.stand_timer = None self.main_timer = None self.alive = True self.showing = False self.size = (40, 40) self.rect_bound = pygame.Rect(0, 0, self.size[0], self.size[1]) appear_avatars = Factory.get_avatars('head_appear_avatars') if appear_avatars is None: raise BaseException('Can not load avatar for Head') self.appear_drawable_avatars = [] for avatar in appear_avatars: pos = (0, 0) drawable_item = Drawable(avatar, pos, DRAWABLE_INDEX.HEAD) self.appear_drawable_avatars.append(drawable_item) die_avatars = Factory.get_avatars('head_die_avatars') if die_avatars is None: raise BaseException('Can not load avatar for Head') self.die_drawable_avatars = [] for avatar in die_avatars: pos = (0, 0) drawable_item = Drawable(avatar, pos, DRAWABLE_INDEX.HEAD) self.die_drawable_avatars.append(drawable_item) disappear_avatars = Factory.get_avatars('head_disappear_avatars') if disappear_avatars is None: raise BaseException('Can not load avatar for Head') self.disappear_drawable_avatars = [] for avatar in disappear_avatars: pos = (0, 0) drawable_item = Drawable(avatar, pos, DRAWABLE_INDEX.HEAD) self.disappear_drawable_avatars.append(drawable_item) stand_avatars = Factory.get_avatars('head_stand_avatars') if disappear_avatars is None: raise BaseException('Can not load avatar for Head') self.stand_drawable_avatars = [] for avatar in stand_avatars: pos = (0, 0) drawable_item = Drawable(avatar, pos, DRAWABLE_INDEX.HEAD) self.stand_drawable_avatars.append(drawable_item) self.stick_time = stick_time self.sound_head_appear = Factory.get_sound('head_appear') self.sound_head_disappear = Factory.get_sound('head_disappear') self.sound_head_hit = Factory.get_sound('head_hit') return def appear(self, pos): self.showing = True self.alive = True self.rect_bound = pygame.Rect(pos[0] + 15, pos[1] + 15, self.size[0], self.size[1]) # Update position to draw on screen for drawable_avatar in self.appear_drawable_avatars: drawable_avatar.pos = pos # Update position to draw on screen for drawable_avatar in self.disappear_drawable_avatars: drawable_avatar.pos = pos # Update position to draw on screen for drawable_avatar in self.die_drawable_avatars: drawable_avatar.pos = pos # Update position to draw on screen for drawable_avatar in self.stand_drawable_avatars: drawable_avatar.pos = pos drawable_object = self.appear_drawable_avatars[self.appear_index] # Start drawing self.register_waiter(str(drawable_object.index) + CUSTOMER_KEY.HEAD + self.name, drawable_object) def do_animation(): self.appear_index += 1 if self.appear_index > (self.appear_drawable_avatars.__len__() - 1): self.appear_index = 0 self.appear_timer.stop() # After appearing animation is standing animation self.stand() return drawable_object = self.appear_drawable_avatars[self.appear_index] key = str(drawable_object.index) + CUSTOMER_KEY.HEAD + self.name # Insert index as prefix keyword to sort self.register_waiter(key, drawable_object) # blank = Factory.get_avatars('blank') # drawable_object1 = Drawable(blank[0], (self.get_rect_bound()[0], self.get_rect_bound()[1]), 2) # self.register_waiter('blank1', drawable_object1) # drawable_object2 = Drawable(blank[0], (self.get_rect_bound()[0] + self.get_rect_bound()[2], # self.get_rect_bound()[1] + self.get_rect_bound()[3]), 2) # self.register_waiter('blank2', drawable_object2) if self.appear_timer is None: self.appear_timer = Timer(self.stick_time, do_animation) self.appear_timer.start() self.sound_head_appear.play() def disappear(self): self.alive = False drawable_object = self.disappear_drawable_avatars[self.disappear_index] # Start drawing self.register_waiter(str(drawable_object.index) + CUSTOMER_KEY.HEAD + self.name, drawable_object) def do_animation(): self.disappear_index += 1 if self.disappear_index > (self.disappear_drawable_avatars.__len__() - 1): # Disappear actually from screen key = str(self.disappear_drawable_avatars[0].index) + CUSTOMER_KEY.HEAD + self.name self.unregister_waiter(key) self.disappear_index = 0 self.showing = False self.disappear_timer.stop() # Close all timer self.close() return drawable_object = self.disappear_drawable_avatars[self.disappear_index] key = str(drawable_object.index) + CUSTOMER_KEY.HEAD + self.name # Insert index as prefix keyword to sort self.register_waiter(key, drawable_object) if self.disappear_timer is None: self.disappear_timer = Timer(self.stick_time, do_animation) self.disappear_timer.start() self.sound_head_disappear.play() def show(self, pos, duration): self.appear(pos) start = datetime.datetime.now() def work(): end = datetime.datetime.now() if (end - start).total_seconds() > duration: if self.stand_timer is not None: self.stand_timer.stop() if self.main_timer is not None: self.main_timer.stop() self.disappear() self.main_timer = Timer(0.1, work) self.main_timer.start() def stand(self): drawable_object = self.stand_drawable_avatars[self.stand_index] # Start drawing self.register_waiter(str(drawable_object.index) + CUSTOMER_KEY.HEAD + self.name, drawable_object) def do_animation(): self.stand_index += 1 if self.stand_index > (self.stand_drawable_avatars.__len__() - 1): self.stand_index = 0 return drawable_object = self.stand_drawable_avatars[self.stand_index] key = str(drawable_object.index) + CUSTOMER_KEY.HEAD + self.name # Insert index as prefix keyword to sort self.register_waiter(key, drawable_object) if self.stand_timer is None: self.stand_timer = Timer(self.stick_time, do_animation) self.stand_timer.start() def die(self): self.alive = False drawable_object = self.die_drawable_avatars[self.die_index] # Start drawing self.register_waiter(str(drawable_object.index) + CUSTOMER_KEY.HEAD + self.name, drawable_object) def do_animation(): self.die_index += 1 if self.die_index > (self.die_drawable_avatars.__len__() - 1): self.die_index = 0 self.die_timer.stop() self.disappear() return drawable_object = self.die_drawable_avatars[self.die_index] key = str(drawable_object.index) + CUSTOMER_KEY.HEAD + self.name # Insert index as prefix keyword to sort self.register_waiter(key, drawable_object) if self.die_timer is None: self.die_timer = Timer(0.1, do_animation) if self.appear_timer is not None: self.appear_timer.stop() if self.stand_timer is not None: self.stand_timer.stop() if self.main_timer is not None: self.main_timer.stop() self.die_timer.start() self.sound_head_hit.play() def get_rect_bound(self): return self.rect_bound def set_stick_time(self, stick_time): self.stick_time = stick_time def close(self): if self.appear_timer is not None: self.appear_timer.close() if self.die_timer is not None: self.die_timer.close() if self.disappear_timer is not None: self.disappear_timer.close() if self.stand_timer is not None: self.stand_timer.close() if self.main_timer is not None: self.main_timer.close()
class Hammer(Customer, Observer): """ This class managers hammer of player, detail: - Animation when hammer - Sound effect when hammer - Avatar of hammer - Replace cursor with avatar """ def __init__(self, waiter, subject): """ Constructor :param waiter: waiter for implement hammer action :return: None """ # Constructor of base class Customer.__init__(self, waiter) Observer.__init__(self) self.register(subject, 'player_motion') avatars = Factory.get_avatars('hammer_avatars') if avatars is None: raise BaseException('Can not load avatar for hammer') self.drawable_avatars = [] for avatar in avatars: drawable_item = Drawable(avatar, (0, 0), DRAWABLE_INDEX.PLAYER) self.drawable_avatars.append(drawable_item) # Default is the first one self.drawable_avatar = self.drawable_avatars[0] self.sound_hit = Factory.get_sound('hammer_hit') if self.sound_hit is None: raise BaseException('Can not load "hit" sound effect of hammer') # Used to step counter, change image when hammer self.index = 0 # Used to prevent hammer when hammering self.hammering = False self.timer = None self.pos = (0, 0) self.size = (10, 15) # Height, Width self.distance = (150, 150) def hit(self): """ This function play a sound in new thread, so it can be call 2 times concurrent :return: """ if not self.hammering: self.sound_hit.play() # Hammer action effect # TODO Need to improve effect def work(): self.hammering = True self.index += 1 if self.index > 8: self.index = 0 self.hammering = False self.timer.stop() avatar_index = 4 - abs(self.index - 4) self.drawable_avatar = self.drawable_avatars[avatar_index] self.drawable_avatar.pos = self.pos # Insert index as prefix keyword to sort key = str(self.drawable_avatar.index) + CUSTOMER_KEY.HAMMER self.register_waiter(key, self.drawable_avatar) if self.timer is None: self.timer = Timer(0.01, work) if not self.hammering: self.timer.start() rect = pygame.Rect(self.pos[0] + 10, self.pos[1] + 130, 30, 30) return rect def get_avatar(self): return self.drawable_avatar.bitmap def update(self, type_key, data): """ Override function of base class: Observer :param event: :return: """ if type_key == 'player_motion' or type_key == 'player_hammer': self.pos = (data[0] - self.distance[0], data[1] - self.distance[1]) def close(self): if self.timer is not None: self.timer.close()
class MainController(Observer, Waiter): """Control common stage of game, include: - Init game - Load background - Load model This class has role: - Waiter: update screen - Observer: listen and resolve special event, such as: QUIT """ def __init__(self, event_controller, env): # Save event_controller to use later self.event_controller = event_controller self.env = env #set some value for head self.number_of_enemy = NUM_SPRITES.NUMBER_OF_ENEMY self.time_to_create_new = DURATION.TIME_CREATE_NEW self.max_of_current_enemy = NUM_SPRITES.MAX_OF_CURRENT_ENEMY self.totalCreatedEnemy = 0 # Constructor of base class Observer.__init__(self) # Register to receive some special events self.register(event_controller, 'special') Waiter.__init__(self) # Attribute declare self.quit_game = False self.player = None #self.drawable_components = [] self.heads = [] self.head_timer = None # Init pygame pygame.init() pygame.mixer.init() self.screen = pygame.display.set_mode(self.env.screen_size) pygame.mouse.set_visible(False) # Hide default mouse cursor def update(self, type_key, event): """ Override function of base class: Observer This function is called when expected event is happened :param event: event is happened :return: None """ if type_key == 'special': if event.type == pygame.QUIT: self.close() self.quit_game = True elif type_key == 'player_hammer': rect_bound_hammer = event head = self.__check_collision(rect_bound_hammer) if head: self.player.increase_score() head.die() else: self.player.decrease_score() def init_game(self): """ Init logically game :return: None """ self.screen.fill((255, 255, 255)) background = Factory.get_background() if background is not None: drawable_object = Drawable(background, (0, 0), DRAWABLE_INDEX.BACKGROUND) key = str(drawable_object.index) + CUSTOMER_KEY.BACKGROUND # Insert index as prefix keyword to sort self.register_waiter(key, drawable_object) else: raise 'Can not load background image' self.player = Player(self.event_controller, self, self.screen) self.register(self.player, 'player_hammer') # Init list of heads head = Head('1', self) self.heads.append(head) head = Head('2', self) self.heads.append(head) head = Head('3', self) self.heads.append(head) #Draft self.original_head_pos = (20, 200) self.pos_index = 0 self.id = 0 # Define work of timer: choose random a head and show it def work(): if self.pos_index > 7: self.pos_index = 0 i = 0 self.id += 1 self.pos_index += 1 self.original_head_pos = HolePosition.POS[self.pos_index] head = Head(str(self.id), self) head.show(self.original_head_pos,3) self.head_timer = Timer(3, work) self.head_timer.start() def run(self): """ Implement from Waiter. This function clear and then draw whole screen. :return: None """ self.screen.fill((255, 255, 255)) for key in sorted(self.objects.keys()): # TODO: Need to improve self.screen.blit(self.objects[key].bitmap, self.objects[key].pos) def __check_collision(self, rect_bound_hammer): """ Check collision between player and head :return: """ for head in self.heads: if head.alive and head.showing: if rect_bound_hammer.colliderect(head.get_rect_bound()): return head return None def close(self): self.player.close() for head in self.heads: head.close() if self.head_timer is not None: self.head_timer.stop() self.head_timer.close()
class MainController(Observer, Waiter): """Control common stage of game, include: - Init game - Load background - Load model This class has role: - Waiter: update screen - Observer: listen and resolve special event, such as: QUIT """ def __init__(self, event_controller, env): # Init pygame pygame.init() pygame.mixer.init() # Save event_controller to use later self.event_controller = event_controller self.env = env # Set some value for head self.number_of_enemy = NUM_SPRITES.NUMBER_OF_ENEMY self.time_to_create_new = DURATION.TIME_CREATE_NEW self.max_of_current_enemy = NUM_SPRITES.MAX_OF_CURRENT_ENEMY self.totalCreatedEnemy = 0 # Constructor of base class Observer.__init__(self) # Register to receive some special events self.register(event_controller, 'special') Waiter.__init__(self) # Attribute declare self.quit_game = False self.player = None self.heads = [] self.head_timer = None self.interval_section = 3 self.appear_delay = 3 self.stick_time = 0.12 self.is_first_blood = True self.sound_prepare4battle = Factory.get_sound('prepare4battle') if self.sound_prepare4battle is None: raise NotImplementedError self.sound_prepare4battle_playing = False self.sound_first_blood = Factory.get_sound('first_blood') if self.sound_first_blood is None: raise NotImplementedError self.sound_double_kill = Factory.get_sound('double_kill') if self.sound_double_kill is None: raise NotImplementedError self.sound_triple_kill = Factory.get_sound('triple_kill') if self.sound_triple_kill is None: raise NotImplementedError self.sound_ultra_kill = Factory.get_sound('ultra_kill') if self.sound_ultra_kill is None: raise NotImplementedError self.sound_rampage_kill = Factory.get_sound('rampage') if self.sound_rampage_kill is None: raise NotImplementedError self.screen = pygame.display.set_mode(self.env.screen_size) pygame.mouse.set_visible(False) # Hide default mouse cursor self.timer_counter = None self.stage = None self.id = 0 self.finish = False self.num_head_kill_in_section = 0 def update(self, type_key, event): """ Override function of base class: Observer This function is called when expected event is happened :param event: event is happened :return: None """ if type_key == 'special': if event.type == pygame.QUIT: self.close() self.quit_game = True elif type_key == 'player_hammer': rect_bound_hammer = event head = self.__check_collision(rect_bound_hammer) if head: self.player.increase_score() head.die() if self.is_first_blood: self.sound_first_blood.play() self.is_first_blood = False self.num_head_kill_in_section += 1 print self.num_head_kill_in_section if self.num_head_kill_in_section == 2: self.sound_double_kill.play() if self.num_head_kill_in_section == 3: self.sound_triple_kill.play() if self.num_head_kill_in_section == 4: self.sound_ultra_kill.play() if self.num_head_kill_in_section == 5: self.sound_rampage_kill.play() else: self.player.decrease_score() elif type_key == 'time_up': self.finish = True self.finish_game() def init_game(self): """ Init logically game :return: None """ start_time = time.time() self.screen.fill((255, 255, 255)) background = Factory.get_background() if background is not None: drawable_object = Drawable(background, (0, 20), DRAWABLE_INDEX.BACKGROUND) key = str(drawable_object.index) + CUSTOMER_KEY.BACKGROUND # Insert index as prefix keyword to sort self.register_waiter(key, drawable_object) else: raise 'Can not load background image' self.player = Player(self.event_controller, self) self.register(self.player, 'player_hammer') def start_game(self): self.id = 0 # Define work of timer: choose random a number of head and show them def work(): self.num_head_kill_in_section = 0 num_head = 7 #random.randint(1, 5) positions = range(8) random.shuffle(positions) self.interval_section = random.random() * 2 + 0.5 for i in range(num_head): self.interval_head = random.random() * 0.1 + 0.2 self.stick_time = random.random() * 0.05 + 0.05 self.appear_delay = random.random() * 1.5 if self.appear_delay < 6 * self.stick_time + 0.01: self.appear_delay = 6 * self.stick_time + 0.01 # Position of top left corner of bitmap original_head_pos = HolePosition.POS[positions[i]] head = Head(str(self.id), self, self.stick_time) # Actually position pos = (original_head_pos[0] - 30, original_head_pos[1] - 40) head.show(pos, self.appear_delay) self.heads.append(head) self.id += 1 time.sleep(self.interval_head) # Finish work() function self.head_timer = Timer(self.interval_section, work) self.head_timer.start() self.timer_counter = TimerCounter(self, 60, 40, (570, 490), (240, 5, 12)) self.register(self.timer_counter, 'time_up') self.timer_counter.run() font = pygame.font.Font("resources/font.ttf", 40) drawable_object = Drawable(font.render('Score: ', True, (255, 0, 0)), (15, 490), DRAWABLE_INDEX.TIMER_COUNTER) self.register_waiter('score_text', drawable_object) drawable_object = Drawable(font.render('0', True, (255, 0, 0)), (150, 490), DRAWABLE_INDEX.TIMER_COUNTER) self.register_waiter('score', drawable_object) def run(self): """ Implement from Waiter. This function clear and then draw whole screen. :return: None """ if not self.finish: self.screen.fill((0, 0, 0)) for key in sorted(self.objects.keys()): # TODO: Need to improve if key in self.objects.keys(): try: self.screen.blit(self.objects[key].bitmap, self.objects[key].pos) except KeyError: continue def __check_collision(self, rect_bound_hammer): """ Check collision between player and head :return: """ heads = [] for head in self.heads: if head.alive: heads.append(head) if rect_bound_hammer.colliderect(head.get_rect_bound()): return head # if head.showing: # heads.append(head) # self.heads = heads return None def close(self): self.player.close() if self.head_timer is not None: self.head_timer.stop() self.head_timer.close() for head in self.heads: head.close() if self.head_timer is not None: self.head_timer.close() if self.timer_counter is not None: self.timer_counter.close() def intro(self, clock): logo = pygame.image.load('resources/Logo.png') i = 0 while i < 58: self.screen.fill((0, 0, 0)) img = pygame.image.load('resources/intro/tmp-' + str(i) + '.gif') i += 1 self.screen.blit(img, (30, 150)) self.screen.blit(logo, (i * 2, 30)) self.screen.blit(logo, (480 - i * 2, 30)) Font = pygame.font.Font("resources/HorrorFont.ttf", 64) self.screen.blit(Font.render('PUNCH ZOMBIE ', True, (255, 0, 0)), (100, 100)) pygame.display.flip() clock.tick(10) def prepare(self): self.stage = 'prepare' def work(): if not self.sound_prepare4battle_playing: self.sound_prepare4battle.play() self.sound_prepare4battle_playing = True else: time.sleep(4) self.prepare_timer.stop() self.start_game() self.prepare_timer = Timer(0.001, work) self.prepare_timer.start() def finish_game(self): self.close() self.screen.fill((0, 0, 0)) end_background = pygame.image.load('resources/endgame.jpg') self.screen.blit(end_background, (20, 10)) font = pygame.font.Font("resources/HorrorFont.ttf", 64) self.screen.blit(font.render('Your score:', True, (255, 0, 0)), (130, 100)) self.screen.blit(font.render(str(self.player.score), True, (255, 0, 0)), (300, 200)) pygame.display.flip() time.sleep(10) self.quit_game = True