Merge branch 'release/2.3.2-alpha'

This commit is contained in:
Kylin_on_Mac 2023-10-12 17:24:49 +08:00
commit 6cc58de054
21 changed files with 135 additions and 82 deletions

View File

@ -65,7 +65,8 @@ game = EasyGame(
```bash ```bash
# 在easy game中打開終端機 # 在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範例 ## AI範例
@ -100,8 +101,10 @@ class MLPlay:
```json ```json
{ {
"frame": 25, "frame": 25,
"ball_x": 425, "player_x": 425,
"ball_y": 306, "player_y": 306,
"player_size": 90,
"player_vel": 16,
"foods": [ "foods": [
{ {
"x": 656, "x": 656,
@ -124,8 +127,10 @@ class MLPlay:
``` ```
- `frame`:遊戲畫面更新的編號 - `frame`:遊戲畫面更新的編號
- `ball_x`:主角方塊的X座標,表示方塊的左邊座標值。 - `player_x`:主角方塊的X座標,表示方塊的`中心點`座標值,單位 pixel。
- `ball_y`:主角方塊的Y座標,表示方塊的上方座標值。 - `player_y`:主角方塊的Y座標,表示方塊的`中心點`座標值,單位 pixel。
- `player_size`:主角方塊的大小,表示方塊的長寬,單位 pixel。
- `player_vel`:主角方塊的速度,表示方塊每幀移動的像素,單位 pixel。
- `foods`:食物的清單,清單內每一個物件都是一個食物的左上方座標值,也會提供此食物是什麼類型和分數多少。 - `foods`:食物的清單,清單內每一個物件都是一個食物的左上方座標值,也會提供此食物是什麼類型和分數多少。
- `type` 食物類型: `GOOD_1`, `GOOD_2`, `GOOD_3`, `BAD_1`, `BAD_2`, `BAD_3` - `type` 食物類型: `GOOD_1`, `GOOD_2`, `GOOD_3`, `BAD_1`, `BAD_2`, `BAD_3`
- `score`:目前得到的分數 - `score`:目前得到的分數

BIN
asset/sounds/lv_down.wav Normal file

Binary file not shown.

BIN
asset/sounds/lv_up.wav Normal file

Binary file not shown.

View File

@ -9,8 +9,10 @@
"SCENE_INFO": [ "SCENE_INFO": [
["scene_info['frame']", "# frame", "# 幀數"], ["scene_info['frame']", "# frame", "# 幀數"],
["scene_info['status']", "game status", "遊戲狀態"], ["scene_info['status']", "game status", "遊戲狀態"],
["scene_info['ball_x']", "x coordinate of ball", "球的 x 座標"], ["scene_info['player_x']", "x coordinate of player", "玩家角色的 x 座標"],
["scene_info['ball_y']", "y coordinate of ball", "球的 y 座標"], ["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['score']", "score", "目前的分數"],
["scene_info['foods']", "list of foods positions", "點點的位置清單"], ["scene_info['foods']", "list of foods positions", "點點的位置清單"],
["scene_info['score_to_pass']", "the score for next level", "通關分數"], ["scene_info['score_to_pass']", "the score for next level", "通關分數"],
@ -21,8 +23,7 @@
[800, "right boundary", "右邊界"], [800, "right boundary", "右邊界"],
[0, "top boundary", "上邊界"], [0, "top boundary", "上邊界"],
[600, "bottom boundary", "下邊界"], [600, "bottom boundary", "下邊界"],
[30, "ball width", "球身的寬度"], [30, "player size", "玩家角色的初始大小"],
[30, "ball height", "球身的高度"],
[8, "food lv1 size", "等級一食物的寬高度"], [8, "food lv1 size", "等級一食物的寬高度"],
[12, "food lv2 size", "等級二食物的高度"], [12, "food lv2 size", "等級二食物的高度"],
[16, "food lv3 size", "等級三食物的高度"] [16, "food lv3 size", "等級三食物的高度"]

View File

@ -1,6 +1,6 @@
{ {
"game_name": "easy_game", "game_name": "easy_game",
"version": "2.3.1-alpha", "version": "2.3.2-alpha",
"url": "https://github.com/PAIA-Playful-AI-Arena/easy_game", "url": "https://github.com/PAIA-Playful-AI-Arena/easy_game",
"description": "這是一個吃東西小遊戲,除了讓你熟習所有基本操作,也是 PAIA 的遊戲教學範例", "description": "這是一個吃東西小遊戲,除了讓你熟習所有基本操作,也是 PAIA 的遊戲教學範例",
"logo": [ "logo": [
@ -18,8 +18,8 @@
"type": "int", "type": "int",
"min": -1, "min": -1,
"max": 100, "max": 100,
"default": 1, "default": -1,
"help": "選定內建關卡,預設為 1 選擇第一關。" "help": "選定內建關卡,請注意,使用此設定將會覆蓋掉其他關卡設定,預設為 -1 不選擇任何關卡。"
}, { }, {
"name": "level_file", "name": "level_file",
"verbose": "匯入關卡檔案", "verbose": "匯入關卡檔案",

View File

@ -5,6 +5,6 @@
200 200
], ],
"score_to_pass": 10, "score_to_pass": 10,
"green_food_count": [3,0,0], "good_food_count": [3,0,0],
"black_food_count": [0,0,0] "bad_food_count": [0,0,0]
} }

View File

@ -5,6 +5,6 @@
200 200
], ],
"score_to_pass": 15, "score_to_pass": 15,
"green_food_count": [5,0,0], "good_food_count": [5,0,0],
"black_food_count": [0,0,0] "bad_food_count": [0,0,0]
} }

View File

@ -5,6 +5,6 @@
300 300
], ],
"score_to_pass": 15, "score_to_pass": 15,
"green_food_count": [6,0,0], "good_food_count": [6,0,0],
"black_food_count": [2,0,0] "bad_food_count": [2,0,0]
} }

View File

@ -5,6 +5,6 @@
300 300
], ],
"score_to_pass": 15, "score_to_pass": 15,
"green_food_count": [8,0,0], "good_food_count": [8,0,0],
"black_food_count": [4,0,0] "bad_food_count": [4,0,0]
} }

View File

@ -5,6 +5,6 @@
300 300
], ],
"score_to_pass": 15, "score_to_pass": 15,
"green_food_count": [8,3,0], "good_food_count": [8,3,0],
"black_food_count": [4,0,0] "bad_food_count": [4,0,0]
} }

View File

@ -5,6 +5,6 @@
400 400
], ],
"score_to_pass": 15, "score_to_pass": 15,
"green_food_count": [8,4,0], "good_food_count": [8,4,0],
"black_food_count": [5,2,0] "bad_food_count": [5,2,0]
} }

View File

@ -5,6 +5,6 @@
400 400
], ],
"score_to_pass": 20, "score_to_pass": 20,
"green_food_count": [8,4,2], "good_food_count": [8,4,2],
"black_food_count": [4,4,0] "bad_food_count": [4,4,0]
} }

View File

@ -5,6 +5,6 @@
500 500
], ],
"score_to_pass": 20, "score_to_pass": 20,
"green_food_count": [6,4,2], "good_food_count": [6,4,2],
"black_food_count": [6,4,2] "bad_food_count": [6,4,2]
} }

View File

@ -5,6 +5,6 @@
500 500
], ],
"score_to_pass": 25, "score_to_pass": 25,
"green_food_count": [10,5,3], "good_food_count": [10,5,3],
"black_food_count": [12,7,4] "bad_food_count": [12,7,4]
} }

View File

@ -5,6 +5,6 @@
600 600
], ],
"score_to_pass": 25, "score_to_pass": 25,
"green_food_count": [12,8,4], "good_food_count": [12,8,4],
"black_food_count": [25,10,5] "bad_food_count": [25,10,5]
} }

View File

@ -5,7 +5,7 @@
200 200
], ],
"score_to_pass": 10, "score_to_pass": 10,
"green_food_count": [3,0,0], "good_food_count": [3,0,0],
"black_food_count": [0,0,0] "bad_food_count": [0,0,0]
} }

View File

@ -9,7 +9,7 @@ from src.game import EasyGame
FPS = 30 FPS = 30
if __name__ == '__main__': if __name__ == '__main__':
pygame.init() 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() scene_init_info_dict = game.get_scene_init_data()
game_view = PygameView(scene_init_info_dict) game_view = PygameView(scene_init_info_dict)
frame_count = 0 frame_count = 0

View File

@ -10,10 +10,16 @@ PG_COLOR = "#B3E5FC"
# ball # ball
BALL_COLOR = "#FFEB3B" BALL_COLOR = "#FFEB3B"
BALL_VEL = 10.5 BALL_VEL = 10
BALL_H = 30 BALL_H = 30
BALL_W = 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 # food
class FoodTypeEnum(StringEnum): class FoodTypeEnum(StringEnum):

View File

@ -36,12 +36,11 @@ class EasyGame(PaiaGame):
def __init__( def __init__(
self, self,
level: int = 1, level: int = -1,
level_file: str = None, level_file: str = "",
sound: str = "off", sound: str = "off",
*args, **kwargs): *args, **kwargs):
super().__init__(user_num=1) super().__init__(user_num=1)
# TODO reduce game config and use level file
self.game_result_state = GameResultState.FAIL self.game_result_state = GameResultState.FAIL
self.scene = Scene(width=WIDTH, height=HEIGHT, color=BG_COLOR, bias_x=0, bias_y=0) self.scene = Scene(width=WIDTH, height=HEIGHT, color=BG_COLOR, bias_x=0, bias_y=0)
self._level = level self._level = level
@ -49,17 +48,8 @@ class EasyGame(PaiaGame):
self.foods = pygame.sprite.Group() self.foods = pygame.sprite.Group()
self.sound_controller = SoundController(sound) self.sound_controller = SoundController(sound)
if path.isfile(self._level_file) or self._level_file == "": self._init_game()
# set by injected file
self._init_game_by_file(self._level_file)
pass
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): def _init_game_by_file(self, level_file_path: str):
try: try:
@ -82,8 +72,8 @@ class EasyGame(PaiaGame):
self._playground_w, self._playground_w,
self._playground_h self._playground_h
) )
self._green_food_count = game_params["green_food_count"] self._good_food_count = game_params["good_food_count"]
self._red_food_count = game_params["black_food_count"] self._bad_food_count = game_params["bad_food_count"]
self._score_to_pass = int(game_params["score_to_pass"]) self._score_to_pass = int(game_params["score_to_pass"])
self._frame_limit = int(game_params["time_to_play"]) self._frame_limit = int(game_params["time_to_play"])
self.playground.center = (WIDTH / 2, HEIGHT / 2) self.playground.center = (WIDTH / 2, HEIGHT / 2)
@ -91,15 +81,19 @@ class EasyGame(PaiaGame):
# init game # init game
self.ball = Ball() self.ball = Ball()
self.foods.empty() self.foods.empty()
self.score = 0
# todo validate food count if not isinstance(self._good_food_count,list) or len(self._good_food_count)<3:
self._create_foods(GoodFoodLv1, self._green_food_count[0]) raise Exception("你的關卡檔案格式有誤,請在'good_food_count' 欄位後面填入一個長度為3的陣列舉例 [1,2,3]")
self._create_foods(GoodFoodLv2, self._green_food_count[1]) elif not isinstance(self._bad_food_count, list) or len(self._bad_food_count) < 3:
self._create_foods(GoodFoodLv3, self._green_food_count[2]) raise Exception("你的關卡檔案格式有誤,請在'bad_food_count' 欄位後面填入一個長度為3的陣列舉例 [1,2,3]")
self._create_foods(BadFoodLv1, self._red_food_count[0])
self._create_foods(BadFoodLv2, self._red_food_count[1]) else:
self._create_foods(BadFoodLv3, self._red_food_count[2]) 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 = 0
self._frame_count_down = self._frame_limit self._frame_count_down = self._frame_limit
@ -136,7 +130,9 @@ class EasyGame(PaiaGame):
hits = pygame.sprite.spritecollide(self.ball, self.foods, True) hits = pygame.sprite.spritecollide(self.ball, self.foods, True)
if hits: if hits:
for food in 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) self._create_foods(food.__class__, 1)
if isinstance(food, (GoodFoodLv1,GoodFoodLv2,GoodFoodLv3,)): if isinstance(food, (GoodFoodLv1,GoodFoodLv2,GoodFoodLv3,)):
self.sound_controller.play_eating_good() self.sound_controller.play_eating_good()
@ -151,17 +147,22 @@ class EasyGame(PaiaGame):
to_players_data = {} to_players_data = {}
foods_data = [] foods_data = []
for food in self.foods: 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}) foods_data.append({"x": food.rect.x, "y": food.rect.y, "type": food.type, "score": food.score})
data_to_1p = { data_to_1p = {
"frame": self.frame_count, "frame": self.frame_count,
"ball_x": self.ball.rect.centerx, # TODO 確認要提供中心點座標還是左上角座標。
"ball_y": self.ball.rect.centery, "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, "foods": foods_data,
"score": self.score,
"score": self.ball.score,
"score_to_pass":self._score_to_pass, "score_to_pass":self._score_to_pass,
"status": self.get_game_status() "status": self.get_game_status()
} }
to_players_data[get_ai_name(0)] = data_to_1p to_players_data[get_ai_name(0)] = data_to_1p
@ -182,22 +183,27 @@ class EasyGame(PaiaGame):
def reset(self): def reset(self):
if self.is_passed: 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._level += 1
self._init_game_by_level(self._level) self.sound_controller.play_cheer()
self._init_game() self._init_game()
pass 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 @property
def is_passed(self): def is_passed(self):
return self.score > self._score_to_pass return self.ball.score > self._score_to_pass
@property @property
def is_running(self): def is_running(self):
@ -239,7 +245,7 @@ class EasyGame(PaiaGame):
] ]
toggle_objs = [ 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" 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" Time:{self._frame_count_down:04d}", 600, 150, "#FF5722", "24px Arial BOLD"),
@ -262,7 +268,7 @@ class EasyGame(PaiaGame):
{ {
"player": get_ai_name(0), "player": get_ai_name(0),
"rank": 1, "rank": 1,
"score": self.score, "score": self.ball.score,
"passed": self.is_passed "passed": self.is_passed
} }
] ]

View File

@ -1,7 +1,11 @@
import math
import pygame.sprite 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.foods import Food
from games.easy_game.src.sound_controller import SoundController
from mlgame.view.view_model import create_rect_view_data from mlgame.view.view_model import create_rect_view_data
@ -13,20 +17,23 @@ class Ball(pygame.sprite.Sprite):
self.color = BALL_COLOR self.color = BALL_COLOR
self.rect = self.image.get_rect() self.rect = self.image.get_rect()
self.rect.center = (400, 300) self.rect.center = (400, 300)
self._score = 0
self._vel = BALL_VEL
self._lv =1
def update(self, motion): def update(self, motion):
# for motion in motions: # for motion in motions:
if motion == "UP": if motion == "UP":
self.rect.centery -= BALL_VEL self.rect.centery -= self._vel
elif motion == "DOWN": elif motion == "DOWN":
self.rect.centery += BALL_VEL self.rect.centery += self._vel
elif motion == "LEFT": elif motion == "LEFT":
self.rect.centerx -= BALL_VEL self.rect.centerx -= self._vel
# self.angle += 5 # self.angle += 5
elif motion == "RIGHT": elif motion == "RIGHT":
self.rect.centerx += BALL_VEL self.rect.centerx += self._vel
# self.angle -= 5 # self.angle -= 5
# self.image = pygame.transform.rotate(self.origin_image, self.angle) # self.image = pygame.transform.rotate(self.origin_image, self.angle)
# print(self.angle) # print(self.angle)
# center = self.rect.center # center = self.rect.center
@ -44,4 +51,22 @@ class Ball(pygame.sprite.Sprite):
self.color 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

View File

@ -25,6 +25,8 @@ class SoundController():
self._eating_good = pygame.mixer.Sound(path.join(SOUND_PATH, "Coin.wav")) 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._eating_bad = pygame.mixer.Sound(path.join(SOUND_PATH, "Low Boing.wav"))
self._cheer = pygame.mixer.Sound(path.join(SOUND_PATH, "Cheer.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: except Exception:
self._is_sound_on = False self._is_sound_on = False
@ -42,3 +44,11 @@ class SoundController():
@sound_enabled @sound_enabled
def play_cheer(self): def play_cheer(self):
self._cheer.play() 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()