Merge branch 'feature/add_diverse_lv_food' into develop

This commit is contained in:
Kylin_on_Mac 2023-10-10 23:37:10 +08:00
commit 92c16637af
18 changed files with 243 additions and 227 deletions

View File

@ -22,20 +22,11 @@
```python
# main.py
game = EasyGame(
time_to_play, score_to_pass,
green_food_count, red_food_count,
playground_size: list,
level: int = -1,
level: int = 1,
level_file: str = None,
sound: str = "off")
```
- `time_to_play`:遊戲執行的終止時間,單位是 frame也就是遊戲內部更新畫面的次數每更新一次 frame +1
- `green_food_count`:遊戲中綠色食物的數量。
- `red_food_count`:遊戲中紅色食物的數量。
- `score_to_pass`:遊戲通關的點數,要超過這個分數才算過關。
- `playground_size`:可移動區域的大小。 使用逗號將數字隔開 `width,height` `100,200`
- `level`: 選定內建關卡,請注意,使用此設定將會覆蓋掉上述其他關卡參數設定,預設為 -1 不選擇任何關卡。
- `level`: 選定內建關卡,預設為 1 選擇第一關。
- `level_file`: 使用外部檔案作為關卡,請注意,使用此設定將會覆蓋掉關卡編號,並且不會自動進入下一關。
- `sound`: 音效。
@ -83,10 +74,10 @@ python -m mlgame -i ./ml/ml_play_template.py ./ --time_to_play 1200 --green_food
import random
class MLPlay:
def __init__(self):
def __init__(self,ai_name,*args, **kwargs):
print("Initial ml script")
def update(self, scene_info: dict):
def update(self, scene_info: dict,,*args, **kwargs):
# print("AI received data from game :", scene_info)
@ -114,12 +105,16 @@ class MLPlay:
"foods": [
{
"x": 656,
"y": 210
"y": 210,
"type": "GOOD_1",
"score": 1
},
...,
{
"x": 371,
"y": 217
"y": 217,
"type": "BAD_1",
"score": -1
}
],
@ -131,8 +126,10 @@ class MLPlay:
- `frame`:遊戲畫面更新的編號
- `ball_x`:主角方塊的X座標,表示方塊的左邊座標值。
- `ball_y`:主角方塊的Y座標,表示方塊的上方座標值。
- `foods`:食物的清單,清單內每一個物件都是一個食物的左上方座標值
- `foods`:食物的清單,清單內每一個物件都是一個食物的左上方座標值,也會提供此食物是什麼類型和分數多少。
- `type` 食物類型: `GOOD_1`, `GOOD_2`, `GOOD_3`, `BAD_1`, `BAD_2`, `BAD_3`
- `score`:目前得到的分數
- `score_to_pass`:通關分數
- `status` 目前遊戲的狀態
- `GAME_ALIVE`:遊戲進行中
- `GAME_PASS`:遊戲通關

View File

@ -13,6 +13,7 @@
["scene_info['ball_y']", "y coordinate of ball", "球的 y 座標"],
["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": [
@ -22,8 +23,9 @@
[600, "bottom boundary", "下邊界"],
[30, "ball width", "球身的寬度"],
[30, "ball height", "球身的高度"],
[8, "food width", "食物的寬度"],
[8, "food height", "食物的高度"]
[8, "food lv1 size", "等級一食物的寬高度"],
[12, "food lv2 size", "等級二食物的高度"],
[16, "food lv3 size", "等級三食物的高度"]
],
"ACTION": [
["['UP']", "moving up", "向上移動"],

View File

@ -12,77 +12,14 @@
"max": 1
},
"game_params": [
{
"name": "time_to_play",
"verbose": "遊戲總幀數",
"type": "int",
"max": 2000,
"min": 600,
"default": 600,
"help": "set the limit of frame count , actually time will be revised according to your FPS ."
},
{
"name": "green_food_count",
"verbose": "綠色食物數量",
"type": "int",
"choices": [
5,
10,
15,
20,
25,
30,
35,
40,
45,
50
],
"help": "set the total number of points",
"default": 10
},
{
"name": "red_food_count",
"verbose": "紅色食物數量",
"type": "int",
"choices": [
5,
10,
15,
20,
25,
30,
35,
40,
45,
50
],
"help": "set the total number of points",
"default": 10
},
{
"name": "score_to_pass",
"verbose": "通關分數",
"type": "int",
"min": 1,
"max": 30,
"default": 10,
"help": "set the score to win this game "
},
{
"name": "playground_size",
"verbose": "場地尺寸",
"type": "list",
"default": "400,400",
"help": "set the size of playground "
},
{
"name": "level",
"verbose": "內建關卡編號",
"type": "int",
"min": -1,
"max": 5,
"default": "-1",
"help": "選定內建關卡,請注意,使用此設定將會覆蓋掉其他關卡設定,預設為 -1 不選擇任何關卡。"
"max": 100,
"default": 1,
"help": "選定內建關卡,預設為 1 選擇第一關。"
}, {
"name": "level_file",
"verbose": "匯入關卡檔案",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,11 @@
{
"time_to_play": 1200,
"time_to_play": 300,
"playground_size": [
500,
100,
200
]
],
"score_to_pass": 10,
"green_food_count": [3,0,0],
"black_food_count": [0,0,0]
}

View File

@ -17,10 +17,24 @@ BALL_W = 30
# food
class FoodTypeEnum(StringEnum):
GREEN = auto()
RED = auto()
FOOD_COLOR_MAP = {FoodTypeEnum.GREEN: "#009688",
FoodTypeEnum.RED: "#FF1744"}
GOOD_1 = auto()
GOOD_2 = auto()
GOOD_3 = auto()
BAD_1 = auto()
BAD_2 = auto()
BAD_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
# path of assets
ASSET_PATH = path.join(path.dirname(__file__), "..", "asset")

92
src/foods.py Normal file
View File

@ -0,0 +1,92 @@
import pygame.sprite
from games.easy_game.src.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
class Food(pygame.sprite.Sprite):
def __init__(self, group):
pygame.sprite.Sprite.__init__(self, group)
self.image = pygame.Surface([8, 8])
self.type = None
self.score = 0
self.color = None
self.rect = self.image.get_rect()
self.angle = 0
def update(self) -> None:
pass
@property
def game_object_data(self):
return create_rect_view_data(
"food",
self.rect.x,
self.rect.y,
self.rect.width,
self.rect.height,
self.color
)
class GoodFoodLv1(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):
def __init__(self, group):
super().__init__(group)
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()
class GoodFoodLv3(Food):
def __init__(self, group):
super().__init__(group)
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()
class BadFoodLv1(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
class BadFoodLv2(Food):
def __init__(self, group):
super().__init__(group)
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()
class BadFoodLv3(Food):
def __init__(self, group):
super().__init__(group)
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()

View File

@ -9,65 +9,62 @@ 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 .env import *
from .env import FoodTypeEnum
from .game_object import Ball, Food
from .foods import *
from .game_object import Ball
from .sound_controller import SoundController
def revise_ball(ball: Ball, playground: pygame.Rect):
ball_rect = copy.deepcopy(ball.rect)
if ball_rect.left < playground.left:
ball_rect.left = playground.left
elif ball_rect.right > playground.right:
ball_rect.right = playground.right
if ball_rect.top < playground.top:
ball_rect.top = playground.top
elif ball_rect.bottom > playground.bottom:
ball_rect.bottom = playground.bottom
ball.rect = ball_rect
pass
class EasyGame(PaiaGame):
"""
This is a Interface of a game
"""
def __init__(
self, time_to_play, score_to_pass, green_food_count, red_food_count,
playground_size: list,
level: int = -1,
self,
level: int = 1,
level_file: str = None,
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
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(
0, 0,
self._playground_w,
self._playground_h
)
self._green_food_count = green_food_count
self._red_food_count = red_food_count
self._score_to_pass = score_to_pass
self._frame_limit = time_to_play
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):
if path.isfile(self._level_file) or self._level_file == "":
# 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.set_game_params_by_file(level_file_path)
self._init_game_by_file(level_file_path)
def set_game_params_by_file(self, level_file_path: str):
def _init_game_by_file(self, level_file_path: str):
try:
with open(level_file_path) as f:
game_params = json.load(f)
self._set_game_params(game_params)
except:
# If the file doesn't exist, use default parameters
print("此關卡檔案不存在,遊戲將會會自動使用第一關檔案 001.json。")
@ -76,9 +73,8 @@ class EasyGame(PaiaGame):
game_params = json.load(f)
self._level = 1
self._level_file = None
self._set_game_params(game_params)
def _set_game_params(self, game_params):
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(
@ -86,22 +82,31 @@ class EasyGame(PaiaGame):
self._playground_w,
self._playground_h
)
self._green_food_count = int(game_params["green_food_count"])
self._red_food_count = int(game_params["black_food_count"])
self._green_food_count = game_params["green_food_count"]
self._red_food_count = game_params["black_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)
def init_game(self):
# init game
self.ball = Ball()
self.foods.empty()
self.score = 0
self._create_foods(self._green_food_count, FoodTypeEnum.GREEN)
self._create_foods(self._red_food_count, FoodTypeEnum.RED)
# 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])
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)]
@ -111,24 +116,13 @@ class EasyGame(PaiaGame):
action = "NONE"
self.ball.update(action)
self.revise_ball(self.ball, self.playground)
revise_ball(self.ball, self.playground)
# update sprite
self.foods.update()
# 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._check_foods_collision()
# self._timer = round(time.time() - self._begin_time, 3)
self.frame_count += 1
@ -138,6 +132,17 @@ class EasyGame(PaiaGame):
if not self.is_running:
return "RESET"
def _check_foods_collision(self):
hits = pygame.sprite.spritecollide(self.ball, self.foods, True)
if hits:
for food in hits:
self.score += food.score
self._create_foods(food.__class__, 1)
if isinstance(food, (GoodFoodLv1,GoodFoodLv2,GoodFoodLv3,)):
self.sound_controller.play_eating_good()
elif isinstance(food, (BadFoodLv1,BadFoodLv2,BadFoodLv3,)):
self.sound_controller.play_eating_bad()
def get_data_from_game_to_player(self):
"""
send something to game AI
@ -146,13 +151,16 @@ class EasyGame(PaiaGame):
to_players_data = {}
foods_data = []
for food in self.foods:
foods_data.append({"x": food.rect.x, "y": food.rect.y})
# TODO add good food and bad food
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,
"foods": foods_data,
"score": self.score,
"score_to_pass":self._score_to_pass,
"status": self.get_game_status()
}
@ -181,9 +189,9 @@ class EasyGame(PaiaGame):
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)
self._init_game_by_level(self._level)
self.init_game()
self._init_game()
pass
@ -199,7 +207,6 @@ class EasyGame(PaiaGame):
"""
Get the initial scene and object information for drawing on the web
"""
# TODO add music or sound
# bg_path = path.join(ASSET_PATH, "img/background.jpg")
# background = create_asset_init_data(
# "background", WIDTH, HEIGHT, bg_path,
@ -280,25 +287,10 @@ class EasyGame(PaiaGame):
cmd_1p.append("NONE")
return {get_ai_name(0): cmd_1p}
def _create_foods(self, count: int = 5, type: FoodTypeEnum = FoodTypeEnum.GREEN):
def _create_foods(self, FOOD_TYPE, count: int = 5):
for i in range(count):
# add food to group
food = Food(self.foods, type)
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)
pass
def revise_ball(self, ball: Ball, playground: pygame.Rect):
ball_rect = copy.deepcopy(ball.rect)
if ball_rect.left < playground.left:
ball_rect.left = playground.left
elif ball_rect.right > playground.right:
ball_rect.right = playground.right
if ball_rect.top < playground.top:
ball_rect.top = playground.top
elif ball_rect.bottom > playground.bottom:
ball_rect.bottom = playground.bottom
ball.rect = ball_rect
pass

View File

@ -1,6 +1,7 @@
import pygame.sprite
from games.easy_game.src.env import FoodTypeEnum, FOOD_COLOR_MAP, BALL_COLOR, BALL_VEL, BALL_H, BALL_W
from games.easy_game.src.env import BALL_COLOR, BALL_VEL, BALL_H, BALL_W
from games.easy_game.src.foods import Food
from mlgame.view.view_model import create_rect_view_data
@ -44,26 +45,3 @@ class Ball(pygame.sprite.Sprite):
)
class Food(pygame.sprite.Sprite):
def __init__(self, group, type: FoodTypeEnum):
pygame.sprite.Sprite.__init__(self, group)
self.image = pygame.Surface([8, 8])
self.type = type
self.color = FOOD_COLOR_MAP[type]
self.rect = self.image.get_rect()
self.angle = 0
def update(self) -> None:
pass
@property
def game_object_data(self):
return create_rect_view_data(
"food",
self.rect.x,
self.rect.y,
self.rect.width,
self.rect.height,
self.color
)