Esempio n. 1
0
    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()
Esempio n. 2
0
    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()
Esempio n. 3
0
    def show(self, pos, duration):
        self.appear(pos)
        start = datetime.datetime.now()

        def work():
            end = datetime.datetime.now()
            if (end - start).total_seconds() > duration:
                self.disappear()
                self.main_timer.stop()
                self.stand_timer.stop()
        self.main_timer = Timer(0.1, work)
        self.main_timer.start()
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()
    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()
Esempio n. 6
0
    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 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):

        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 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()
Esempio n. 10
0
    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()
Esempio n. 11
0
    def appear(self, pos):
        self.showing = True
        self.rect_bound = pygame.Rect(pos[0], pos[1], 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)

        if self.appear_timer is None:
            self.appear_timer = Timer(0.15, do_animation)

        self.appear_timer.start()
Esempio n. 12
0
    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
Esempio n. 13
0
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 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()
Esempio n. 15
0
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):

        # 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