Example #1
0
class StateMachine(object):
    def __init__(self, sprite, tick):
        self.sprite = sprite
        self.states = {}
        self.active_state = None
        self.last_state = None
        self.timer = Timer(tick)
        self.timer.begin()


    def add_state(self, state):
        self.states[state.id] = state


    def run(self):
        new_state_id = None
        # ai event tick, interrupt event will trigger condition calculation at once
        if self.sprite.brain.interrupt or self.timer.exceed():
            self.timer.begin()
            new_state_id = self.active_state.check_conditions()
        
        if new_state_id is not None:
            self.set_state(new_state_id)

        self.sprite.brain.actions = self.active_state.send_actions()


    def set_state(self, new_state_id):
        if self.active_state is not None:
            self.last_state = self.active_state
            self.active_state.exit()

        self.active_state = self.states[new_state_id]
        self.active_state.enter(self.last_state)
Example #2
0
class SpriteDefence(State):
    def __init__(self, sprite, ai):
        super(SpriteDefence, self).__init__(cfg.SpriteState.DEFENCE)
        self.sprite = sprite
        self.ai = ai
        self.enter_timer = Timer()
        #self.action_to_do = cfg.EnemyAction.STAND


    def enter(self, last_state):
        self.enter_timer.begin(gauss(self.ai.DEFENCE_TIME_MU, self.ai.DEFENCE_TIME_SIGMA))
        sp = self.sprite
        sp.direction = cal_face_direct(sp.pos.as_tuple(), sp.brain.target.pos.as_tuple())
        #if sp.hp_status == cfg.HpStatus.DANGER and happen(self.ai.DEFENCE_BACKWARD_PROB):
        #    self.action_to_do = cfg.EnemyAction.BACKWARD
        #else:
        #    self.action_to_do = cfg.EnemyAction.STAND


    def send_actions(self):
        return (cfg.EnemyAction.STAND, )
        #return (self.action_to_do, )


    def check_conditions(self):
        sp = self.sprite
        if self.enter_timer.exceed():
            if happen(self.ai.DEFENCE_TO_OFFENCE_PROB) and sp.attacker.chance(sp.brain.target):
                return cfg.SpriteState.OFFENCE

            distance_to_target = sp.pos.get_distance_to(sp.brain.target.pos)
            if happen(self.ai.DEFENCE_TO_CHASE_PROB) and distance_to_target <= self.ai.CHASE_RANGE :
                return cfg.SpriteState.CHASE

            if distance_to_target > self.ai.CHASE_RANGE:
                sp.brain.target = None
                return cfg.SpriteState.STAY

            return cfg.SpriteState.DEFENCE

        else:
            sp.direction = cal_face_direct(sp.pos.as_tuple(), sp.brain.target.pos.as_tuple())


    def exit(self):
        self.enter_timer.clear()
Example #3
0
class SpriteOffence(State):
    def __init__(self, sprite, ai):
        super(SpriteOffence, self).__init__(cfg.SpriteState.OFFENCE)
        self.sprite = sprite
        self.ai = ai
        self.enter_timer = Timer()


    def enter(self, last_state):
        sp = self.sprite
        sp.brain.persistent = True
        sp.direction = cal_face_direct(sp.pos.as_tuple(), sp.brain.target.pos.as_tuple())
        self.enter_timer.begin(gauss(self.ai.OFFENCE_GO_DELAY_TIME_MU, self.ai.OFFENCE_GO_DELAY_TIME_SIGMA))


    def send_actions(self):
        if not self.enter_timer.exceed():
            # add delay time for attack
            return (cfg.EnemyAction.STAND, )

        sp = self.sprite
        return (cfg.EnemyAction.ATTACK, ) if sp.brain.persistent else (cfg.EnemyAction.STAND, )


    def check_conditions(self):
        sp = self.sprite
        if sp.brain.persistent:
            return 

        if sp.attacker.chance(sp.brain.target):
            return cfg.SpriteState.OFFENCE

        if happen(self.ai.OFFENCE_TO_CHASE_PROB):
            return cfg.SpriteState.CHASE

        return cfg.SpriteState.DEFENCE


    def exit(self):
        self.enter_timer.clear()
        self.sprite.brain.persistent = False
Example #4
0
class SpritePatrol(State):
    def __init__(self, sprite, ai):
        super(SpritePatrol, self).__init__(cfg.SpriteState.PATROL)
        self.sprite = sprite
        self.ai = ai
        self.enter_timer = Timer()
        self.view_sensor_timer = Timer(ai.VIEW_SENSOR_TICK)


    def choose_a_backside_direction(self, current_direction):
        total = cfg.Direction.TOTAL
        opposite_direction = (current_direction + 4) % total
        return choice([(opposite_direction - 1) % total, opposite_direction,
            (opposite_direction + 1) % total])


    def enter(self, last_state):
        self.enter_timer.begin(gauss(self.ai.WALK_TIME_MU, self.ai.WALK_TIME_SIGMA))
        self.sprite.direction = self.choose_a_backside_direction(self.sprite.direction)

    
    def send_actions(self):
        if self.view_sensor_timer.is_begin():
            if self.view_sensor_timer.exceed():
                self.view_sensor_timer.begin()
                return (cfg.EnemyAction.LOOKOUT, cfg.EnemyAction.WALK)
            else:
                return (cfg.EnemyAction.WALK, )
        else:
            self.view_sensor_timer.begin()
            return (cfg.EnemyAction.WALK, )


    def check_conditions(self):
        sp = self.sprite
        if sp.brain.target is not None:
            sp.set_emotion(cfg.SpriteEmotion.ALERT)
            if sp.attacker.chance(sp.brain.target):
                #print "patrol to attack"
                return cfg.SpriteState.OFFENCE

            if happen(self.ai.PATROL_TO_CHASE_PROB):
                #print "patrol to chase"
                return cfg.SpriteState.CHASE

            return cfg.SpriteState.DEFENCE

        if sp.brain.interrupt:
            sp.brain.interrupt = False
            return cfg.SpriteState.STAY

        if self.enter_timer.exceed():
            return cfg.SpriteState.STAY


    def exit(self):
        self.enter_timer.clear()
Example #5
0
class SpriteStay(State):
    def __init__(self, sprite, ai):
        super(SpriteStay, self).__init__(cfg.SpriteState.STAY)
        self.sprite = sprite
        self.ai = ai
        self.enter_timer = Timer()
        self.view_sensor_timer = Timer(ai.VIEW_SENSOR_TICK)


    def enter(self, last_state):
        self.enter_timer.begin(gauss(self.ai.STAY_TIME_MU, self.ai.STAY_TIME_SIGMA))
        # turn for a random direction if the last state is the same "stay"
        if last_state and last_state.id == cfg.SpriteState.STAY \
            and happen(self.ai.STAY_CHANGE_DIRECTION_PROB):
            self.sprite.direction = choice(cfg.Direction.ALL)   # a random direction from "all"

        if happen(self.ai.EMOTION_SILENT_PROB):
            self.sprite.set_emotion(cfg.SpriteEmotion.SILENT)


    def send_actions(self):
        if self.view_sensor_timer.is_begin():
            if self.view_sensor_timer.exceed():
                self.view_sensor_timer.begin()
                return (cfg.EnemyAction.LOOKOUT, cfg.EnemyAction.STAND)
            else:
                return (cfg.EnemyAction.STAND, )
        else:
            self.view_sensor_timer.begin()
            return (cfg.EnemyAction.STAND, )


    def check_conditions(self):
        sp = self.sprite
        if sp.brain.target is not None:
            sp.set_emotion(cfg.SpriteEmotion.ALERT)
            # discover a target
            if happen(self.ai.STAY_TO_OFFENCE_PROB) and sp.attacker.chance(sp.brain.target):
                #print "to attack"
                return cfg.SpriteState.OFFENCE

            if happen(self.ai.STAY_TO_CHASE_PROB):
                return cfg.SpriteState.CHASE

            return cfg.SpriteState.DEFENCE

        if self.enter_timer.exceed():
            if happen(self.ai.STAY_TO_PATROL_PROB):
                #print "stay to patrol"
                return cfg.SpriteState.PATROL
            else:
                return cfg.SpriteState.STAY


    def exit(self):
        self.enter_timer.clear()
Example #6
0
class GameDirector(object):
    def __init__(self, chapter, hero, enemy_list):
        self.chapter = chapter
        self.win_cond = sfg.Chapter.WIN_CONDITION.get(self.chapter, sfg.Chapter.WIN_CONDITION_NONE)
        self.hero = hero
        self.enemy_list = enemy_list
        if self.win_cond == sfg.Chapter.WIN_CONDITION_BOSS_DIE:
            self.boss = None
            for em in self.enemy_list:
                if hasattr(em.setting, "IS_BOSS"):
                    self.boss = em
                    break

        self.status = cfg.GameStatus.INIT
        self.win_panel = gen_panel(battle_images, 
            sfg.GameStatus.HERO_WIN_PANEL_IMAGE_KEY, sfg.GameStatus.HERO_WIN_PANEL_RECT)
        self.lose_panel = gen_panel(battle_images, 
            sfg.GameStatus.HERO_LOSE_PANEL_IMAGE_KEY, sfg.GameStatus.HERO_LOSE_PANEL_RECT)
        self.chapter_score_icon = gen_panel(battle_images, 
            sfg.GameStatus.CHAPTER_SCORE_ICON_IMAGE_KEY, 
            sfg.GameStatus.CHAPTER_SCORE_ICON_RECT, 
            sfg.GameStatus.CHAPTER_SCORE_ICON_SCALE_SIZE)
        self.bonus_icon = gen_panel(battle_images, 
            sfg.GameStatus.BONUS_ICON_IMAGE_KEY, sfg.GameStatus.BONUS_ICON_RECT)
        self.chapter_score_line = gen_panel(battle_images, 
            sfg.GameStatus.CHAPTER_SCORE_LINE_IMAGE_KEY, 
            sfg.GameStatus.CHAPTER_SCORE_LINE_RECT, 
            sfg.GameStatus.CHAPTER_SCORE_LINE_SCALE_SIZE)
        self.numbers1 = gen_numbers(battle_images, sfg.GameStatus.NUMBER_IMAGE_KEY1, 
            sfg.GameStatus.NUMBER_RECT1, sfg.GameStatus.NUMBER_SIZE1)
        self.begin_timer = Timer()
        self.hero_status = HeroStatus(hero)
        self.achievement = Achievement(hero, enemy_list)
        self.menu = Menu(sfg.Menu.PAUSE)
        self.mask = sfg.Screen.DEFAULT_SURFACE

        bg_box.play("%s%s" % (sfg.Music.CHAPTER_KEY_PREFIX, chapter))


    def update(self, passed_seconds):
        if self.status == cfg.GameStatus.IN_PROGRESS:
            if self.hero.hp_status == cfg.HpStatus.DIE:
                # hero is dead, game over
                self.status = cfg.GameStatus.HERO_LOSE
                return

            if self.win_cond != sfg.Chapter.WIN_CONDITION_NONE and len(self.enemy_list) == 0:
                # all enemies are gone, hero win
                self.status = cfg.GameStatus.HERO_WIN
                self.achievement.cal_chapter_score()
                return 

        elif self.status == cfg.GameStatus.HERO_WIN:
            self.achievement.chapter_score.update(passed_seconds)

        if self.win_cond == sfg.Chapter.WIN_CONDITION_BOSS_DIE \
            and self.boss.hp_status == cfg.HpStatus.DIE:
            # in an chapter that boss die you win, kill all enemy when boss die
            for em in self.enemy_list:
                if em.hp > 0:
                    em.hp = 0
                    em.hp_status = cfg.HpStatus.DIE

        for em in self.enemy_list:
            if em.hp_status == cfg.HpStatus.DIE:
                can_be_removed = em.animation.dead_tick()
                if can_be_removed:
                    em.hp_status = cfg.HpStatus.VANISH
                    # kill the sprite from sprite groups containing it, but not chaning its internal status
                    em.kill()

        # achievement calculation here
        self.achievement.update(passed_seconds)


    def draw_count_down_number(self, camera, timer, persist_time):
        left_time = persist_time - timer.passed_time()
        number_to_draw = self.numbers1[int(left_time)+1]
        camera.screen.blit(number_to_draw, sfg.GameStatus.BEGIN_NUMBER_BLIT_POS)


    def draw(self, camera):
        # hero status draw here
        self.hero_status.draw(camera)

        # achievement draw here
        self.achievement.draw(camera)

        if self.status == cfg.GameStatus.INIT:
            if not self.begin_timer.is_begin():
                self.begin_timer.begin(sfg.GameStatus.INIT_PERSIST_TIME)

            if self.begin_timer.exceed():
                # game begin, change the status
                self.status = cfg.GameStatus.IN_PROGRESS
            else:
                # count down for game begin, draw corresponding count-down numbers
                self.draw_count_down_number(camera, self.begin_timer, sfg.GameStatus.INIT_PERSIST_TIME)

        elif self.status in cfg.GameStatus.STATUS_WITH_MASK:
            self.mask.fill(sfg.Stuff.MASK_ALPHA_128)
            camera.screen.blit(self.mask, (0, 0))

            if self.status == cfg.GameStatus.HERO_WIN:
                camera.screen.blit(self.achievement.kill_icon, sfg.GameStatus.CHAPTER_KILL_ICON_BLIT_POS)
                camera.screen.blit(self.achievement.n_hit_icon, sfg.GameStatus.CHAPTER_N_HIT_ICON_BLIT_POS)
                camera.screen.blit(self.achievement.n_kill_icon, sfg.GameStatus.CHAPTER_N_KILL_ICON_BLIT_POS)
                camera.screen.blit(self.chapter_score_line, sfg.GameStatus.CHAPTER_SCORE_LINE_BLIT_POS)
                camera.screen.blit(self.chapter_score_icon, sfg.GameStatus.CHAPTER_SCORE_ICON_BLIT_POS)
                camera.screen.blit(self.win_panel, sfg.GameStatus.HERO_WIN_BLIT_POS)
                camera.screen.blit(self.bonus_icon, sfg.GameStatus.BONUS_ICON_BLIT_POS1)
                camera.screen.blit(self.bonus_icon, sfg.GameStatus.BONUS_ICON_BLIT_POS2)
                screen_draw_y_symmetric(camera, sfg.GameStatus.CHAPTER_NEXT, 
                    sfg.GameStatus.CHAPTER_NEXT_BLIT_Y)

                self.achievement.kill_score.draw(camera, sfg.GameStatus.CHAPTER_KILL_BLIT_POS)
                self.achievement.n_hit_score.draw(camera, sfg.GameStatus.CHAPTER_N_HIT_BLIT_POS)
                self.achievement.n_kill_score.draw(camera, sfg.GameStatus.CHAPTER_N_KILL_BLIT_POS)
                self.achievement.chapter_score.draw(camera)

                if bg_box.current_playing != sfg.Music.HERO_WIN_KEY:
                    bg_box.play(sfg.Music.HERO_WIN_KEY, 0)

            elif self.status == cfg.GameStatus.HERO_LOSE:
                camera.screen.blit(self.lose_panel, sfg.GameStatus.HERO_LOSE_BLIT_POS)
                screen_draw_y_symmetric(camera, sfg.GameStatus.CHAPTER_AGAIN, 
                    sfg.GameStatus.CHAPTER_AGAIN_BLIT_Y)
                if bg_box.current_playing != sfg.Music.HERO_LOSE_KEY:
                    bg_box.play(sfg.Music.HERO_LOSE_KEY, 0)
            
            elif self.status == cfg.GameStatus.PAUSE:
                self.menu.draw(camera.screen)
Example #7
0
class SpriteChase(State):
    def __init__(self, sprite, ai, waypoints):
        super(SpriteChase, self).__init__(cfg.SpriteState.CHASE)
        self.sprite = sprite
        self.ai = ai
        self.pathfinder = pathfinding.Astar(sprite, waypoints)
        self.steerer = Steerer(sprite)
        self.enter_timer = Timer()
        self.target_move_threshold = sfg.WayPoint.STEP_WIDTH * 4


    def enter(self, last_state):
        sp = self.sprite
        if last_state and last_state.id in (cfg.SpriteState.STAY, cfg.SpriteState.PATROL):
            # discover hero right now, record the time for action delay
            self.enter_timer.begin(self.ai.CHASE_GO_DELAY_TIME)

        sp.direction = cal_face_direct(sp.pos.as_tuple(), sp.brain.target.pos.as_tuple())
        sp.brain.destination = sp.brain.target.pos.copy()
        path = self.pathfinder.find(sp.brain.destination.as_tuple(), sfg.WayPoint.STEP_WIDTH * 2)
        self.steerer.init(path)


    def send_actions(self):
        if self.enter_timer.is_begin() and not self.enter_timer.exceed():
            # delay chase action for a more real effect
            return (cfg.EnemyAction.STAND, )

        if self.steerer.is_ok:
            self.steerer.run()
            if not self.steerer.is_end:
                return (cfg.EnemyAction.STEER, )

        return (cfg.EnemyAction.STAND, )


    def check_conditions(self):
        sp = self.sprite

        if happen(self.ai.CHASE_TO_OFFENCE_PROB) and sp.attacker.chance(sp.brain.target):
            #print "to attack"
            return cfg.SpriteState.OFFENCE

        if (not self.steerer.is_ok) or happen(self.ai.CHASE_TO_DEFENCE_PROB):
            return cfg.SpriteState.DEFENCE

        distance_to_target = sp.pos.get_distance_to(sp.brain.target.pos)
        if distance_to_target <= self.ai.CHASE_RANGE:
            target_move = sp.brain.destination.get_distance_to(sp.brain.target.pos)
            if target_move > self.target_move_threshold or self.steerer.is_end:
                #print "chase to chase"
                return cfg.SpriteState.CHASE
        else:
            # lose target
            #print "lose target"
            sp.brain.target = None
            sp.set_emotion(cfg.SpriteEmotion.CHAOS)
            return cfg.SpriteState.STAY


    def exit(self):
        self.sprite.brain.destination = None
        self.enter_timer.clear()
Example #8
0
class EnemyAnimator(SpriteAnimator):
    def __init__(self, sprite):
        super(EnemyAnimator, self).__init__(sprite)
        self.die_image = None
        self.dead_timer = Timer(sfg.Enemy.DEAD_TICK)
        self.hp_bar = pygame.Surface(sfg.SpriteStatus.ENEMY_HP_BAR_SIZE).convert_alpha()


    def dead_tick(self):
        if not self.dead_timer.is_begin():
            self.die_image = self.image.copy()
            self.dead_timer.begin()
        else:
            if self.dead_timer.exceed():
                self.image = None
                return True

            pass_time = self.dead_timer.passed_time()
            # in 1 blink unit, one half show image, another half hide it
            # make it like a blink effect
            dead_blink_unit = float(sfg.Enemy.DEAD_TICK) / sfg.Enemy.DEAD_BLINK_TIMES
            if pass_time % dead_blink_unit < dead_blink_unit * 0.5:
                self.image = self.die_image
            else:
                self.image = None

        return False


    def draw_hp_bar(self, camera):
        # fill color to hp_bar according to the sprite hp
        sp = self.sprite
        self.hp_bar.fill(sfg.SpriteStatus.SPRITE_BAR_BG_COLOR)
        r = self.hp_bar.get_rect()
        r.width *= float(sp.hp) / sp.setting.HP
        hp_color = sfg.SpriteStatus.SPRITE_HP_COLORS[sp.hp_status]
        self.hp_bar.fill(hp_color, r)

        # adjust hp_bar position relative to screen
        r = self.hp_bar.get_rect()
        r.center = (sp.pos.x, sp.pos.y * 0.5 - sp.setting.HEIGHT)
        r.left -= camera.rect.left
        r.top -= camera.rect.top
        if sp.status.get(cfg.SpriteStatus.IN_AIR) is not None:
            r.top -= sp.status[cfg.SpriteStatus.IN_AIR]["height"]
        camera.screen.blit(self.hp_bar, r)


    def draw_with_height(self, camera, height):
        image_blit_pos = (self.rect.x - camera.rect.x, self.rect.y - camera.rect.y - height)
        camera.screen.blit(self.image, image_blit_pos)


    def draw(self, camera):
        sp = self.sprite
        if sp.status.get(cfg.SpriteStatus.AMBUSH) is not None:
            # this enemy is in ambush status, draw it according the corresponding status stage
            if sp.status[cfg.SpriteStatus.AMBUSH]["status"] == cfg.Ambush.STATUS_INIT:
                # don't draw it because hero doesn't enter this ambush
                return

            elif sp.status[cfg.SpriteStatus.AMBUSH]["status"] == cfg.Ambush.STATUS_ENTER:
                # hero enter the ambush, draw corresponding episode
                if sp.status[cfg.SpriteStatus.AMBUSH]["type"] == cfg.Ambush.APPEAR_TYPE_TOP_DOWN:
                    self.draw_with_height(camera, 
                        sp.status[cfg.SpriteStatus.AMBUSH]["height"])
                return

        if sp.status.get(cfg.SpriteStatus.INVISIBLE) is not None:
            return

        super(EnemyAnimator, self).draw(camera)
        if sp.hp_status in cfg.HpStatus.ALIVE:
            self.draw_hp_bar(camera)