diff --git a/README.md b/README.md index f35889b..8f962db 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,12 @@ ```python # main.py game = EasyGame( - time_to_play, score_to_pass, green_food_count, red_food_count, - playground_size: list,level: int = -1) + time_to_play, score_to_pass, + green_food_count, red_food_count, + playground_size: list, + level: int = -1, + level_file: str = None, + sound: str = "off") ``` - `time_to_play`:遊戲執行的終止時間,單位是 frame,也就是遊戲內部更新畫面的次數,每更新一次 frame +1 @@ -31,7 +35,9 @@ game = EasyGame( - `red_food_count`:遊戲中紅色食物的數量。 - `score_to_pass`:遊戲通關的點數,要超過這個分數才算過關。 - `playground_size`:可移動區域的大小。 使用逗號將數字隔開 `width,height` `100,200` -- `level`: 選定內建關卡,請注意,使用此設定將會覆蓋掉其他設定,預設為 -1 不選擇任何關卡。 +- `level`: 選定內建關卡,請注意,使用此設定將會覆蓋掉上述其他關卡參數設定,預設為 -1 不選擇任何關卡。 +- `level_file`: 使用外部檔案作為關卡,請注意,使用此設定將會覆蓋掉關卡編號,並且不會自動進入下一關。 +- `sound`: 音效。 ## 玩法 @@ -153,7 +159,8 @@ class MLPlay: { "player": "1P", "score": 0, - "rank": 1 + "rank": 1, + "passed": false } ] } diff --git a/asset/easy_game.gif b/asset/easy_game.gif index 6d8cff9..4796c22 100644 Binary files a/asset/easy_game.gif and b/asset/easy_game.gif differ diff --git a/asset/music/bgm.wav b/asset/music/bgm.wav new file mode 100644 index 0000000..85fbd6c Binary files /dev/null and b/asset/music/bgm.wav differ diff --git a/asset/sounds/Cheer.wav b/asset/sounds/Cheer.wav new file mode 100644 index 0000000..4bc9782 Binary files /dev/null and b/asset/sounds/Cheer.wav differ diff --git a/asset/sounds/Coin.wav b/asset/sounds/Coin.wav new file mode 100644 index 0000000..ca52ea4 Binary files /dev/null and b/asset/sounds/Coin.wav differ diff --git a/asset/sounds/Low Boing.wav b/asset/sounds/Low Boing.wav new file mode 100644 index 0000000..cb62397 Binary files /dev/null and b/asset/sounds/Low Boing.wav differ diff --git a/game_config.json b/game_config.json index bd1b77b..6365363 100644 --- a/game_config.json +++ b/game_config.json @@ -82,7 +82,24 @@ "min": -1, "max": 5, "default": "-1", - "help": "選定內建關卡,請注意,使用此設定將會覆蓋掉其他設定,預設為 -1 不選擇任何關卡。" + "help": "選定內建關卡,請注意,使用此設定將會覆蓋掉其他關卡設定,預設為 -1 不選擇任何關卡。" + }, { + "name": "level_file", + "verbose": "匯入關卡檔案", + "type": "path", + "default": "", + "help": "可匯入自定義的關卡資料,請參考內建範例的資料格式來設定。使用此設定會覆蓋掉其他關卡設定,也不會使用關卡編號,也不會自動進入下一關。" + }, + { + "name": "sound", + "verbose": "遊戲音效", + "type": "str", + "choices": [ + "on", + "off" + ], + "help": "'on' can turn on the sound.", + "default": "on" } ] } \ No newline at end of file diff --git a/levels/template.json b/levels/template.json index 8e70077..3d76644 100644 --- a/levels/template.json +++ b/levels/template.json @@ -3,8 +3,5 @@ "playground_size": [ 500, 200 - ], - "score_to_pass": 20, - "green_food_count": 3, - "black_food_count": 1 + ] } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 936367f..0a49b58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -mlgame>=10.3.1 \ No newline at end of file +mlgame>=10.3.2 \ No newline at end of file diff --git a/src/enums.py b/src/enums.py deleted file mode 100644 index 67d67c9..0000000 --- a/src/enums.py +++ /dev/null @@ -1,6 +0,0 @@ - -from enum import auto -from mlgame.utils.enum import StringEnum -class FoodTypeEnum(StringEnum): - GREEN = auto() - RED = auto() diff --git a/src/env.py b/src/env.py new file mode 100644 index 0000000..82de125 --- /dev/null +++ b/src/env.py @@ -0,0 +1,31 @@ +from enum import auto +from os import path + +from mlgame.utils.enum import StringEnum +# game +WIDTH = 800 +HEIGHT = 600 +BG_COLOR = "#111111" +PG_COLOR = "#B3E5FC" + +# ball +BALL_COLOR = "#FFEB3B" +BALL_VEL = 10.5 +BALL_H = 30 +BALL_W = 30 + + +# food +class FoodTypeEnum(StringEnum): + GREEN = auto() + RED = auto() +FOOD_COLOR_MAP = {FoodTypeEnum.GREEN: "#009688", + FoodTypeEnum.RED: "#FF1744"} + +# path of assets +ASSET_PATH = path.join(path.dirname(__file__), "..", "asset") +LEVEL_PATH = path.join(path.dirname(__file__), "..", "levels") +SOUND_PATH = path.join(path.dirname(__file__), "..", "asset", "sounds") +MUSIC_PATH = path.join(path.dirname(__file__), "..", "asset", "music") + + diff --git a/src/game.py b/src/game.py index f69c57d..3b916df 100644 --- a/src/game.py +++ b/src/game.py @@ -1,7 +1,6 @@ import copy import json import os.path -from os import path import pygame @@ -9,16 +8,10 @@ from mlgame.game.paia_game import PaiaGame, GameResultState, GameStatus from mlgame.utils.enum import get_ai_name from mlgame.view.decorator import check_game_progress, check_game_result from mlgame.view.view_model import * -from .enums import FoodTypeEnum +from .env import * +from .env import FoodTypeEnum from .game_object import Ball, Food - -BG_COLOR = "#111111" -PG_COLOR = "#B3E5FC" - -WIDTH = 800 -HEIGHT = 600 -ASSET_PATH = path.join(path.dirname(__file__), "../asset") -LEVEL_PATH = path.join(path.dirname(__file__), "../levels") +from .sound_controller import SoundController class EasyGame(PaiaGame): @@ -30,18 +23,25 @@ class EasyGame(PaiaGame): self, time_to_play, score_to_pass, green_food_count, red_food_count, playground_size: list, level: int = -1, - # level_file, + level_file: str = None, + sound: str = "off", *args, **kwargs): super().__init__(user_num=1) self.game_result_state = GameResultState.FAIL self.scene = Scene(width=WIDTH, height=HEIGHT, color=BG_COLOR, bias_x=0, bias_y=0) self._level = level - if level != -1: - - self.set_game_params_by_level(level) + self._level_file = level_file + if self._level_file is not None or level == "": + # set by injected file + self.set_game_params_by_file(self._level_file) + pass + elif self._level != -1: + # set by level number + self.set_game_params_by_level(self._level) pass else: + # set by game params self._playground_w = int(playground_size[0]) self._playground_h = int(playground_size[1]) self.playground = pygame.Rect( @@ -56,22 +56,29 @@ class EasyGame(PaiaGame): self.playground.center = (WIDTH / 2, HEIGHT / 2) self.foods = pygame.sprite.Group() + self.sound_controller = SoundController(sound) self.init_game() def set_game_params_by_level(self, level): - level_file_path =os.path.join(LEVEL_PATH, f"{level:03d}.json") - if os.path.exists(level_file_path): - # If the file exists, load parameters from the file + level_file_path = os.path.join(LEVEL_PATH, f"{level:03d}.json") + self.set_game_params_by_file(level_file_path) + + def set_game_params_by_file(self, level_file_path: str): + try: with open(level_file_path) as f: game_params = json.load(f) - else: + self._set_game_params(game_params) + except: # If the file doesn't exist, use default parameters - + print("此關卡檔案不存在,遊戲將會會自動使用第一關檔案 001.json。") + print("This level file is not existed , game will load 001.json automatically.") with open(os.path.join(LEVEL_PATH, "001.json")) as f: game_params = json.load(f) - self._level=1 - + self._level = 1 + self._level_file=None + self._set_game_params(game_params) + def _set_game_params(self, game_params): self._playground_w = int(game_params["playground_size"][0]) self._playground_h = int(game_params["playground_size"][1]) self.playground = pygame.Rect( @@ -85,7 +92,6 @@ class EasyGame(PaiaGame): self._frame_limit = int(game_params["time_to_play"]) self.playground.center = (WIDTH / 2, HEIGHT / 2) - def init_game(self): self.ball = Ball() self.foods.empty() @@ -94,6 +100,7 @@ class EasyGame(PaiaGame): self._create_foods(self._red_food_count, FoodTypeEnum.RED) self.frame_count = 0 self._frame_count_down = self._frame_limit + self.sound_controller.play_music() def update(self, commands): # handle command @@ -111,12 +118,15 @@ class EasyGame(PaiaGame): # handle collision hits = pygame.sprite.spritecollide(self.ball, self.foods, True) if hits: + for food in hits: if food.type == FoodTypeEnum.GREEN: + self.sound_controller.play_eating_good() self.score += 1 self._create_foods(1, FoodTypeEnum.GREEN) elif food.type == FoodTypeEnum.RED: + self.sound_controller.play_eating_bad() self._create_foods(1, FoodTypeEnum.RED) self.score -= 1 # self._timer = round(time.time() - self._begin_time, 3) @@ -155,14 +165,20 @@ class EasyGame(PaiaGame): if self.is_running: status = GameStatus.GAME_ALIVE - elif self.score > self._score_to_pass: + elif self.is_passed: status = GameStatus.GAME_PASS else: status = GameStatus.GAME_OVER return status def reset(self): - if self.score > self._score_to_pass and self._level != -1: + + if self.is_passed: + self.sound_controller.play_cheer() + + if self._level_file: + pass + elif self.is_passed and self._level != -1: # win and use level will enter next level self._level += 1 self.set_game_params_by_level(self._level) @@ -171,6 +187,10 @@ class EasyGame(PaiaGame): pass + @property + def is_passed(self): + return self.score > self._score_to_pass + @property def is_running(self): return self.frame_count < self._frame_limit @@ -236,7 +256,7 @@ class EasyGame(PaiaGame): "player": get_ai_name(0), "rank": 1, "score": self.score, - "passed": self.score > self._score_to_pass + "passed": self.is_passed } ] diff --git a/src/game_object.py b/src/game_object.py index bda8172..e7c316a 100644 --- a/src/game_object.py +++ b/src/game_object.py @@ -1,17 +1,8 @@ import pygame.sprite -from games.easy_game.src.enums import FoodTypeEnum +from games.easy_game.src.env import FoodTypeEnum, FOOD_COLOR_MAP, BALL_COLOR, BALL_VEL, BALL_H, BALL_W from mlgame.view.view_model import create_rect_view_data -FOOD_COLOR_MAP = {FoodTypeEnum.GREEN: "#009688", - FoodTypeEnum.RED: "#FF1744"} -BALL_COLOR = "#FFEB3B" -BALL_VEL = 10.5 - -BALL_H = 30 - -BALL_W = 30 - class Ball(pygame.sprite.Sprite): def __init__(self): diff --git a/src/sound_controller.py b/src/sound_controller.py new file mode 100644 index 0000000..284109d --- /dev/null +++ b/src/sound_controller.py @@ -0,0 +1,44 @@ +import pygame + +from .env import * + + +def sound_enabled(func): + def wrapper(self, *args, **kwargs): + if self._is_sound_on: + return func(self, *args, **kwargs) + + return wrapper + + +class SoundController(): + def __init__(self, is_sound_on): + self._is_sound_on = bool(is_sound_on == "on") + if self._is_sound_on: + self.load_sounds() + + def load_sounds(self): + try: + pygame.mixer.init() + pygame.mixer.music.load(path.join(MUSIC_PATH, "bgm.wav")) + pygame.mixer.music.set_volume(0.6) + self._eating_good = pygame.mixer.Sound(path.join(SOUND_PATH, "Coin.wav")) + self._eating_bad = pygame.mixer.Sound(path.join(SOUND_PATH, "Low Boing.wav")) + self._cheer = pygame.mixer.Sound(path.join(SOUND_PATH, "Cheer.wav")) + except Exception: + self._is_sound_on = False + + @sound_enabled + def play_music(self): + pygame.mixer.music.play(-1) + + @sound_enabled + def play_eating_good(self): + self._eating_good.play() + + @sound_enabled + def play_eating_bad(self): + self._eating_bad.play() + @sound_enabled + def play_cheer(self): + self._cheer.play()