diff --git a/asset/easy_game.gif b/asset/easy_game.gif index 4796c22..4ac9274 100644 Binary files a/asset/easy_game.gif and b/asset/easy_game.gif differ diff --git a/asset/img/background.jpg b/asset/img/background.jpg deleted file mode 100644 index e20b960..0000000 Binary files a/asset/img/background.jpg and /dev/null differ diff --git a/asset/img/background.png b/asset/img/background.png new file mode 100644 index 0000000..506cdec Binary files /dev/null and b/asset/img/background.png differ diff --git a/asset/img/food_01_L.png b/asset/img/food_01_L.png new file mode 100644 index 0000000..c1c62a7 Binary files /dev/null and b/asset/img/food_01_L.png differ diff --git a/asset/img/food_01_R.png b/asset/img/food_01_R.png new file mode 100644 index 0000000..7812e60 Binary files /dev/null and b/asset/img/food_01_R.png differ diff --git a/asset/img/food_02_L.png b/asset/img/food_02_L.png new file mode 100644 index 0000000..5cff68e Binary files /dev/null and b/asset/img/food_02_L.png differ diff --git a/asset/img/food_02_R.png b/asset/img/food_02_R.png new file mode 100644 index 0000000..8d557e8 Binary files /dev/null and b/asset/img/food_02_R.png differ diff --git a/asset/img/food_03_L.png b/asset/img/food_03_L.png new file mode 100644 index 0000000..0fc98db Binary files /dev/null and b/asset/img/food_03_L.png differ diff --git a/asset/img/food_03_R.png b/asset/img/food_03_R.png new file mode 100644 index 0000000..52e6fbd Binary files /dev/null and b/asset/img/food_03_R.png differ diff --git a/asset/img/garbage_01.png b/asset/img/garbage_01.png new file mode 100644 index 0000000..eb0861c Binary files /dev/null and b/asset/img/garbage_01.png differ diff --git a/asset/img/garbage_02.png b/asset/img/garbage_02.png new file mode 100644 index 0000000..cc51f71 Binary files /dev/null and b/asset/img/garbage_02.png differ diff --git a/asset/img/garbage_03.png b/asset/img/garbage_03.png new file mode 100644 index 0000000..51a23f9 Binary files /dev/null and b/asset/img/garbage_03.png differ diff --git a/asset/img/squid.png b/asset/img/squid.png new file mode 100644 index 0000000..8cce6b3 Binary files /dev/null and b/asset/img/squid.png differ diff --git a/asset/logo.png b/asset/logo.png new file mode 100644 index 0000000..5a024f3 Binary files /dev/null and b/asset/logo.png differ diff --git a/asset/logo.svg b/asset/logo.svg deleted file mode 100644 index 687a4ad..0000000 --- a/asset/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/asset/music/bgm.wav b/asset/music/bgm.wav index 85fbd6c..f65ea6f 100644 Binary files a/asset/music/bgm.wav and b/asset/music/bgm.wav differ diff --git a/asset/sounds/eat_bad_food.mp3 b/asset/sounds/eat_bad_food.mp3 new file mode 100644 index 0000000..00682a0 Binary files /dev/null and b/asset/sounds/eat_bad_food.mp3 differ diff --git a/asset/sounds/eat_good_food.mp3 b/asset/sounds/eat_good_food.mp3 new file mode 100644 index 0000000..b72f2a0 Binary files /dev/null and b/asset/sounds/eat_good_food.mp3 differ diff --git a/asset/sounds/fail.mp3 b/asset/sounds/fail.mp3 new file mode 100644 index 0000000..88fd93b Binary files /dev/null and b/asset/sounds/fail.mp3 differ diff --git a/asset/sounds/lv_down.mp3 b/asset/sounds/lv_down.mp3 new file mode 100644 index 0000000..5c0ae6b Binary files /dev/null and b/asset/sounds/lv_down.mp3 differ diff --git a/asset/sounds/lv_up.mp3 b/asset/sounds/lv_up.mp3 new file mode 100644 index 0000000..6b8d1ea Binary files /dev/null and b/asset/sounds/lv_up.mp3 differ diff --git a/asset/sounds/pass.mp3 b/asset/sounds/pass.mp3 new file mode 100644 index 0000000..9d814a7 Binary files /dev/null and b/asset/sounds/pass.mp3 differ diff --git a/blockly.json b/blockly.json index d3059b8..a1e2229 100644 --- a/blockly.json +++ b/blockly.json @@ -9,24 +9,22 @@ "SCENE_INFO": [ ["scene_info['frame']", "# frame", "# 幀數"], ["scene_info['status']", "game status", "遊戲狀態"], - ["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['squid_x']", "x coordinate of squid", "玩家角色的中心 x 座標"], + ["scene_info['squid_y']", "y coordinate of squid", "玩家角色的中心 y 座標"], + ["scene_info['squid_w']", "width of squid", "玩家角色的寬度"], + ["scene_info['squid_h']", "height of squid", "玩家角色的高度"], + ["scene_info['squid_lv']", "level of squid", "玩家角色的等級"], + ["scene_info['squid_vel']", "velocity of squid", "玩家角色的速度"], ["scene_info['score']", "score", "目前的分數"], ["scene_info['foods']", "list of foods positions", "點點的位置清單"], ["scene_info['score_to_pass']", "the score for next level", "通關分數"], ["scene_info", "dictionary of all information", "包含所有資訊的字典"] ], "CONSTANT": [ - [0, "left boundary", "左邊界"], - [800, "right boundary", "右邊界"], - [0, "top boundary", "上邊界"], - [600, "bottom boundary", "下邊界"], - [30, "player size", "玩家角色的初始大小"], - [8, "food lv1 size", "等級一食物的寬高度"], - [12, "food lv2 size", "等級二食物的高度"], - [16, "food lv3 size", "等級三食物的高度"] + + [30, "food lv1 size", "等級一食物的寬高度"], + [40, "food lv2 size", "等級二食物的高度"], + [50, "food lv3 size", "等級三食物的高度"] ], "ACTION": [ ["['UP']", "moving up", "向上移動"], diff --git a/game_config.json b/game_config.json index 29e8c25..f9f7fea 100644 --- a/game_config.json +++ b/game_config.json @@ -1,6 +1,6 @@ { "game_name": "easy_game", - "version": "2.3.4-alpha", + "version": "2.4a0", "url": "https://github.com/PAIA-Playful-AI-Arena/easy_game", "description": "這是一個吃東西小遊戲,除了讓你熟習所有基本操作,也是 PAIA 的遊戲教學範例", "logo": [ @@ -18,7 +18,7 @@ "type": "int", "min": -1, "max": 100, - "default": -1, + "default": 1, "help": "選定內建關卡,請注意,使用此設定將會覆蓋掉其他關卡設定,預設為 -1 不選擇任何關卡。" }, { "name": "level_file", diff --git a/levels/001.json b/levels/001.json index 42701af..88221cd 100644 --- a/levels/001.json +++ b/levels/001.json @@ -1,10 +1,12 @@ { "time_to_play": 300, - "playground_size": [ - 100, - 200 - ], + "playground_size_w":300, + "playground_size_h":300, "score_to_pass": 10, - "good_food_count": [3,0,0], - "bad_food_count": [0,0,0] + "food_1": 3, + "food_2": 0, + "food_3": 0, + "garbage_1": 0, + "garbage_2": 0, + "garbage_3": 0 } \ No newline at end of file diff --git a/levels/002.json b/levels/002.json index 01b35a3..df6ef2c 100644 --- a/levels/002.json +++ b/levels/002.json @@ -1,10 +1,12 @@ { "time_to_play": 300, - "playground_size": [ - 200, - 200 - ], - "score_to_pass": 15, - "good_food_count": [5,0,0], - "bad_food_count": [0,0,0] + "playground_size_w":350, + "playground_size_h":350, + "score_to_pass": 10, + "food_1": 5, + "food_2": 0, + "food_3": 0, + "garbage_1": 0, + "garbage_2": 0, + "garbage_3": 0 } \ No newline at end of file diff --git a/levels/003.json b/levels/003.json index 0363ee9..e3715c7 100644 --- a/levels/003.json +++ b/levels/003.json @@ -1,10 +1,12 @@ { "time_to_play": 500, - "playground_size": [ - 300, - 300 - ], + "playground_size_w":350, + "playground_size_h":350, "score_to_pass": 15, - "good_food_count": [6,0,0], - "bad_food_count": [2,0,0] + "food_1": 3, + "food_2": 0, + "food_3": 0, + "garbage_1": 3, + "garbage_2": 0, + "garbage_3": 0 } \ No newline at end of file diff --git a/levels/004.json b/levels/004.json index 3fd03b9..e148b40 100644 --- a/levels/004.json +++ b/levels/004.json @@ -1,10 +1,12 @@ { "time_to_play": 600, - "playground_size": [ - 300, - 300 - ], - "score_to_pass": 15, - "good_food_count": [8,0,0], - "bad_food_count": [4,0,0] + "playground_size_w":400, + "playground_size_h":400, + "score_to_pass": 20, + "food_1": 8, + "food_2": 0, + "food_3": 0, + "garbage_1": 4, + "garbage_2": 0, + "garbage_3": 0 } \ No newline at end of file diff --git a/levels/005.json b/levels/005.json index cc1c2d7..5fb3414 100644 --- a/levels/005.json +++ b/levels/005.json @@ -1,10 +1,12 @@ { "time_to_play": 800, - "playground_size": [ - 300, - 300 - ], - "score_to_pass": 15, - "good_food_count": [8,3,0], - "bad_food_count": [4,0,0] + "playground_size_w":400, + "playground_size_h":400, + "score_to_pass": 30, + "food_1": 8, + "food_2": 3, + "food_3": 0, + "garbage_1": 3, + "garbage_2": 0, + "garbage_3": 0 } \ No newline at end of file diff --git a/levels/006.json b/levels/006.json index 3632840..940e8fe 100644 --- a/levels/006.json +++ b/levels/006.json @@ -1,10 +1,12 @@ { "time_to_play": 1000, - "playground_size": [ - 400, - 400 - ], - "score_to_pass": 15, - "good_food_count": [8,4,0], - "bad_food_count": [5,2,0] + "playground_size_w":400, + "playground_size_h":400, + "score_to_pass": 30, + "food_1": 8, + "food_2": 4, + "food_3": 0, + "garbage_1": 5, + "garbage_2": 2, + "garbage_3": 0 } \ No newline at end of file diff --git a/levels/007.json b/levels/007.json index c6ca5a4..b3bf74a 100644 --- a/levels/007.json +++ b/levels/007.json @@ -1,10 +1,12 @@ { - "time_to_play": 1000, - "playground_size": [ - 400, - 400 - ], - "score_to_pass": 20, - "good_food_count": [8,4,2], - "bad_food_count": [4,4,0] + "time_to_play": 800, + "playground_size_w":500, + "playground_size_h":500, + "score_to_pass": 30, + "food_1": 8, + "food_2": 4, + "food_3": 2, + "garbage_1": 4, + "garbage_2": 2, + "garbage_3": 0 } \ No newline at end of file diff --git a/levels/008.json b/levels/008.json index b69ef36..12041c4 100644 --- a/levels/008.json +++ b/levels/008.json @@ -1,10 +1,12 @@ { "time_to_play": 1200, - "playground_size": [ - 500, - 500 - ], - "score_to_pass": 20, - "good_food_count": [6,4,2], - "bad_food_count": [6,4,2] + "playground_size_w":500, + "playground_size_h":500, + "score_to_pass": 50, + "food_1": 6, + "food_2": 4, + "food_3": 2, + "garbage_1": 6, + "garbage_2": 4, + "garbage_3": 2 } \ No newline at end of file diff --git a/levels/009.json b/levels/009.json index 67a42c4..2eecf53 100644 --- a/levels/009.json +++ b/levels/009.json @@ -1,10 +1,12 @@ { "time_to_play": 1200, - "playground_size": [ - 600, - 500 - ], - "score_to_pass": 25, - "good_food_count": [10,5,3], - "bad_food_count": [12,7,4] + "playground_size_w":600, + "playground_size_h":600, + "score_to_pass": 50, + "food_1": 6, + "food_2": 5, + "food_3": 3, + "garbage_1": 8, + "garbage_2": 8, + "garbage_3": 5 } \ No newline at end of file diff --git a/levels/010.json b/levels/010.json index 9dda519..6ec7ea2 100644 --- a/levels/010.json +++ b/levels/010.json @@ -1,10 +1,12 @@ { "time_to_play": 1200, - "playground_size": [ - 700, - 600 - ], - "score_to_pass": 25, - "good_food_count": [12,8,4], - "bad_food_count": [25,10,5] + "playground_size_w":700, + "playground_size_h":600, + "score_to_pass": 50, + "food_1": 12, + "food_2": 8, + "food_3": 4, + "garbage_1": 25, + "garbage_2": 10, + "garbage_3": 8 } \ No newline at end of file diff --git a/levels/template.json b/levels/template.json index 3a25bec..0e3feb8 100644 --- a/levels/template.json +++ b/levels/template.json @@ -1,11 +1,13 @@ { - "time_to_play": 300, - "playground_size": [ - 100, - 200 - ], + "time_to_play": 600, + "playground_size_w":650, + "playground_size_h":550, "score_to_pass": 10, - "good_food_count": [3,0,0], - "bad_food_count": [0,0,0] + "food_1": 3, + "food_2": 2, + "food_3": 5, + "garbage_1": 1, + "garbage_2": 1, + "garbage_3": 1 } \ No newline at end of file diff --git a/ml/ml_play_manual.py b/ml/ml_play_manual.py index 99ac762..b46b770 100644 --- a/ml/ml_play_manual.py +++ b/ml/ml_play_manual.py @@ -1,4 +1,6 @@ import random + +import orjson import pygame @@ -10,7 +12,7 @@ class MLPlay: """ Generate the command according to the received scene information """ - # print("AI received data from game :", json.dumps(scene_info)) + # print("AI received data from game :", orjson.dumps(scene_info)) # print(scene_info) actions = [] diff --git a/src/env.py b/src/env.py index b225bfe..17213fb 100644 --- a/src/env.py +++ b/src/env.py @@ -3,44 +3,41 @@ from os import path from mlgame.utils.enum import StringEnum # game -WIDTH = 800 +WIDTH = 900 HEIGHT = 600 -BG_COLOR = "#111111" +BG_COLOR = "#2B2B49" PG_COLOR = "#B3E5FC" -# ball -BALL_COLOR = "#FFEB3B" -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 +# ball -> squid +# BALL_COLOR = "#FFEB3B" +SQUID_W = 40 +SQUID_H = 60 +LEVEL_THRESHOLDS = [10, 30, 60, 100, 150,200] +LEVEL_PROPERTIES = { + 1: {'size_ratio': 1.0, 'vel': 10}, + 2: {'size_ratio': 1.2, 'vel': 12}, + 3: {'size_ratio': 1.4, 'vel': 15}, + 4: {'size_ratio': 1.6, 'vel': 18}, + 5: {'size_ratio': 1.8, 'vel': 21}, + 6: {'size_ratio': 2.0, 'vel': 25}, +} + + +ASSET_IMAGE_DIR = path.join(path.dirname(__file__), "../asset/img") # food class FoodTypeEnum(StringEnum): - GOOD_1 = auto() - GOOD_2 = auto() - GOOD_3 = auto() - BAD_1 = auto() - BAD_2 = auto() - BAD_3 = auto() + FOOD_1 = auto() + FOOD_2 = auto() + FOOD_3 = auto() + GARBAGE_1 = auto() + GARBAGE_2 = auto() + GARBAGE_3 = auto() -FOOD_COLOR_MAP = { - FoodTypeEnum.GOOD_1: "#009688", - FoodTypeEnum.GOOD_2: "#009688", - FoodTypeEnum.GOOD_3: "#009688", - FoodTypeEnum.BAD_1: "#FF1744", - FoodTypeEnum.BAD_2: "#FF1744", - FoodTypeEnum.BAD_3: "#FF1744" -} -FOOD_LV1_SIZE = 8 -FOOD_LV2_SIZE = 12 -FOOD_LV3_SIZE = 16 + +FOOD_LV1_SIZE = 30 +FOOD_LV2_SIZE = 40 +FOOD_LV3_SIZE = 50 # path of assets ASSET_PATH = path.join(path.dirname(__file__), "..", "asset") @@ -48,4 +45,43 @@ 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") +BG_PATH = path.join(ASSET_IMAGE_DIR, "background.png") +SQUID_PATH = path.join(ASSET_IMAGE_DIR, "squid.png") +IMG_ID_FOOD01_L = "food_01_L" +IMG_ID_FOOD02_L = "food_02_L" +IMG_ID_FOOD03_L = "food_03_L" +IMG_ID_FOOD01_R = "food_01_R" +IMG_ID_FOOD02_R = "food_02_R" +IMG_ID_FOOD03_R = "food_03_R" + +FOOD01_L_PATH = path.join(ASSET_IMAGE_DIR, "food_01_L.png") +FOOD02_L_PATH = path.join(ASSET_IMAGE_DIR, "food_02_L.png") +FOOD03_L_PATH = path.join(ASSET_IMAGE_DIR, "food_03_L.png") +FOOD01_R_PATH = path.join(ASSET_IMAGE_DIR, "food_01_R.png") +FOOD02_R_PATH = path.join(ASSET_IMAGE_DIR, "food_02_R.png") +FOOD03_R_PATH = path.join(ASSET_IMAGE_DIR, "food_03_R.png") + +GARBAGE01_PATH = path.join(ASSET_IMAGE_DIR, "garbage_01.png") +GARBAGE02_PATH = path.join(ASSET_IMAGE_DIR, "garbage_02.png") +GARBAGE03_PATH = path.join(ASSET_IMAGE_DIR, "garbage_03.png") + + +ASSET_IMG_URL = "https://raw.githubusercontent.com/PAIA-Playful-AI-Arena/easy_game/main/asset/img/" +BG_URL = ASSET_IMG_URL + "background.png" +SQUID_URL = ASSET_IMG_URL + "squid.png" +# Food URLs +FOOD01_L_URL = ASSET_IMG_URL + "food_01_L.png" +FOOD02_L_URL = ASSET_IMG_URL + "food_02_L.png" # Assuming the naming pattern is similar +FOOD03_L_URL = ASSET_IMG_URL + "food_03_L.png" +FOOD01_R_URL = ASSET_IMG_URL + "food_01_R.png" +FOOD02_R_URL = ASSET_IMG_URL + "food_02_R.png" +FOOD03_R_URL = ASSET_IMG_URL + "food_03_R.png" + +# Garbage URLs +GARBAGE01_URL = ASSET_IMG_URL + "garbage_01.png" +GARBAGE02_URL = ASSET_IMG_URL + "garbage_02.png" +GARBAGE03_URL = ASSET_IMG_URL + "garbage_03.png" +# BAR_URL = "https://raw.githubusercontent.com/PAIA/dont_touch/master/asset/image/bar.png" + +# https://raw.githubusercontent.com/PAIA-Playful-AI-Arena/easy_game/main/asset/img/background.jpg \ No newline at end of file diff --git a/src/foods.py b/src/foods.py index 3d8ea19..0cfeaf2 100644 --- a/src/foods.py +++ b/src/foods.py @@ -1,92 +1,162 @@ -import pygame.sprite +import random -from .env import FoodTypeEnum, FOOD_COLOR_MAP, FOOD_LV1_SIZE, FOOD_LV2_SIZE, FOOD_LV3_SIZE -from mlgame.view.view_model import create_rect_view_data +import pygame.sprite +from pygame import Rect + +from mlgame.view.view_model import create_image_view_data +from .env import * + +FOOD1_VEL = 1 +FOOD2_VEL = 2 +FOOD3_VEL = 4 class Food(pygame.sprite.Sprite): - def __init__(self, group): + def __init__(self, group, type: FoodTypeEnum, image_id: str, image_size=None, score: int = 1): pygame.sprite.Sprite.__init__(self, group) - self.image = pygame.Surface([8, 8]) - self.type = None - self.score = 0 - self.color = None - + if image_size is None: + image_size = [FOOD_LV1_SIZE, FOOD_LV1_SIZE] + self.image = pygame.Surface(image_size) + self.type = type + self.score = score self.rect = self.image.get_rect() self.angle = 0 + self.rect_float_x = 0 + self.rect_float_y = 0 + self.image_id = image_id - def update(self) -> None: + def update(self, *args, **kwargs) -> None: pass @property def game_object_data(self): - return create_rect_view_data( - "food", + return create_image_view_data( + self.image_id, self.rect.x, self.rect.y, self.rect.width, self.rect.height, - self.color ) + def set_center_x_and_y(self, x: int, y: int): + self.rect.centerx = x + self.rect.centery = y + self.rect_float_x = self.rect.centerx + self.rect_float_y = self.rect.centery -class GoodFoodLv1(Food): + +class Food1(Food): def __init__(self, group): - super().__init__(group) - self.image = pygame.Surface([FOOD_LV1_SIZE, FOOD_LV1_SIZE]) - self.type = FoodTypeEnum.GOOD_1 - self.color = FOOD_COLOR_MAP[self.type] - self.score = 1 - self.rect = self.image.get_rect() -class GoodFoodLv2(Food): + super().__init__(group, FoodTypeEnum.FOOD_1, IMG_ID_FOOD01_L, [FOOD_LV1_SIZE, FOOD_LV1_SIZE], + 1) + self._vel = FOOD1_VEL * random.choice([-1,1]) + + self.image_id= IMG_ID_FOOD01_R if self._vel > 0 else IMG_ID_FOOD01_L + + def update(self, playground: Rect, squid: pygame.sprite.Sprite): + + self.rect_float_x += self._vel + self.rect_float_y += random.choice([-0.3, -0.5, -0.7, 0, 0.3, 0.5, 0.7]) + self.rect.centerx = self.rect_float_x + self.rect.centery = self.rect_float_y + + if self.rect.left < playground.left and self._vel < 0.0: + self._vel = FOOD1_VEL + self.image_id = IMG_ID_FOOD01_R + elif self.rect.right > playground.right and self._vel > 0.0: + self._vel = - FOOD1_VEL + self.image_id = IMG_ID_FOOD01_L + + +class Food2(Food): def __init__(self, group): - super().__init__(group) + super().__init__(group, FoodTypeEnum.FOOD_2, IMG_ID_FOOD02_L, [FOOD_LV2_SIZE, FOOD_LV2_SIZE], 2) + self._vel = FOOD2_VEL * random.choice([-1, 1]) + self.image_id = IMG_ID_FOOD02_R if self._vel > 0 else IMG_ID_FOOD02_L - self.image = pygame.Surface([FOOD_LV2_SIZE, FOOD_LV2_SIZE]) - self.type = FoodTypeEnum.GOOD_2 - self.color = FOOD_COLOR_MAP[self.type] - self.score = 2 - self.rect = self.image.get_rect() + def update(self, playground: Rect, squid: pygame.sprite.Sprite): + self.rect_float_x += self._vel + self.rect_float_y += random.choice([-0.5, -0.7, -1, -1.3, 0, 1, 1.3, 0.3, 0.5, 0.7]) + self.rect.centerx = self.rect_float_x + self.rect.centery = self.rect_float_y -class GoodFoodLv3(Food): + if self.rect.left < playground.left and self._vel < 0.0: + self._vel = FOOD2_VEL + self.image_id = IMG_ID_FOOD02_R + elif self.rect.right > playground.right and self._vel > 0.0: + self._vel = -FOOD2_VEL + self.image_id = IMG_ID_FOOD02_L + +class Food3(Food): def __init__(self, group): - super().__init__(group) + super().__init__(group, FoodTypeEnum.FOOD_3, IMG_ID_FOOD03_L, [FOOD_LV3_SIZE, FOOD_LV3_SIZE], 4) + self._vel = FOOD3_VEL * random.choice([-1, 1]) + self.image_id = IMG_ID_FOOD03_R if self._vel > 0 else IMG_ID_FOOD03_L - self.image = pygame.Surface([FOOD_LV3_SIZE, FOOD_LV3_SIZE]) - self.type = FoodTypeEnum.GOOD_3 - self.color = FOOD_COLOR_MAP[self.type] - self.score = 4 - self.rect = self.image.get_rect() + def update(self, playground: Rect, squid: pygame.sprite.Sprite): + self.rect_float_x += self._vel + self.rect_float_y += random.choice([-0.7, -1, -1.3, -1.7, 0, 1.7, 1, 1.3, 0.3, 0.7]) + self.rect.centerx = self.rect_float_x + self.rect.centery = self.rect_float_y - - - -class BadFoodLv1(Food): + if self.rect.left < playground.left and self._vel < 0.0: + self._vel = FOOD3_VEL + self.image_id = IMG_ID_FOOD03_R + elif self.rect.right > playground.right and self._vel > 0.0: + self._vel = -FOOD3_VEL + self.image_id = IMG_ID_FOOD03_L +class Garbage1(Food): def __init__(self, group): - super().__init__(group) - self.image = pygame.Surface([FOOD_LV1_SIZE, FOOD_LV1_SIZE]) - self.type = FoodTypeEnum.BAD_1 - self.color = FOOD_COLOR_MAP[self.type] - self.score = -1 - self.rect = self.image.get_rect() - self.angle = 0 + super().__init__(group, FoodTypeEnum.GARBAGE_1, "garbage01", + [FOOD_LV1_SIZE, FOOD_LV1_SIZE], -1) + self._vel = FOOD1_VEL -class BadFoodLv2(Food): + def update(self, playground: Rect, squid: pygame.sprite.Sprite): + self.rect_float_x += random.choice([-0.3, -0.5, -0.7, 0, 0.3, 0.5, 0.7]) + self.rect_float_y += self._vel + self.rect.centerx = self.rect_float_x + self.rect.centery = self.rect_float_y + + if self.rect.top > playground.bottom: + self.rect.y = playground.top + self.rect_float_y = self.rect.centery + + pass + + +class Garbage2(Food): def __init__(self, group): - super().__init__(group) + super().__init__(group, FoodTypeEnum.GARBAGE_2, "garbage02", + [FOOD_LV2_SIZE, FOOD_LV2_SIZE], -4) + self._vel = FOOD2_VEL - self.image = pygame.Surface([FOOD_LV2_SIZE, FOOD_LV2_SIZE]) - self.type = FoodTypeEnum.BAD_2 - self.color = FOOD_COLOR_MAP[self.type] - self.score = -2 - self.rect = self.image.get_rect() + def update(self, playground: Rect, squid: pygame.sprite.Sprite): + self.rect_float_x += random.choice([-0.5, -0.7, -1, -1.3, 0, 1, 1.3, 0.3, 0.5, 0.7]) + self.rect_float_y += self._vel + self.rect.centerx = self.rect_float_x + self.rect.centery = self.rect_float_y -class BadFoodLv3(Food): + if self.rect.top > playground.bottom: + self.rect.y = playground.top + self.rect_float_y = self.rect.centery + + pass + + +class Garbage3(Food): def __init__(self, group): - super().__init__(group) + super().__init__(group, FoodTypeEnum.GARBAGE_3, "garbage03", + [FOOD_LV3_SIZE, FOOD_LV3_SIZE], -10) + self._vel = FOOD1_VEL - self.image = pygame.Surface([FOOD_LV3_SIZE, FOOD_LV3_SIZE]) - self.type = FoodTypeEnum.BAD_3 - self.color = FOOD_COLOR_MAP[self.type] - self.score = -4 - self.rect = self.image.get_rect() + def update(self, playground: Rect, squid: pygame.sprite.Sprite): + self.rect_float_x += random.choice([-0.7, -1, -1.3, -1.7, 0, 1.7, 1, 1.3, 0.3, 0.7]) + self.rect_float_y += self._vel + self.rect.centerx = self.rect_float_x + self.rect.centery = self.rect_float_y + + if self.rect.top > playground.bottom: + self.rect.y = playground.top + self.rect_float_y = self.rect.centery + + pass diff --git a/src/game.py b/src/game.py index 69dbba3..3ce88ad 100644 --- a/src/game.py +++ b/src/game.py @@ -10,11 +10,11 @@ from mlgame.view.decorator import check_game_progress, check_game_result from mlgame.view.view_model import * from .env import * from .foods import * -from .game_object import Ball +from .game_object import Squid, LevelParams from .sound_controller import SoundController -def revise_ball(ball: Ball, playground: pygame.Rect): +def revise_ball(ball: Squid, playground: pygame.Rect): ball_rect = copy.deepcopy(ball.rect) if ball_rect.left < playground.left: ball_rect.left = playground.left @@ -50,57 +50,45 @@ class EasyGame(PaiaGame): self._init_game() - def _init_game_by_file(self, level_file_path: str): try: with open(level_file_path) as f: - game_params = json.load(f) + game_params = LevelParams(**json.load(f)) + 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) + game_params = LevelParams(**json.load(f)) self._level = 1 self._level_file = "" finally: # set game params - self._playground_w = int(game_params["playground_size"][0]) - self._playground_h = int(game_params["playground_size"][1]) self.playground = pygame.Rect( 0, 0, - self._playground_w, - self._playground_h + game_params.playground_size_w, + game_params.playground_size_h ) - 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) + + self._score_to_pass = game_params.score_to_pass + self._frame_limit = game_params.time_to_play + self.playground.center = ((WIDTH-200) / 2, HEIGHT / 2) # init game - self.ball = Ball() + self.squid = Squid() self.foods.empty() - - 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._create_foods(Food1, game_params.food_1) + self._create_foods(Food2, game_params.food_2) + self._create_foods(Food3, game_params.food_3) + self._create_foods(Garbage1, game_params.garbage_1) + self._create_foods(Garbage2, game_params.garbage_2) + self._create_foods(Garbage3, game_params.garbage_3) self.frame_count = 0 self._frame_count_down = self._frame_limit self.sound_controller.play_music() - - def update(self, commands): # handle command ai_1p_cmd = commands[get_ai_name(0)] @@ -109,10 +97,10 @@ class EasyGame(PaiaGame): else: action = "NONE" - self.ball.update(action) - revise_ball(self.ball, self.playground) + self.squid.update(action) + revise_ball(self.squid, self.playground) # update sprite - self.foods.update() + self.foods.update(playground=self.playground, squid=self.squid) # handle collision @@ -127,16 +115,16 @@ class EasyGame(PaiaGame): return "RESET" def _check_foods_collision(self): - hits = pygame.sprite.spritecollide(self.ball, self.foods, True) + hits = pygame.sprite.spritecollide(self.squid, self.foods, True) if hits: for food in hits: # self.ball.score += food.score # growth play special sound - self.ball.eat_food_and_change_level_and_play_sound(food,self.sound_controller) + self.squid.eat_food_and_change_level_and_play_sound(food, self.sound_controller) self._create_foods(food.__class__, 1) - if isinstance(food, (GoodFoodLv1,GoodFoodLv2,GoodFoodLv3,)): + if isinstance(food, (Food1, Food2, Food3,)): self.sound_controller.play_eating_good() - elif isinstance(food, (BadFoodLv1,BadFoodLv2,BadFoodLv3,)): + elif isinstance(food, (Garbage1, Garbage2, Garbage3,)): self.sound_controller.play_eating_bad() def get_data_from_game_to_player(self): @@ -147,20 +135,23 @@ class EasyGame(PaiaGame): to_players_data = {} foods_data = [] for food in self.foods: - # TODO 確認要提供中心點座標還是左上角座標。 - foods_data.append({"x": food.rect.x, "y": food.rect.y, "type": food.type, "score": food.score}) + foods_data.append( + {"x": food.rect.centerx, "y": food.rect.centery, + "w":food.rect.width,"h":food.rect.height, + "type": str(food.type), "score": food.score} + ) data_to_1p = { "frame": self.frame_count, - # TODO 確認要提供中心點座標還是左上角座標。 - "player_x": self.ball.rect.centerx, - "player_y": self.ball.rect.centery, - "player_size":self.ball.rect.width, - "player_vel":self.ball.vel, + "squid_x": self.squid.rect.centerx, + "squid_y": self.squid.rect.centery, + "squid_w": self.squid.rect.width, + "squid_h": self.squid.rect.height, + "squid_vel": self.squid.vel, + "squid_lv": self.squid.lv, "foods": foods_data, - - "score": self.ball.score, - "score_to_pass":self._score_to_pass, + "score": self.squid.score, + "score_to_pass": self._score_to_pass, "status": self.get_game_status() } @@ -185,11 +176,10 @@ class EasyGame(PaiaGame): if self.is_passed: self._level += 1 self.sound_controller.play_cheer() - + else: + self.sound_controller.play_fail() self._init_game() - - pass def _init_game(self): @@ -203,7 +193,7 @@ class EasyGame(PaiaGame): @property def is_passed(self): - return self.ball.score > self._score_to_pass + return self.squid.score > self._score_to_pass @property def is_running(self): @@ -217,12 +207,29 @@ class EasyGame(PaiaGame): # background = create_asset_init_data( # "background", WIDTH, HEIGHT, bg_path, # github_raw_url="https://raw.githubusercontent.com/PAIA-Playful-AI-Arena/easy_game/main/asset/img/background.jpg") - scene_init_data = {"scene": self.scene.__dict__, - "assets": [ - # background - ], - # "audios": {} - } + + scene_init_data = { + "scene": self.scene.__dict__, + "assets": [ + create_asset_init_data("bg", 1000, 1000, BG_PATH, BG_URL), + create_asset_init_data("squid", SQUID_W, SQUID_H, SQUID_PATH, SQUID_URL), + create_asset_init_data(IMG_ID_FOOD01_L, FOOD_LV1_SIZE, FOOD_LV1_SIZE, FOOD01_L_PATH, FOOD01_L_URL), + create_asset_init_data(IMG_ID_FOOD02_L, FOOD_LV2_SIZE, FOOD_LV2_SIZE, FOOD02_L_PATH, FOOD02_L_URL), + create_asset_init_data(IMG_ID_FOOD03_L, FOOD_LV3_SIZE, FOOD_LV3_SIZE, FOOD03_L_PATH, FOOD03_L_URL), + create_asset_init_data(IMG_ID_FOOD01_R, FOOD_LV1_SIZE, FOOD_LV1_SIZE, FOOD01_R_PATH, FOOD01_R_URL), + create_asset_init_data(IMG_ID_FOOD02_R, FOOD_LV2_SIZE, FOOD_LV2_SIZE, FOOD02_R_PATH, FOOD02_R_URL), + create_asset_init_data(IMG_ID_FOOD03_R, FOOD_LV3_SIZE, FOOD_LV3_SIZE, FOOD03_R_PATH, FOOD03_R_URL), + create_asset_init_data("garbage01", FOOD_LV1_SIZE,FOOD_LV1_SIZE, GARBAGE01_PATH, GARBAGE01_URL), + create_asset_init_data("garbage02", FOOD_LV2_SIZE,FOOD_LV2_SIZE, GARBAGE02_PATH, GARBAGE02_URL), + create_asset_init_data("garbage03", FOOD_LV3_SIZE,FOOD_LV3_SIZE, GARBAGE03_PATH, GARBAGE03_URL), + ], + "background": [ + # create_image_view_data( + # 'bg', self.playground.x, self.playground.y, + # self.playground.w, self.playground.h) + ] + # "audios": {} + } return scene_init_data @check_game_progress @@ -233,21 +240,27 @@ class EasyGame(PaiaGame): foods_data = [] for food in self.foods: foods_data.append(food.game_object_data) - game_obj_list = [self.ball.game_object_data] + game_obj_list = [self.squid.game_object_data] game_obj_list.extend(foods_data) backgrounds = [ # create_image_view_data("background", 0, 0, WIDTH, HEIGHT), - create_rect_view_data( - "playground", self.playground.x, self.playground.y, - self.playground.w, self.playground.h, PG_COLOR) + # create_rect_view_data( + # "playground", self.playground.x, self.playground.y, + # self.playground.w, self.playground.h, PG_COLOR) + create_image_view_data( + 'bg', self.playground.x, self.playground.y, + self.playground.w, self.playground.h) ] foregrounds = [ ] toggle_objs = [ - 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"), + create_text_view_data(f"Lv : {self.squid.lv:4d}", 720, 50, "#EEEEEE", "24px Arial BOLD"), + create_text_view_data(f"Vel : {self.squid.vel:4d}", 720, 80, "#EEEEEE", "24px Arial BOLD"), + create_text_view_data(f"Score: {self.squid.score:04d}", 720, 110, "#EEEEEE", "24px Arial BOLD"), + create_text_view_data(f"Lv_up: {LEVEL_THRESHOLDS[self.squid.lv-1]:4d}", 720, 140, "#EEEEEE", "24px Arial BOLD"), + create_text_view_data(f"Time : {self._frame_count_down:04d}", 720, 200, "#EEEEEE", "24px Arial BOLD"), + create_text_view_data(f"ToPass: {self._score_to_pass:04d}", 720, 230, "#EEEEEE", "24px Arial BOLD"), ] scene_progress = create_scene_progress_data(frame=self.frame_count, background=backgrounds, @@ -268,7 +281,7 @@ class EasyGame(PaiaGame): { "player": get_ai_name(0), "rank": 1, - "score": self.ball.score, + "score": self.squid.score, "passed": self.is_passed } ] @@ -297,6 +310,9 @@ class EasyGame(PaiaGame): for i in range(count): # add food to group food = FOOD_TYPE(self.foods) - food.rect.centerx = random.randint(self.playground.left, self.playground.right) - food.rect.centery = random.randint(self.playground.top, self.playground.bottom) + food.set_center_x_and_y( + random.randint(self.playground.left, self.playground.right), + random.randint(self.playground.top, self.playground.bottom) + ) + pass diff --git a/src/game_object.py b/src/game_object.py index 22e2be3..1ef29d7 100644 --- a/src/game_object.py +++ b/src/game_object.py @@ -1,25 +1,49 @@ import math +from typing import List + +import pydantic import pygame.sprite -from .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 .env import * from .foods import Food from .sound_controller import SoundController -from mlgame.view.view_model import create_rect_view_data +from mlgame.view.view_model import create_rect_view_data, create_image_view_data -class Ball(pygame.sprite.Sprite): +class LevelParams(pydantic.BaseModel): + + playground_size_w: int = 300 + playground_size_h: int = 300 + score_to_pass: int = 10 + time_to_play: int = 300 + + food_1: int = 3 + food_2: int = 0 + food_3: int = 0 + garbage_1: int = 0 + garbage_2: int = 0 + garbage_3: int = 0 + + +# level_thresholds = [10, 15, 20, 25, 30] + + +class Squid(pygame.sprite.Sprite): + ANGLE_TO_RIGHT = math.radians(-10) + ANGLE_TO_LEFT = math.radians(10) + def __init__(self): pygame.sprite.Sprite.__init__(self) - self.origin_image = pygame.Surface([BALL_W, BALL_H]) + + self.origin_image = pygame.Surface([SQUID_W, SQUID_H]) self.image = self.origin_image - self.color = BALL_COLOR self.rect = self.image.get_rect() - self.rect.center = (400, 300) + self.rect.center = (350, 300) self._score = 0 - self._vel = BALL_VEL - self._lv =1 + self._vel = LEVEL_PROPERTIES[1]['vel'] + self._lv = 1 + self.angle = 0 def update(self, motion): # for motion in motions: @@ -29,11 +53,12 @@ class Ball(pygame.sprite.Sprite): self.rect.centery += self._vel elif motion == "LEFT": self.rect.centerx -= self._vel - # self.angle += 5 + self.angle = self.ANGLE_TO_LEFT elif motion == "RIGHT": self.rect.centerx += self._vel - # self.angle -= 5 - + self.angle = self.ANGLE_TO_RIGHT + else: + self.angle = 0 # self.image = pygame.transform.rotate(self.origin_image, self.angle) # print(self.angle) # center = self.rect.center @@ -42,31 +67,52 @@ class Ball(pygame.sprite.Sprite): @property def game_object_data(self): - return create_rect_view_data( - "ball", + + return create_image_view_data( + "squid", self.rect.x, self.rect.y, self.rect.width, self.rect.height, - self.color + self.angle + ) - def eat_food_and_change_level_and_play_sound(self, food: Food,sound_controller:SoundController): + 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)) + new_lv = get_current_level(self._score) + 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 + if new_lv != self._lv: + self.rect.width = SQUID_W * LEVEL_PROPERTIES[new_lv]['size_ratio'] + self.rect.height = SQUID_H * LEVEL_PROPERTIES[new_lv]['size_ratio'] + self._vel = LEVEL_PROPERTIES[new_lv]['vel'] + self._lv = new_lv @property def score(self): return self._score + @property def vel(self): - return self._vel \ No newline at end of file + return self._vel + @property + def lv(self): + return self._lv + + +def get_current_level(score: int) -> int: + """ + Determines the current level based on the player's score. + + :param score: int - The current score of the player. + :return: int - The current level of the player. + """ + + for level, threshold in enumerate(LEVEL_THRESHOLDS, start=1): + if score < threshold: + return min(level,6) + return len(LEVEL_THRESHOLDS) # Return the next level if score is beyond all thresholds diff --git a/src/sound_controller.py b/src/sound_controller.py index f755ed3..4bdc1a6 100644 --- a/src/sound_controller.py +++ b/src/sound_controller.py @@ -22,11 +22,12 @@ class SoundController(): 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")) - 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")) + self._eating_good = pygame.mixer.Sound(path.join(SOUND_PATH, "eat_good_food.mp3")) + self._eating_bad = pygame.mixer.Sound(path.join(SOUND_PATH, "eat_bad_food.mp3")) + self._pass = pygame.mixer.Sound(path.join(SOUND_PATH, "pass.mp3")) + self._fail = pygame.mixer.Sound(path.join(SOUND_PATH, "fail.mp3")) + self._lv_up = pygame.mixer.Sound(path.join(SOUND_PATH, "lv_up.mp3")) + self._lv_down = pygame.mixer.Sound(path.join(SOUND_PATH, "lv_down.mp3")) except Exception: self._is_sound_on = False @@ -43,9 +44,12 @@ class SoundController(): self._eating_bad.play() @sound_enabled def play_cheer(self): - self._cheer.play() + self._pass.play() @sound_enabled + def play_fail(self): + self._fail.play() + @sound_enabled def play_lv_up(self): self._lv_up.play()