diff --git a/README.md b/README.md index 1635685..1ed748c 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,8 @@ game = EasyGame( ```bash # 在easy game中,打開終端機 -python -m mlgame -i ./ml/ml_play_template.py ./ --time_to_play 1200 --green_food_count 15 --red_food_count 10 --score_to_pass 10 --playground_size 100,200 +python -m mlgame -i ./ml/ml_play_template.py ./ --level 3 +python -m mlgame -i ./ml/ml_play_template.py ./ --level_file /path_to_file/level_file.json ``` ## AI範例 @@ -100,8 +101,10 @@ class MLPlay: ```json { "frame": 25, - "ball_x": 425, - "ball_y": 306, + "player_x": 425, + "player_y": 306, + "player_size": 90, + "player_vel": 16, "foods": [ { "x": 656, @@ -124,8 +127,10 @@ class MLPlay: ``` - `frame`:遊戲畫面更新的編號 -- `ball_x`:主角方塊的X座標,表示方塊的左邊座標值。 -- `ball_y`:主角方塊的Y座標,表示方塊的上方座標值。 +- `player_x`:主角方塊的X座標,表示方塊的`中心點`座標值,單位 pixel。 +- `player_y`:主角方塊的Y座標,表示方塊的`中心點`座標值,單位 pixel。 +- `player_size`:主角方塊的大小,表示方塊的長寬,單位 pixel。 +- `player_vel`:主角方塊的速度,表示方塊每幀移動的像素,單位 pixel。 - `foods`:食物的清單,清單內每一個物件都是一個食物的左上方座標值,也會提供此食物是什麼類型和分數多少。 - `type` 食物類型: `GOOD_1`, `GOOD_2`, `GOOD_3`, `BAD_1`, `BAD_2`, `BAD_3` - `score`:目前得到的分數 diff --git a/asset/sounds/lv_down.wav b/asset/sounds/lv_down.wav new file mode 100644 index 0000000..943843c Binary files /dev/null and b/asset/sounds/lv_down.wav differ diff --git a/asset/sounds/lv_up.wav b/asset/sounds/lv_up.wav new file mode 100644 index 0000000..b1cc725 Binary files /dev/null and b/asset/sounds/lv_up.wav differ diff --git a/blockly.json b/blockly.json index 0435f5a..d3059b8 100644 --- a/blockly.json +++ b/blockly.json @@ -9,8 +9,10 @@ "SCENE_INFO": [ ["scene_info['frame']", "# frame", "# 幀數"], ["scene_info['status']", "game status", "遊戲狀態"], - ["scene_info['ball_x']", "x coordinate of ball", "球的 x 座標"], - ["scene_info['ball_y']", "y coordinate of ball", "球的 y 座標"], + ["scene_info['player_x']", "x coordinate of player", "玩家角色的 x 座標"], + ["scene_info['player_y']", "y coordinate of player", "玩家角色的 y 座標"], + ["scene_info['player_size']", "y coordinate of player", "玩家角色的大小"], + ["scene_info['player_vel']", "velocity of player", "玩家角色的速度"], ["scene_info['score']", "score", "目前的分數"], ["scene_info['foods']", "list of foods positions", "點點的位置清單"], ["scene_info['score_to_pass']", "the score for next level", "通關分數"], @@ -21,8 +23,7 @@ [800, "right boundary", "右邊界"], [0, "top boundary", "上邊界"], [600, "bottom boundary", "下邊界"], - [30, "ball width", "球身的寬度"], - [30, "ball height", "球身的高度"], + [30, "player size", "玩家角色的初始大小"], [8, "food lv1 size", "等級一食物的寬高度"], [12, "food lv2 size", "等級二食物的高度"], [16, "food lv3 size", "等級三食物的高度"] diff --git a/game_config.json b/game_config.json index 6ed2edf..c5fa450 100644 --- a/game_config.json +++ b/game_config.json @@ -1,6 +1,6 @@ { "game_name": "easy_game", - "version": "2.3.1-alpha", + "version": "2.3.2-alpha", "url": "https://github.com/PAIA-Playful-AI-Arena/easy_game", "description": "這是一個吃東西小遊戲,除了讓你熟習所有基本操作,也是 PAIA 的遊戲教學範例", "logo": [ @@ -18,8 +18,8 @@ "type": "int", "min": -1, "max": 100, - "default": 1, - "help": "選定內建關卡,預設為 1 選擇第一關。" + "default": -1, + "help": "選定內建關卡,請注意,使用此設定將會覆蓋掉其他關卡設定,預設為 -1 不選擇任何關卡。" }, { "name": "level_file", "verbose": "匯入關卡檔案", diff --git a/levels/001.json b/levels/001.json index 2a5c36c..42701af 100644 --- a/levels/001.json +++ b/levels/001.json @@ -5,6 +5,6 @@ 200 ], "score_to_pass": 10, - "green_food_count": [3,0,0], - "black_food_count": [0,0,0] + "good_food_count": [3,0,0], + "bad_food_count": [0,0,0] } \ No newline at end of file diff --git a/levels/002.json b/levels/002.json index 1d7ad96..01b35a3 100644 --- a/levels/002.json +++ b/levels/002.json @@ -5,6 +5,6 @@ 200 ], "score_to_pass": 15, - "green_food_count": [5,0,0], - "black_food_count": [0,0,0] + "good_food_count": [5,0,0], + "bad_food_count": [0,0,0] } \ No newline at end of file diff --git a/levels/003.json b/levels/003.json index 1e7af2e..0363ee9 100644 --- a/levels/003.json +++ b/levels/003.json @@ -5,6 +5,6 @@ 300 ], "score_to_pass": 15, - "green_food_count": [6,0,0], - "black_food_count": [2,0,0] + "good_food_count": [6,0,0], + "bad_food_count": [2,0,0] } \ No newline at end of file diff --git a/levels/004.json b/levels/004.json index 61f4ddd..3fd03b9 100644 --- a/levels/004.json +++ b/levels/004.json @@ -5,6 +5,6 @@ 300 ], "score_to_pass": 15, - "green_food_count": [8,0,0], - "black_food_count": [4,0,0] + "good_food_count": [8,0,0], + "bad_food_count": [4,0,0] } \ No newline at end of file diff --git a/levels/005.json b/levels/005.json index 09e99fc..cc1c2d7 100644 --- a/levels/005.json +++ b/levels/005.json @@ -5,6 +5,6 @@ 300 ], "score_to_pass": 15, - "green_food_count": [8,3,0], - "black_food_count": [4,0,0] + "good_food_count": [8,3,0], + "bad_food_count": [4,0,0] } \ No newline at end of file diff --git a/levels/006.json b/levels/006.json index bac42f3..3632840 100644 --- a/levels/006.json +++ b/levels/006.json @@ -5,6 +5,6 @@ 400 ], "score_to_pass": 15, - "green_food_count": [8,4,0], - "black_food_count": [5,2,0] + "good_food_count": [8,4,0], + "bad_food_count": [5,2,0] } \ No newline at end of file diff --git a/levels/007.json b/levels/007.json index 84ee5bf..c6ca5a4 100644 --- a/levels/007.json +++ b/levels/007.json @@ -5,6 +5,6 @@ 400 ], "score_to_pass": 20, - "green_food_count": [8,4,2], - "black_food_count": [4,4,0] + "good_food_count": [8,4,2], + "bad_food_count": [4,4,0] } \ No newline at end of file diff --git a/levels/008.json b/levels/008.json index c94adc3..b69ef36 100644 --- a/levels/008.json +++ b/levels/008.json @@ -5,6 +5,6 @@ 500 ], "score_to_pass": 20, - "green_food_count": [6,4,2], - "black_food_count": [6,4,2] + "good_food_count": [6,4,2], + "bad_food_count": [6,4,2] } \ No newline at end of file diff --git a/levels/009.json b/levels/009.json index 9ee974f..67a42c4 100644 --- a/levels/009.json +++ b/levels/009.json @@ -5,6 +5,6 @@ 500 ], "score_to_pass": 25, - "green_food_count": [10,5,3], - "black_food_count": [12,7,4] + "good_food_count": [10,5,3], + "bad_food_count": [12,7,4] } \ No newline at end of file diff --git a/levels/010.json b/levels/010.json index d7450e3..9dda519 100644 --- a/levels/010.json +++ b/levels/010.json @@ -5,6 +5,6 @@ 600 ], "score_to_pass": 25, - "green_food_count": [12,8,4], - "black_food_count": [25,10,5] + "good_food_count": [12,8,4], + "bad_food_count": [25,10,5] } \ No newline at end of file diff --git a/levels/template.json b/levels/template.json index 3b9a69a..3a25bec 100644 --- a/levels/template.json +++ b/levels/template.json @@ -5,7 +5,7 @@ 200 ], "score_to_pass": 10, - "green_food_count": [3,0,0], - "black_food_count": [0,0,0] + "good_food_count": [3,0,0], + "bad_food_count": [0,0,0] } \ No newline at end of file diff --git a/main.py b/main.py index 3485878..80df9ad 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,7 @@ from src.game import EasyGame FPS = 30 if __name__ == '__main__': pygame.init() - game = EasyGame(time_to_play=1000, total_point_count=10, score=5, color="FF9800") + game = EasyGame(level=1) scene_init_info_dict = game.get_scene_init_data() game_view = PygameView(scene_init_info_dict) frame_count = 0 diff --git a/src/env.py b/src/env.py index 82274ce..b225bfe 100644 --- a/src/env.py +++ b/src/env.py @@ -10,10 +10,16 @@ PG_COLOR = "#B3E5FC" # ball BALL_COLOR = "#FFEB3B" -BALL_VEL = 10.5 +BALL_VEL = 10 BALL_H = 30 BALL_W = 30 - +BALL_GROWTH_SCORE_STEP = 15 +BALL_GROWTH_SIZE_STEP=10 +BALL_GROWTH_VEL_STEP=3 +BALL_SIZE_MAX = 100 +BALL_SIZE_MIN = 10 +BALL_VEL_MAX = 25 +BALL_VEL_MIN = 10 # food class FoodTypeEnum(StringEnum): diff --git a/src/game.py b/src/game.py index 9f063ef..cad6cf0 100644 --- a/src/game.py +++ b/src/game.py @@ -36,12 +36,11 @@ class EasyGame(PaiaGame): def __init__( self, - level: int = 1, - level_file: str = None, + level: int = -1, + level_file: str = "", sound: str = "off", *args, **kwargs): super().__init__(user_num=1) - # TODO reduce game config and use level file 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 @@ -49,17 +48,8 @@ class EasyGame(PaiaGame): self.foods = pygame.sprite.Group() self.sound_controller = SoundController(sound) - if path.isfile(self._level_file) or self._level_file == "": - # set by injected file - self._init_game_by_file(self._level_file) - pass + self._init_game() - else: - self._init_game_by_level(self._level) - - def _init_game_by_level(self, level: int): - level_file_path = os.path.join(LEVEL_PATH, f"{level:03d}.json") - self._init_game_by_file(level_file_path) def _init_game_by_file(self, level_file_path: str): try: @@ -82,8 +72,8 @@ class EasyGame(PaiaGame): self._playground_w, self._playground_h ) - self._green_food_count = game_params["green_food_count"] - self._red_food_count = game_params["black_food_count"] + self._good_food_count = game_params["good_food_count"] + self._bad_food_count = game_params["bad_food_count"] self._score_to_pass = int(game_params["score_to_pass"]) self._frame_limit = int(game_params["time_to_play"]) self.playground.center = (WIDTH / 2, HEIGHT / 2) @@ -91,15 +81,19 @@ class EasyGame(PaiaGame): # init game self.ball = Ball() self.foods.empty() - self.score = 0 - # todo validate food count - self._create_foods(GoodFoodLv1, self._green_food_count[0]) - self._create_foods(GoodFoodLv2, self._green_food_count[1]) - self._create_foods(GoodFoodLv3, self._green_food_count[2]) - self._create_foods(BadFoodLv1, self._red_food_count[0]) - self._create_foods(BadFoodLv2, self._red_food_count[1]) - self._create_foods(BadFoodLv3, self._red_food_count[2]) + if not isinstance(self._good_food_count,list) or len(self._good_food_count)<3: + raise Exception("你的關卡檔案格式有誤,請在'good_food_count' 欄位後面填入一個長度為3的陣列,舉例: [1,2,3]") + elif not isinstance(self._bad_food_count, list) or len(self._bad_food_count) < 3: + raise Exception("你的關卡檔案格式有誤,請在'bad_food_count' 欄位後面填入一個長度為3的陣列,舉例: [1,2,3]") + + else: + self._create_foods(GoodFoodLv1, self._good_food_count[0]) + self._create_foods(GoodFoodLv2, self._good_food_count[1]) + self._create_foods(GoodFoodLv3, self._good_food_count[2]) + self._create_foods(BadFoodLv1, self._bad_food_count[0]) + self._create_foods(BadFoodLv2, self._bad_food_count[1]) + self._create_foods(BadFoodLv3, self._bad_food_count[2]) self.frame_count = 0 self._frame_count_down = self._frame_limit @@ -136,7 +130,9 @@ class EasyGame(PaiaGame): hits = pygame.sprite.spritecollide(self.ball, self.foods, True) if hits: for food in hits: - self.score += food.score + # self.ball.score += food.score + # growth play special sound + self.ball.eat_food_and_change_level_and_play_sound(food,self.sound_controller) self._create_foods(food.__class__, 1) if isinstance(food, (GoodFoodLv1,GoodFoodLv2,GoodFoodLv3,)): self.sound_controller.play_eating_good() @@ -151,17 +147,22 @@ class EasyGame(PaiaGame): to_players_data = {} foods_data = [] for food in self.foods: - # TODO add good food and bad food - + # TODO 確認要提供中心點座標還是左上角座標。 foods_data.append({"x": food.rect.x, "y": food.rect.y, "type": food.type, "score": food.score}) + data_to_1p = { "frame": self.frame_count, - "ball_x": self.ball.rect.centerx, - "ball_y": self.ball.rect.centery, + # TODO 確認要提供中心點座標還是左上角座標。 + "player_x": self.ball.rect.centerx, + "player_y": self.ball.rect.centery, + "player_size":self.ball.rect.width, + "player_vel":self.ball.vel, "foods": foods_data, - "score": self.score, + + "score": self.ball.score, "score_to_pass":self._score_to_pass, "status": self.get_game_status() + } to_players_data[get_ai_name(0)] = data_to_1p @@ -182,22 +183,27 @@ class EasyGame(PaiaGame): def reset(self): 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._init_game_by_level(self._level) + self.sound_controller.play_cheer() self._init_game() + + pass + def _init_game(self): + if path.isfile(self._level_file): + # set by injected file + self._init_game_by_file(self._level_file) + pass + else: + level_file_path = os.path.join(LEVEL_PATH, f"{self._level:03d}.json") + self._init_game_by_file(level_file_path) + @property def is_passed(self): - return self.score > self._score_to_pass + return self.ball.score > self._score_to_pass @property def is_running(self): @@ -239,7 +245,7 @@ class EasyGame(PaiaGame): ] toggle_objs = [ - create_text_view_data(f"Score:{self.score:04d}", 600, 50, "#A5D6A7", "24px Arial BOLD"), + create_text_view_data(f"Score:{self.ball.score:04d}", 600, 50, "#A5D6A7", "24px Arial BOLD"), create_text_view_data(f" Next:{self._score_to_pass:04d}", 600, 100, "#FF4081", "24px Arial BOLD"), create_text_view_data(f" Time:{self._frame_count_down:04d}", 600, 150, "#FF5722", "24px Arial BOLD"), @@ -262,7 +268,7 @@ class EasyGame(PaiaGame): { "player": get_ai_name(0), "rank": 1, - "score": self.score, + "score": self.ball.score, "passed": self.is_passed } ] diff --git a/src/game_object.py b/src/game_object.py index bf0e9c9..ce5303e 100644 --- a/src/game_object.py +++ b/src/game_object.py @@ -1,7 +1,11 @@ +import math + import pygame.sprite -from games.easy_game.src.env import BALL_COLOR, BALL_VEL, BALL_H, BALL_W +from games.easy_game.src.env import BALL_COLOR, BALL_VEL, BALL_H, BALL_W, BALL_GROWTH_SCORE_STEP, BALL_GROWTH_SIZE_STEP, \ + BALL_SIZE_MAX, BALL_GROWTH_VEL_STEP, BALL_VEL_MAX, BALL_SIZE_MIN, BALL_VEL_MIN from games.easy_game.src.foods import Food +from games.easy_game.src.sound_controller import SoundController from mlgame.view.view_model import create_rect_view_data @@ -13,20 +17,23 @@ class Ball(pygame.sprite.Sprite): self.color = BALL_COLOR self.rect = self.image.get_rect() self.rect.center = (400, 300) - + self._score = 0 + self._vel = BALL_VEL + self._lv =1 def update(self, motion): # for motion in motions: if motion == "UP": - self.rect.centery -= BALL_VEL + self.rect.centery -= self._vel elif motion == "DOWN": - self.rect.centery += BALL_VEL + self.rect.centery += self._vel elif motion == "LEFT": - self.rect.centerx -= BALL_VEL + self.rect.centerx -= self._vel # self.angle += 5 elif motion == "RIGHT": - self.rect.centerx += BALL_VEL + self.rect.centerx += self._vel # self.angle -= 5 + # self.image = pygame.transform.rotate(self.origin_image, self.angle) # print(self.angle) # center = self.rect.center @@ -44,4 +51,22 @@ class Ball(pygame.sprite.Sprite): self.color ) + def eat_food_and_change_level_and_play_sound(self, food: Food,sound_controller:SoundController): + self._score += food.score + new_lv = math.ceil((self._score-BALL_GROWTH_SCORE_STEP + 1) / BALL_GROWTH_SCORE_STEP) + self.rect.width = max(BALL_SIZE_MIN,min(BALL_W + new_lv * BALL_GROWTH_SIZE_STEP, BALL_SIZE_MAX)) + self.rect.height = max(BALL_SIZE_MIN,min(BALL_H + new_lv * BALL_GROWTH_SIZE_STEP, BALL_SIZE_MAX)) + self._vel = max(BALL_VEL_MIN,min(BALL_VEL + new_lv * BALL_GROWTH_VEL_STEP, BALL_VEL_MAX)) + if new_lv > self._lv: + sound_controller.play_lv_up() + elif new_lv < self._lv: + sound_controller.play_lv_down() + self._lv=new_lv + pass + @property + def score(self): + return self._score + @property + def vel(self): + return self._vel \ No newline at end of file diff --git a/src/sound_controller.py b/src/sound_controller.py index 284109d..f755ed3 100644 --- a/src/sound_controller.py +++ b/src/sound_controller.py @@ -25,6 +25,8 @@ class SoundController(): 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")) + self._lv_up = pygame.mixer.Sound(path.join(SOUND_PATH, "lv_up.wav")) + self._lv_down = pygame.mixer.Sound(path.join(SOUND_PATH, "lv_down.wav")) except Exception: self._is_sound_on = False @@ -42,3 +44,11 @@ class SoundController(): @sound_enabled def play_cheer(self): self._cheer.play() + + @sound_enabled + def play_lv_up(self): + self._lv_up.play() + + @sound_enabled + def play_lv_down(self): + self._lv_down.play() \ No newline at end of file