예제 #1
0
    def setup(self):
        self.planets = arcade.SpriteList()
        self.absconded = False
        self.game_over_time = None
        self.lithium_count = 0
        self.player_has_clicked_lithium = False
        self.player_has_healed_planet = False
        self.banner_text = ""
        self.last_banner_change = None
        self.story_iter = itertools.cycle(line for line in STORY_LINES)
        self.background = arcade.load_texture(BACKGROUND_IMAGE)
        try:
            self.background_music.stop()
        except NameError:
            # Soloud not installed
            pass
        self.background_music.play(BACKGROUND_MUSIC_VOLUME *
                                   self.master_volume)
        planets = [Planet(planet_name) for planet_name in ALL_PLANETS]
        self.setup_theme()
        self.abscond_button = TextButton(SCREEN_SIZE[0] / 6,
                                         SCREEN_SIZE[1] / 15,
                                         200,
                                         50,
                                         "Abscond",
                                         theme=self.theme)
        self.restart_button = arcade.Sprite(RESTART_IMAGE)
        self.restart_button.center_x = self.restart_location[0]
        self.restart_button.center_y = self.restart_location[1]
        self.restart_button.scale /= 2
        self.abscond_button.on_press = self.abscond_press
        self.abscond_button.on_release = self.abscond_release
        self.button_list.append(self.abscond_button)

        self.volume_meter = arcade.Sprite(VOLUME_IMAGE)
        self.volume_meter.center_x = self.volume_location[0]
        self.volume_meter.center_y = self.volume_location[1]
        self.volume_meter.width *= 3
        self.volume_meter_leftmost = (self.volume_meter.center_x -
                                      (self.volume_meter.width / 2))
        self.volume_meter_rightmost = (self.volume_meter.center_x +
                                       (self.volume_meter.width / 2))
        self.volume_meter_bottommost = (self.volume_meter.center_y - 10)
        self.volume_meter_topmost = (self.volume_meter.center_y + 10)
        self.volume_mover = arcade.Sprite(VOLUME_MOVER_IMAGE)
        self.volume_mover.center_x = self.volume_meter.center_x
        self.volume_mover.center_y = self.volume_meter.center_y
        self.volume_mover.scale /= 3

        spawn_locations = get_spawn_locations()
        for planet, spawn_location in zip(planets, spawn_locations):
            self.planets.append(planet)
            others = [other for other in planets if other != planet]
            planet.setup(parent=self,
                         others=others,
                         center_x=spawn_location[0],
                         center_y=spawn_location[1],
                         start_speed_x=random.random(),
                         start_speed_y=random.random())
예제 #2
0
def test_draw_when_theme_not_set_and_text_is_empty(mocker):
    mocker.patch.object(TextButton, 'draw_texture_theme')
    mocker.patch.object(TextButton, 'draw_color_theme')
    mocker.patch('arcade.draw_text')

    button = TextButton(center_x=50, center_y=20, width=30, height=10, text='')
    button.draw()
    button.draw_color_theme.assert_called_once()
    button.draw_texture_theme.assert_not_called()
    arcade.draw_text.assert_not_called()
예제 #3
0
def test_check_mouse_press_when_check_mouse_collision_true(mocker):
    mocker.patch.object(TextButton, 'check_mouse_collision', return_value=True)
    mocker.patch.object(TextButton, 'on_press')
    button = TextButton(center_x=50,
                        center_y=20,
                        width=30,
                        height=10,
                        text='Click me')
    button.check_mouse_press(50, 20)

    assert button.pressed is True
    button.check_mouse_collision.assert_called_once_with(50, 20)
    button.on_press.assert_called_once()
예제 #4
0
def test_draw_texture_theme(mocker):
    mocker.patch('arcade.draw_texture_rectangle')

    theme = arcade.gui.Theme()
    normal_texture = arcade.Texture('normal')
    clicked_texture = arcade.Texture('clicked')
    theme.button_textures['normal'] = normal_texture
    theme.button_textures['clicked'] = clicked_texture
    button = TextButton(center_x=50,
                        center_y=20,
                        width=30,
                        height=10,
                        text='',
                        theme=theme)

    button.pressed = False
    button.draw_texture_theme()
    arcade.draw_texture_rectangle.assert_called_once_with(
        50, 20, 30, 10, normal_texture)

    arcade.draw_texture_rectangle.reset_mock()
    button.pressed = True
    button.draw_texture_theme()
    arcade.draw_texture_rectangle.assert_called_once_with(
        50, 20, 30, 10, clicked_texture)
예제 #5
0
def test_check_mouse_release_when_pressed_is_false(mocker):
    mocker.patch.object(TextButton, 'check_mouse_collision', return_value=True)
    mocker.patch.object(TextButton, 'on_release')
    mocker.patch.object(TextButton, 'on_click')
    button = TextButton(center_x=50,
                        center_y=20,
                        width=30,
                        height=10,
                        text='Click me')
    button.check_mouse_release(50, 20)

    assert button.pressed is False
    button.check_mouse_collision.assert_not_called()
    button.on_release.assert_not_called()
    button.on_click.assert_not_called()
예제 #6
0
def test_on_press():
    class Counter:
        def __init__(self):
            self.value = 0

        def update(self):
            self.value += 1

    button = TextButton(center_x=50,
                        center_y=20,
                        width=30,
                        height=10,
                        text='Click me')

    counter = Counter()
    button.press_action = counter.update
    button.on_press()
    assert counter.value == 1
예제 #7
0
def test_setup_button_font_with_theme():
    theme = Theme()
    button = TextButton(center_x=50,
                        center_y=20,
                        width=30,
                        height=10,
                        text='Click me',
                        theme=theme)
    assert button.font is theme.font
예제 #8
0
def test_setup_button_font_without_theme():
    button = TextButton(center_x=50,
                        center_y=20,
                        width=30,
                        height=10,
                        text='Click me',
                        font_size=10,
                        font_face="Arial",
                        font_color=arcade.color.GREEN)

    assert button.font.size == 10
    assert button.font.name == "Arial"
    assert button.font.color == arcade.color.GREEN
예제 #9
0
def test_draw_when_theme_not_set(mocker):
    mocker.patch.object(TextButton, 'draw_texture_theme')
    mocker.patch.object(TextButton, 'draw_color_theme')
    mocker.patch('arcade.draw_text')

    button = TextButton(center_x=50,
                        center_y=20,
                        width=30,
                        height=10,
                        text='Click me')
    button.draw()
    button.draw_color_theme.assert_called_once()
    button.draw_texture_theme.assert_not_called()
    arcade.draw_text.assert_called_once_with('Click me',
                                             50,
                                             20,
                                             button.font.color,
                                             font_size=button.font.size,
                                             font_name=button.font.name,
                                             width=button.width,
                                             align="center",
                                             anchor_x="center",
                                             anchor_y="center")
예제 #10
0
def test_get_border_positions():
    button = TextButton(center_x=50,
                        center_y=20,
                        width=30,
                        height=10,
                        text='Click me')
    assert button.get_top() == 20 + 10 / 2
    assert button.get_bottom() == 20 - 10 / 2
    assert button.get_left() == 50 - 30 / 2
    assert button.get_right() == 50 + 30 / 2
예제 #11
0
def test_create_text_button_with_minimal_params():
    button = TextButton(center_x=50,
                        center_y=20,
                        width=30,
                        height=10,
                        text='Click me')

    assert button.text == 'Click me'

    assert button.center_x == 50
    assert button.center_y == 20
    assert button.width == 30
    assert button.height == 10

    assert button.pressed is False
    assert button.active is True
예제 #12
0
def test_check_mouse_collision():
    button = TextButton(center_x=50,
                        center_y=20,
                        width=30,
                        height=10,
                        text='Click me')

    assert button.check_mouse_collision(x=50, y=20) is True

    assert button.check_mouse_collision(x=35, y=20) is True
    assert button.check_mouse_collision(x=34, y=20) is False
    assert button.check_mouse_collision(x=65, y=20) is True
    assert button.check_mouse_collision(x=66, y=20) is False

    assert button.check_mouse_collision(x=50, y=25) is True
    assert button.check_mouse_collision(x=50, y=26) is False
    assert button.check_mouse_collision(x=50, y=15) is True
    assert button.check_mouse_collision(x=50, y=14) is False
예제 #13
0
def test_draw_color_theme(mocker):
    mocker.patch('arcade.draw_rectangle_filled')
    mocker.patch('arcade.draw_line')

    button = TextButton(center_x=50, center_y=20, width=30, height=10, text='')

    button.pressed = False
    button.draw_color_theme()
    arcade.draw_rectangle_filled.assert_called_once_with(
        50, 20, 30, 10, button.face_color)

    assert arcade.draw_line.call_count == 4
    assert arcade.draw_line.call_args_list == [
        call(button.get_left(), button.get_bottom(), button.get_right(),
             button.get_bottom(), button.shadow_color, button.button_height),
        call(button.get_right(), button.get_bottom(), button.get_right(),
             button.get_top(), button.shadow_color, button.button_height),
        call(button.get_left(), button.get_top(), button.get_right(),
             button.get_top(), button.highlight_color, button.button_height),
        call(button.get_left(), button.get_bottom(), button.get_left(),
             button.get_top(), button.highlight_color, button.button_height)
    ]

    arcade.draw_rectangle_filled.reset_mock()
    arcade.draw_line.reset_mock()
    button.pressed = True
    button.draw_color_theme()
    arcade.draw_rectangle_filled.assert_called_once_with(
        50, 20, 30, 10, button.face_color)

    assert arcade.draw_line.call_count == 4
    assert arcade.draw_line.call_args_list == [
        call(button.get_left(), button.get_bottom(), button.get_right(),
             button.get_bottom(), button.highlight_color,
             button.button_height),
        call(button.get_right(), button.get_bottom(), button.get_right(),
             button.get_top(), button.highlight_color, button.button_height),
        call(button.get_left(), button.get_top(), button.get_right(),
             button.get_top(), button.shadow_color, button.button_height),
        call(button.get_left(), button.get_bottom(), button.get_left(),
             button.get_top(), button.shadow_color, button.button_height)
    ]
예제 #14
0
class Game(arcade.Window):
    def __init__(self):
        super().__init__(SCREEN_SIZE[0], SCREEN_SIZE[1], SCREEN_TITLE)
        self.planets = None
        self.lithium_location = get_new_lithium_location()
        self.last_lithium_change = time.time()
        self.lithium_count = 0
        self.lithium_score_location = (SCREEN_SIZE[0] / 3, SCREEN_SIZE[1] / 20)
        self.theme = None
        self.background = None
        self.background_music = arcade.Sound(BACKGROUND_MUSIC)
        self.lithium_sound = arcade.Sound(LITHIUM_SOUND)
        self.heal_sound = arcade.Sound(HEAL_SOUND)
        self.abscond_sound = arcade.Sound(ABSCOND_SOUND)
        self.game_over_sound = arcade.Sound(GAME_OVER_SOUND)
        self.restart_sound = arcade.Sound(RESTART_SOUND)

        self.master_volume = 0.5

        self.abscond_button = None
        self.restart_button = None
        self.volume_meter = None
        self.volume_mover = None

        self.player_in_tutorial = True
        self.game_over_time = None
        self.absconded = None
        self.player_has_clicked_lithium = False
        self.player_has_healed_planet = False
        self.banner_text = None
        self.banner_location = (SCREEN_SIZE[0] / 100, SCREEN_SIZE[1] / 2)
        self.last_banner_change = None
        self.story_iter = None

        self.volume_location = (7 * SCREEN_SIZE[0] / 8, SCREEN_SIZE[1] / 13)
        self.restart_location = (29 * SCREEN_SIZE[0] / 40, SCREEN_SIZE[1] / 13)

        self.banner_background_color = arcade.make_transparent_color(
            arcade.color.BLUE, 100)

    def setup(self):
        self.planets = arcade.SpriteList()
        self.absconded = False
        self.game_over_time = None
        self.lithium_count = 0
        self.player_has_clicked_lithium = False
        self.player_has_healed_planet = False
        self.banner_text = ""
        self.last_banner_change = None
        self.story_iter = itertools.cycle(line for line in STORY_LINES)
        self.background = arcade.load_texture(BACKGROUND_IMAGE)
        try:
            self.background_music.stop()
        except NameError:
            # Soloud not installed
            pass
        self.background_music.play(BACKGROUND_MUSIC_VOLUME *
                                   self.master_volume)
        planets = [Planet(planet_name) for planet_name in ALL_PLANETS]
        self.setup_theme()
        self.abscond_button = TextButton(SCREEN_SIZE[0] / 6,
                                         SCREEN_SIZE[1] / 15,
                                         200,
                                         50,
                                         "Abscond",
                                         theme=self.theme)
        self.restart_button = arcade.Sprite(RESTART_IMAGE)
        self.restart_button.center_x = self.restart_location[0]
        self.restart_button.center_y = self.restart_location[1]
        self.restart_button.scale /= 2
        self.abscond_button.on_press = self.abscond_press
        self.abscond_button.on_release = self.abscond_release
        self.button_list.append(self.abscond_button)

        self.volume_meter = arcade.Sprite(VOLUME_IMAGE)
        self.volume_meter.center_x = self.volume_location[0]
        self.volume_meter.center_y = self.volume_location[1]
        self.volume_meter.width *= 3
        self.volume_meter_leftmost = (self.volume_meter.center_x -
                                      (self.volume_meter.width / 2))
        self.volume_meter_rightmost = (self.volume_meter.center_x +
                                       (self.volume_meter.width / 2))
        self.volume_meter_bottommost = (self.volume_meter.center_y - 10)
        self.volume_meter_topmost = (self.volume_meter.center_y + 10)
        self.volume_mover = arcade.Sprite(VOLUME_MOVER_IMAGE)
        self.volume_mover.center_x = self.volume_meter.center_x
        self.volume_mover.center_y = self.volume_meter.center_y
        self.volume_mover.scale /= 3

        spawn_locations = get_spawn_locations()
        for planet, spawn_location in zip(planets, spawn_locations):
            self.planets.append(planet)
            others = [other for other in planets if other != planet]
            planet.setup(parent=self,
                         others=others,
                         center_x=spawn_location[0],
                         center_y=spawn_location[1],
                         start_speed_x=random.random(),
                         start_speed_y=random.random())

    def set_banner_text(self, new_text):
        if new_text == self.banner_text:
            return
        self.last_banner_change = time.time()
        self.banner_text = new_text
        x_coord = SCREEN_SIZE[0] / 100
        y_coord = ((random.random() * (6 * SCREEN_SIZE[1] / 8)) +
                   (SCREEN_SIZE[1] / 5))
        self.banner_location = (x_coord, y_coord)

    def abscond_press(self):
        self.abscond_button.pressed = True

    def abscond_release(self):
        if not self.abscond_button.pressed:
            return
        self.abscond_button.pressed = False
        self.absconded = True
        self.abscond_sound.play(self.master_volume * ABSCOND_VOLUME)
        self.game_over(f"Absconded with {self.lithium_count:.2f} lithium!")

    def set_button_textures(self):
        normal = ":resources:gui_themes/Fantasy/Buttons/Normal.png"
        hover = ":resources:gui_themes/Fantasy/Buttons/Hover.png"
        clicked = ":resources:gui_themes/Fantasy/Buttons/Clicked.png"
        locked = ":resources:gui_themes/Fantasy/Buttons/Locked.png"
        self.theme.add_button_textures(normal, hover, clicked, locked)

    def setup_theme(self):
        self.theme = Theme()
        self.theme.set_font(24, arcade.color.WHITE)
        self.set_button_textures()

    @log_exceptions
    def on_draw(self):
        """ Draw everything """
        arcade.start_render()
        arcade.draw_lrwh_rectangle_textured(0, 0, SCREEN_SIZE[0],
                                            SCREEN_SIZE[1], self.background)
        super().on_draw()
        for planet in self.planets:
            for other in planet.attacked_last_round:
                attack_point = random_location_in_planet(
                    (other.center_x, other.center_y), other.width / 4)
                attack_start_radius = min(planet.base_damage * 1e4,
                                          planet.width / 4)
                start_point_1, start_point_2 = get_attack_triangle_points(
                    (planet.center_x, planet.center_y), attack_point,
                    attack_start_radius)
                arcade.draw_triangle_filled(*start_point_1,
                                            *start_point_2,
                                            *attack_point,
                                            color=planet.color)
            planet.attacked_last_round = []
            planet.pushed_last_round = []

            planet.draw_triangulation_circle()
        self.planets.draw()
        lithium_count_text = f"Lithium count: {self.lithium_count:.2f}"
        arcade.draw_text(f"{lithium_count_text}",
                         *self.lithium_score_location,
                         color=arcade.color.WHITE,
                         font_size=24)

        self.abscond_button.draw()

        if self.game_over_time:
            self.restart_button.draw()

        self.volume_meter.draw()
        self.volume_mover.draw()

        arcade.draw_rectangle_filled(center_x=SCREEN_SIZE[0] / 2,
                                     center_y=20 + self.banner_location[1],
                                     width=SCREEN_SIZE[0],
                                     height=40,
                                     color=self.banner_background_color)

        arcade.draw_text(self.banner_text,
                         *self.banner_location,
                         color=arcade.color.GREEN,
                         font_size=22)

    def check_volume_press(self, x, y):
        if (self.volume_meter_leftmost < x < self.volume_meter_rightmost and
                self.volume_meter_bottommost < y < self.volume_meter_topmost):
            self.master_volume = (
                (x - self.volume_meter_leftmost) /
                (self.volume_meter_rightmost - self.volume_meter_leftmost))
            self.background_music.set_volume(BACKGROUND_MUSIC_VOLUME *
                                             self.master_volume)
            self.volume_mover.center_x = x

    @log_exceptions
    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        self.check_volume_press(x, y)

    @log_exceptions
    def on_mouse_press(self, x, y, button, modifiers):
        if self.game_over_time:
            if self.restart_button.collides_with_point((x, y)):
                self.restart_sound.play(self.master_volume * RESTART_VOLUME)
                self.setup()
            return
        if get_distance(x, y, *self.lithium_location) < 10:
            self.clicked_lithium()
        for planet in self.planets:
            if self.lithium_count >= 1 and planet.collides_with_point((x, y)):
                logger.info(f"Healing {planet.name}")
                self.lithium_count -= 1
                planet.get_healed(0.1)
                self.heal_sound.play(self.master_volume * HEAL_VOLUME)
                self.player_has_healed_planet = True

        self.abscond_button.check_mouse_press(x, y)

        self.check_volume_press(x, y)

    def clicked_lithium(self):
        self.lithium_sound.play(self.master_volume * LITHIUM_VOLUME)
        planet_avg_health = self.avg_planet_health()
        self.lithium_count += planet_avg_health * LITHIUM_MULTIPLIER
        self.lithium_location = get_new_lithium_location()
        self.last_lithium_change = time.time()
        self.player_has_clicked_lithium = True

    def avg_planet_health(self):
        return (sum([planet.health
                     for planet in self.planets]) / len(self.planets))

    def on_update(self, delta_time):
        if self.game_over_time:
            game_over_delta_time = (BASE_TIME_MULTIPLIER *
                                    (time.time() - self.game_over_time))
            if game_over_delta_time > 6 and not self.absconded:
                self.setup()
                return
        time_multiplier = BASE_TIME_MULTIPLIER * delta_time / 0.0168
        if self.player_in_tutorial:
            time_multiplier /= 6
        logger.debug("\nNew Round\n")
        if not self.player_in_tutorial and not self.game_over_time:
            self.lithium_count += delta_time / 100
        self.run_assertions()
        self.update_banner()
        self.planets.update()
        [planet.move(time_multiplier) for planet in self.planets]
        [planet.try_attack_others(time_multiplier) for planet in self.planets]
        [planet.try_push_others(time_multiplier) for planet in self.planets]
        should_not_triangulate = (self.player_in_tutorial
                                  and self.lithium_count > 2
                                  and not self.player_has_healed_planet)
        [
            planet.update_triangulating(time_multiplier,
                                        self.player_in_tutorial,
                                        should_not_triangulate)
            for planet in self.planets
        ]

        try:
            if self.background_music.get_stream_position() == 0.0:
                self.background_music.play(self.master_volume *
                                           BACKGROUND_MUSIC_VOLUME)
        except AttributeError:
            # Soloud not installed
            pass

        self.run_assertions()

    def update_banner(self):
        if self.game_over_time:
            return
        if not self.player_in_tutorial:
            self.update_banner_story()
            return
        now = time.time()
        if self.last_banner_change is None:
            self.set_banner_text("This is the story of Ze, Yogh, and Ezh.")
        delta_time = BASE_TIME_MULTIPLIER * (now - self.last_banner_change)
        if not self.player_has_clicked_lithium and delta_time > 3:
            self.set_banner_text(
                "See the flickering circles? Click on their intersection.")
        if (self.player_has_clicked_lithium
                and not self.player_has_healed_planet
                and not self.lithium_count > 2 and delta_time > 0.5):
            self.set_banner_text("Good. Keep doing it.")
        if self.lithium_count > 2 and not self.player_has_healed_planet:
            self.set_banner_text(
                "Good. Now heal one of the planets by clicking on them.")
        if self.player_has_healed_planet:
            self.set_banner_text(
                "You've healed them with lithium. Keep them alive.")
            delta_time = BASE_TIME_MULTIPLIER * (now - self.last_banner_change)
            if delta_time > 2:
                self.player_in_tutorial = False

    def update_banner_story(self):
        if self.game_over_time:
            return
        now = time.time()
        self.last_banner_change = self.last_banner_change or now - 5
        delta_time = BASE_TIME_MULTIPLIER * (now - self.last_banner_change)
        if delta_time < 3:
            return
        next_story_part = next(self.story_iter, self.banner_text)
        self.set_banner_text(next_story_part)

    def run_assertions(self):
        assert len(self.planets) in (1, 2, 3)
        if not self.game_over_time:
            assert len(self.planets) == 3
        assert self.lithium_count >= 0
        for planet in self.planets:
            if not self.game_over_time:
                assert len(planet.others) == 2
            assert planet.center_x > -SCREEN_SIZE[0]
            assert planet.center_x < SCREEN_SIZE[0] * 2
            assert planet.center_y > -SCREEN_SIZE[1]
            assert planet.center_y < SCREEN_SIZE[1] * 2
            assert planet.speed_x != 0
            assert planet.speed_y != 0

    def game_over(self, reason):
        if self.game_over_time:
            return
        if not self.absconded:
            self.game_over_sound.play(self.master_volume * GAME_OVER_VOLUME)
        self.banner_text = reason
        logger.info(f"Game over! {reason}")
        for planet in self.planets:
            logger.info(planet.get_stats_str())
        self.game_over_time = time.time()