-
Notifications
You must be signed in to change notification settings - Fork 49
遊戲與 MLPlay 類別
這個章節介紹遊戲類別與 MLPlay
類別所需要提供的函式與功能。要注意每個提供的函式不能有無限迴圈,否則會造成 MLGame 無法持續執行。如何將一個現有的遊戲導入到 MLGame 中,可以參考貪食蛇的教學將遊戲導入 MLGame。
在 MLGame 中,遊戲需要提供一個遊戲類別,並含有指定的函式供 MLGame 的執行核心傳入或取得資料。
Game.__init__(init_arg_1, init_arg_2, ...)
初始化遊戲類別,所有遊戲的初始化內容都在這邊完成,像是初始化遊戲所需的 pygame
模組。
init_arg_x
是傳入給遊戲類別的初始化參數,與在 config.py
中設定的 GAME_PARAMS
順序一樣。如果沒有遊戲參數,則不用指定。例如,遊戲在 config.py
設定:
GAME_PARAMS = {
"()": {...},
"difficulty": {
"choices": ("EASY", "NORMAL"),
},
"level": {
"type": int,
}
}
那麼,遊戲類別的 __init__()
第一個參數就是 difficulty
,第二個參數是 level
:
$ python MLGame.py -i ml_play.py some_game EASY 2
class Game:
def __init__(self, difficulty, level): # Game("EASY", 2)
...
game.update(command: dict) -> str
遊戲的主要進行函式,每一次呼叫就讓遊戲更新一次(一個影格),遊戲更新畫面也是在這裡執行。
command
會傳入玩家的指令,資料類型為一個 dict。該 dict 的鍵值為機器學習端的名字,而其值就是該機器學習端傳來的指令:
# config.py
GAME_SETUP = {
...,
"ml_clients" : [
{ "name": "ml_1P" },
{ "name": "ml_2P" }
]
}
# Game class
def update(self, command: dict):
print(command.get("ml_1P")) # 輸出從 ml_1P 玩家送來的指令
print(command.get("ml_2P")) # 輸出從 ml_2P 玩家送來的指令
如果該玩家來不及傳送或是沒有傳送指令的話,則得到的值會是 None
。
如果遊戲是動態玩家人數,也就是 GAME_SETUP
中的 dynamic_ml_clients
為 True
的話,則沒被使用的機器學習端不會被包含在 command
裡:
# config.py
GAME_SETUP = {
...,
"dynamic_ml_clients": True,
"ml_clients" : [
{ "name": "ml_1P" },
{ "name": "ml_2P" },
{ "name": "ml_3P" }
]
}
# Game class
def update(self, command: dict):
print(command.get("ml_1P")) # 輸出從 ml_1P 玩家送來的指令
print(command.get("ml_2P")) # 輸出從 ml_2P 玩家送來的指令
print(command.get("ml_3P")) # 永遠是 None,因為鍵值 ml_3P 不存在於 command 裡
print(command["ml_3P"]) # KeyError
建議使用 dict.get(key[, default])
來取得 dict 裡面的值,避免因為 command 中沒有該鍵值而產生 KeyError
。
要注意的是 MLGame 並不會檢查玩家傳得指令是否有效,所以在使用之前建議先檢查其值並轉換成遊戲可用的指令。例如:
VALID_COMMANDS = ("UP", "DOWN", "LEFT", "RIGHT", "NONE")
class Game:
...
def update(self, command: dict):
if not command.get("ml_1P") or command.get("ml_1P") not in VALID_COMMANDS:
command_1P = "NONE"
...
update()
在執行完後,需要回傳指定的字串來告知 MLGame 是否該繼續執行:
-
"RESET"
:重置遊戲,讓 MLGame 呼叫reset()
-
"QUIT"
:結束遊戲,MLGame 會結束執行
class Game:
...
def update(self, command):
...
if game_status == "GAME_OVER":
return "RESET"
如果不回傳任何值,或是不是上面兩個字串,則 MLGame 會繼續執行。
game.reset()
重置遊戲。在 MLGame 從 update()
收到 "RESET"
後,會直接呼叫這個函式來重置遊戲。
game.get_player_scene_info() -> dict
產生給玩家的場景資訊,供玩家判斷或是訓練模型使用。
回傳的場景資訊必須是一個 dict,其鍵值為對應的機器學習端名字,其值為該機器學習端要收到的檔景資訊,因此可以為不同的機器學習端提供不同的場景資訊(例如:只有它可以知道的資訊):
{
"ml_1P": {
"status": "GAME_ALIVE",
"sensor_L": 10,
"sensor_R": 5,
"sensor_F": 2
},
"ml_2P": {
"status": "GAME_ALIVE",
"sensor_L": 7,
"sensor_R": 7,
"sensor_F": 12
}
}
如果提供的場景資訊缺少給正在執行的機器學習端的資訊,則 MLGame 會直接回報錯誤並停止執行。但是如果是動態玩家數量的遊戲,則可以不提供場景資訊給未被啟用的機器學習端,像是沒有該機器學習端的欄位,或是給予 None
,執行過程也不會被除存到紀錄檔中。
game.get_keyboard_command() -> dict
偵測按下的按鍵,並回傳對應的指令。這個函式用在 MLGame 的手動模式中,以模擬機器學習端的執行,用於產生紀錄檔。
get_keyboard_command()
所回傳的 dict 會直接傳給 update()
執行,因此回傳的格式必須與 MLGame 傳給 update()
的格式一樣:
def get_keyboard_command(self):
key_pressed_list = pygame.key.get_pressed()
if key_pressed_list[pygame.K_PERIOD]: cmd_1P = "SERVE_TO_LEFT"
elif key_pressed_list[pygame.K_SLASH]: cmd_1P = "SERVE_TO_RIGHT"
elif key_pressed_list[pygame.K_LEFT]: cmd_1P = "MOVE_LEFT"
elif key_pressed_list[pygame.K_RIGHT]: cmd_1P = "MOVE_RIGHT"
else: cmd_1P = "NONE"
if key_pressed_list[pygame.K_q]: cmd_2P = "SERVE_TO_LEFT"
elif key_pressed_list[pygame.K_e]: cmd_2P = "SERVE_TO_RIGHT"
elif key_pressed_list[pygame.K_a]: cmd_2P = "MOVE_LEFT"
elif key_pressed_list[pygame.K_d]: cmd_2P = "MOVE_RIGHT"
else: cmd_2P = "NONE"
return {"ml_1P": cmd_1P, "ml_2P": cmd_2P}
MLPlay
是玩家提供來玩遊戲的類別,一樣需要含有指定的函式供 MLGame 的機器學習端呼叫。所有執行於機器學習端的程式碼,都必須要有 MLPlay
類別。
MLPlay.__init__(init_arg_1, init_arg_2, ...)
初始化 MLPlay
類別。在這裡初始化所需要的模組、讀取資料等。
MLGame 的機器學習端在呼叫完這個函式後,才會告知遊戲端準備好了。
init_arg_x
是遊戲傳給玩家的參數,與 config.py
中的 GAME_SETUP
的 ml_clients
中指定的參數一樣(args
或 kwargs
),如果沒有,則不須提供。例如:遊戲在 config.py
中設定:
GAME_SETUP = {
"game": some_game,
"ml_clients": [
{ "name": "ml_1P", "args": ("1P",) },
{ "name": "ml_2P", "args": ("2P",) },
]
}
則 MLPlay
類別會需要一個參數來取得遊戲類別指定的值:
class MLPlay:
def __init__(self, side): # MLPlay("1P") for "ml_1P" player
...
mlplay.update(scene_info) -> command or "RESET"
玩家程式主要進行的函式。處理接受到的場景資訊,並回傳遊戲指定的指令或是 "RESET"
。
參數 scene_info
會是從遊戲類別傳送的場景資訊,即 game.get_player_scene_info()
所回傳的物件。
update()
回傳的指令會傳給遊戲類別使用,但如果沒有回傳任何物件(即 None
),則不會傳給遊戲類別。如果回傳的是 "RESET"
,則 MLGame 的機器學習端會直接呼叫 reset()
來重設機器學習端,因此 update()
內要能夠判定遊戲是否結束,並告知重設。
class MLPlay:
def update(self, scene_info):
if scene_info["status"] == "GAME_OVER":
return "RESET"
# do something
...
return "UP"
mlplay.reset()
重設機器學習端。在 update()
回傳 "RESET"
後會呼叫此函式。
MLGame 的機器學習端在呼叫完這個函式後,才會告知遊戲端它已經準備好下一輪遊戲了。