gapoera.envs.surakarta
Surakarta Game.
Permainan Surakarta atau Catur Surakarta berasal dari daerah Surakarta di Indonesia. Permainan ini mirip dengan catur di mana pemain yang menang adalah pemain yang berhasil memakan semua bidak lawannya. Aturan lengkap dapat dibaca pada tautan berikut.
Surakarta Board Representation:
Pada ilustrasi di bawah ini Surakarta diilustrasikan dengan bentuk grid 2D. Papan dinomori dari 0..35.
| 0 | 1 | 2 | 3 | 4 | 5 |
------------------------------
| 6 | 7 | 8 | 9 | 10 | 11 |
------------------------------
| 12 | 13 | 14 | 15 | 16 | 17 |
------------------------------
| 18 | 19 | 20 | 21 | 22 | 23 |
------------------------------
| 24 | 25 | 26 | 27 | 28 | 29 |
------------------------------
| 30 | 31 | 32 | 33 | 34 | 35 |
------------------------------
Posisi bidak atau ID Bidak dituliskan dengan menampilkan angka grid di mana posisi bidak berada, dituliskan dalam bentuk list of list. Misalnya, player 0 (bidaknya disimbolkan X) dan player 1 (bidaknya disimbolkan O) adalah sebagai berikut:
| X | 1 | O | 3 | X | 5 |
------------------------------
| X | X | X | X | 10 | 11 |
------------------------------
| 12 | 13 | 14 | 15 | 16 | 17 |
------------------------------
| 18 | 19 | O | X | O | 23 |
------------------------------
| 24 | 25 | O | 27 | 28 | 29 |
------------------------------
| 30 | 31 | O | 33 | 34 | 35 |
------------------------------
maka dituliskan dalam bentuk list of list sebagai berikut:
player 0 player 1
[[0, 4, 6, 7, 8, 9, 21], [2, 20, 22, 26, 32]]
Pada setiap waktu di permainan, kita dapat mendapat informasi:
- board (list): list of list of int. Menampilkan daftar posisi bidak setiap pemain (ID Bidak). Tampak seperti penjelasan di atas.
- current_player (int): player yang sedang bermain (0 atau 1)
- round (int): banyak ronde yang telah berjalan. 1 ronde dihitung ketika semua pemain telah melakukan aksi.
- is_over (bool):
True
jika game sudah berakhir - score (list): score dari game dengan format:
[score_player_0, score_player_1]
Surakarta Action Representation (act_id):
Sebuah aksi untuk sebuah pion dikodekan sebagai sebuah bilangan bulat antara 0-11:
- aksi 0-7: bergerak 1 langkah ke 8 arah. 0=atas, 1=kanan-atas, 2=kanan, 3=kanan-bawah, 4=bawah, dst.
- aksi 8-11: bergerak memakan dan melalui belokan. 8=small-cw, 9=large-cw, 10=small-ccw, 11=large-ccw.
API menerima aksi berupa sebuah integer dengan format act_id
yang dihitung dari (nomor_posisi x 12 + kode_aksi). Contoh: untuk menggerakkan pion di posisi 8 ke bawah, maka kode aksinya = 8 x 12 + 4 = 100
View Source
""" ## Surakarta Game. Permainan Surakarta atau Catur Surakarta berasal dari daerah Surakarta di Indonesia. Permainan ini mirip dengan catur di mana pemain yang menang adalah pemain yang berhasil memakan semua bidak lawannya. Aturan lengkap dapat dibaca pada [tautan berikut](https://en.wikipedia.org/wiki/Surakarta_(game)). ### Surakarta Board Representation: Pada ilustrasi di bawah ini Surakarta diilustrasikan dengan bentuk grid 2D. Papan dinomori dari 0..35. ``` ------------------------------ | 0 | 1 | 2 | 3 | 4 | 5 | ------------------------------ | 6 | 7 | 8 | 9 | 10 | 11 | ------------------------------ | 12 | 13 | 14 | 15 | 16 | 17 | ------------------------------ | 18 | 19 | 20 | 21 | 22 | 23 | ------------------------------ | 24 | 25 | 26 | 27 | 28 | 29 | ------------------------------ | 30 | 31 | 32 | 33 | 34 | 35 | ------------------------------ ``` Posisi bidak atau **ID Bidak** dituliskan dengan menampilkan angka grid di mana posisi bidak berada, dituliskan dalam bentuk list of list. Misalnya, player 0 (bidaknya disimbolkan X) dan player 1 (bidaknya disimbolkan O) adalah sebagai berikut: ``` ------------------------------ | X | 1 | O | 3 | X | 5 | ------------------------------ | X | X | X | X | 10 | 11 | ------------------------------ | 12 | 13 | 14 | 15 | 16 | 17 | ------------------------------ | 18 | 19 | O | X | O | 23 | ------------------------------ | 24 | 25 | O | 27 | 28 | 29 | ------------------------------ | 30 | 31 | O | 33 | 34 | 35 | ------------------------------ ``` maka dituliskan dalam bentuk list of list sebagai berikut: ``` player 0 player 1 [[0, 4, 6, 7, 8, 9, 21], [2, 20, 22, 26, 32]] ``` Pada setiap waktu di permainan, kita dapat mendapat informasi: - **board (list)**: list of list of int. Menampilkan daftar posisi bidak setiap pemain (ID Bidak). Tampak seperti penjelasan di atas. - **current_player (int)**: player yang sedang bermain (0 atau 1) - **round (int)**: banyak ronde yang telah berjalan. 1 ronde dihitung ketika semua pemain telah melakukan aksi. - **is_over (bool)**: `True` jika game sudah berakhir - **score (list)**: score dari game dengan format: `[score_player_0, score_player_1]` ### Surakarta Action Representation (act_id): Sebuah aksi **untuk sebuah pion** dikodekan sebagai sebuah bilangan bulat antara 0-11: - aksi 0-7: bergerak 1 langkah ke 8 arah. 0=atas, 1=kanan-atas, 2=kanan, 3=kanan-bawah, 4=bawah, dst. - aksi 8-11: bergerak memakan dan melalui belokan. 8=small-cw, 9=large-cw, 10=small-ccw, 11=large-ccw. API menerima aksi berupa sebuah integer dengan format `act_id` yang dihitung dari (nomor_posisi x 12 + kode_aksi). Contoh: untuk menggerakkan pion di posisi 8 ke bawah, maka kode aksinya = 8 x 12 + 4 = 100 --- """ import numpy as np from copy import deepcopy class Surakarta(object): """ Inisial parameter berikut digunakan untuk membuat game baru. --- **Parameter:** - max_round (int): maksimum round yang diperbolehkan. Default: 100 - win_reward (float): reward yang diberikan kepada player yang menang. Default: 10. - lose_reward (float): reward yang diberikan kepada player yang kalah. Default: -10. - eat_reward (float): reward yang diberikan kepada player yang memakan bidak lawannya. Default: 0.5. """ def __init__(self, max_round=750, win_reward=10., lose_reward=-10., eat_reward=0.5): self.__dir_val = [-6, -5, +1, +7, +6, +5, -1, -7] # from top clockwise self.__version__ = 2.0 self.__max_round = max_round self._set_board() self.ACT_SPACE = 432 # 0..431 (36 x (8+4)) self.WIN_R = win_reward self.LOSE_R = lose_reward self.EAT_R = eat_reward self.reset() def observe(self, player_id=0): """ return observation dari game. Observation adalah state dari game yang tampak oleh salah satu player. --- **Parameter:** - player_id (int): player id yang akan mendapatkan observation. Defaultnya adalah player 0. **Return:** - observation (dict): observation dari game. - board (list): list of list of int berukuran 6x6 yang menggambarkan kondisi papan. Papan akan bernilai 0 untuk ruang kosong, 1 untuk bidak player_id dan, -1 untuk bidak lawannya. - current_player (int): player yang sedang bermain - round (int): round yang sedang berjalan - is_over (bool): `True` jika game sudah berakhir - score (list): score dari game. Format: `[score_player_0, score_player_1]` """ board = [[0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0]] color = [1, -1] if player_id==1: color = [-1, 1] for i in range(2): for j in range(len(self.board[i])): pos = self.board[i][j] board[pos//6][pos%6]=color[i] observed_state = { "score": deepcopy(self.score), "board": board, "round": deepcopy(self.round), "current_player" : deepcopy(self.get_current_player()), "is_over": self.gameover() } return observed_state def game_state(self): """ return state dari game. State adalah state dari game yang ditampilkan di layar (dari mata pihak ketiga). --- **Return:** - state (dict): state dari game. - board (list): list of list of int. Menampilkan daftar posisi bidak setiap pemain (ID Bidak). Tampak seperti penjelasan di deskripsi di atas. - current_player (int): player yang sedang bermain - round (int): round yang sedang berjalan - is_over (bool): `True` jika game sudah berakhir - score (list): score dari game. Format: `[score_player_0, score_player_1]` """ state = { "score": deepcopy(self.score), "board": deepcopy(self.board), "round": deepcopy(self.round), "current_player" : deepcopy(self.get_current_player()), "is_over": self.gameover() } return state def set_game_state(self, state): """ Set state dari game ke state yang diberikan. --- **Parameter:** - state (dictionary): state dari game. Format seperti yang dikembalikan oleh `game_state` atau `observe`. """ self.score = deepcopy(state["score"]) self.board = deepcopy(state["board"]) self.round = deepcopy(state["round"]) def reset(self): """ Reset game ke state awal saat game belum dimulai. Fungsi ini akan mengembalikan posisi bidak, mengubah nilai menjadi 0, dan mengubah giliran pemain ke pemain ke 0 """ self.score = [0, 0] self.board = [ [0,1,2,3,4,5,6,7,8,9,10,11], # player 0 [24,25,26,27,28,29,30,31,32,33,34,35] # player 1 ] self.round = 0 def all_data(self, c): ret_val = { "game_state" : self.state(c), "valid_act" : self.valid_act(c, compact=True), "next_pos": self.valid_act(c, compact=False), "our_eatable_pos":self.get_eatable_pos(c), "enm_eatable_pos":self.get_eatable_pos(1-c), "is_over" : self.gameover() } return ret_val def get_eatable_pos(self, c): """ return a list of tuple of eatable pos, without duplicates. tuple dengan format `(pawn_id, en_pawn_id, act)` --- **Parameter:** - c (int): player id (0 untuk pemain pertama, 1 untuk pemain kedua) """ pos_list = [] for p in self.board[c]: pos_list += self._get_eatacts(p) pos_list = list(set(pos_list)) return pos_list def valid_act(self, c, compact=True): """ return a list of valid actions for player `c`. Ada 2 format yang bisa dikembalikan: - Pada `compact=False` nilai yang dikembalikan adalah list yang berisi informasi setiap bidak. Pada setiap bidak ditampilkan posisi selanjutnya (`next_post_i`) jika melakukan aksi `act_i`. ``` [(pos_pawn_1, [(next_pos_1, act_1), (next_pos_2, act_2), ...]), (pos_pawn_2, [(next_pos_1, act_1), (next_pos_2, act_2), ...]), ... ] ``` - Pada `compact=True` nilai yang dikembalikan adalah list yang berisi informasi aksi yang valid untuk setiap bidak dengan format `act_id` yang dijelaskan di deskripsi. ``` [act_id_1, act_id_2, ...] ``` --- **Parameter:** - c (int): player id (0 untuk pemain pertama, 1 untuk pemain kedua) - compact (bool): format output. Default: True """ pawns_list = [] for p in self.board[c]: act_of_p = self._get_pawn_acts(p) if(len(act_of_p)>0): pawns_list.append((p, act_of_p)) if compact: drct_compact = [] for vm in pawns_list: for d in vm[1]: drct_compact.append(vm[0]*12+d[1]) return drct_compact else: return pawns_list def _get_pawn_acts(self, pawn_id): """ return a list of ALL available next position of pawn u. Method ini mengembalikan nliai seperti yang dikembalikan pada `valid_act` dengan compact=False namun hanya untuk bidak u. --- **Parameter:** - pawn_id (int): posisi bidak (ID Bidak) yang valid """ acts_list = [] for i in range(8): if self._move_check(pawn_id, pawn_id+self.__dir_val[i]): acts_list.append((pawn_id+self.__dir_val[i], i)) for i in range(36): flag, dir = self._eat_check(pawn_id, i) if flag: acts_list.append((i, dir)) return acts_list def _get_eatacts(self, pawn_id): """ return a list of tuple `(pawn_id, en_pawn_id, act)`. Method ini mengembalikan list pasangan nilai en_pawn_id (posisi bidak lawan) dan act (aksi yang harus dilakukan pion `pawn_id`) untuk dapat memakannya. --- **Parameter:** - pawn_id (int): posisi bidak (ID Bidak) yang valid """ acts_list = [] for i in range(36): flag, dir = self._eat_check(pawn_id, i) if flag: acts_list.append((pawn_id, i, dir)) return acts_list def get_score(self): """ return current score. Score ditampilkan dalam sebuah list dengan format `[score_player_0, score_player_1]` """ return self.score def get_current_player(self) -> int: """ return current player """ return int(self.round % 2) def get_round(self) -> int: """ return current round """ return self.round def gameover(self) -> bool: """ return `True` jika game sudah berakhir. Game berakhir jika salah satu poin ini terpenuhi: - sudah mencapai round maksimum - salah satu player kehabisan bidak - tidak ada aksi yang valid """ return int(12 in self.get_score() or self.round > self.__max_round or len(self.valid_act(0))==0 or len(self.valid_act(1))==0) def _empty_check(self, u): """ return true if position u is empty """ return u not in self.board[0] and u not in self.board[1] def _diff_check(self, u, v): """ return true if pawn u and pawn v is from different team """ return (u in self.board[0] and v in self.board[1]) or \ (u in self.board[1] and v in self.board[0]) def _eat_check(self, u, v): """ return true if u can eat v """ if not self._diff_check(u, v): return False, None flag = [-1, -1, -1, -1] dir = None for j in range(4): if u in self.__curve[j] and v in self.__curve[j]: for i in self.__curve[j]: if i == u and flag[j] == -1: flag[j] = 0 elif flag[j] == 0 and i == -1: flag[j] = 1 elif flag[j] == 1 and not self._empty_check(i) and i == v: flag[j] = 2 dir = j + 8 elif flag[j] == 0 and not self._empty_check(i) and i!=-1: flag[j] = -1 elif flag[j] == 1 and not self._empty_check(i) and i!=v and i!=u: flag[j] = -1 return 2 in flag, dir def _move_check(self,u, v): """ return true if u can move to v (NO EAT) """ return (u>=0 and u<36 and v>=0 and v<36 and self.__g[u][v]==1 and self._empty_check(v)) def step(self, c, act): """ Digunakan untuk mengaplikasikan suatu aksi ke environment. Hasil dari pemanggilan fungsi ini akan mengubah kondisi dari game state. --- **Parameter:** - c (int): id player yang akan mengambil aksi. 0 untuk player pertama, 1 untuk player kedua, dst. - act (int): id aksi yang akan diambil. `act_id` dapat dibaca di deskripsi di awal. **Return:** - confirmation (bool): `True` jika aksi berhasil dilakukan, `False` jika tidak. - reward (float): reward yang diberikan kepada player `c` jika aksi berhasil dilakukan. """ if type(act) == tuple and len(act)==2: cur_pos = act[0] act = act[1] elif type(act) == int: cur_pos = act//12 act = act%12 else: raise RuntimeError("Invalid Action Format") reward = 0 if not self.gameover(): if cur_pos not in self.board[c]: return False, reward if act < 8: next_pos = cur_pos + self.__dir_val[act] if self._move_check(cur_pos, next_pos): self.board[c].remove(cur_pos) self.board[c].append(next_pos) if(self.gameover()): reward += self.WIN_R self.round += 1 return True, reward else: return False, reward else: all_eat_acts = self.get_eatacts(cur_pos) for eat_act in all_eat_acts: next_pos = eat_act[0] next_dir = eat_act[1] if act == next_dir: self.score[c] += 1 self.board[1-c].remove(next_pos) reward += self.EAT_R self.board[c].remove(cur_pos) self.board[c].append(next_pos) if(self.gameover()): reward += self.WIN_R self.round += 1 return True, reward return False, reward else: print("THE GAME IS OVER, NO MORE ACTION!") return False, reward def render(self, state): """ Digunakan untuk menampilkan tampilan game sederhana di layar (terminal) pada suatu state. --- **Parameter:** - state (dict): state dari game yang akan ditampilkan. """ pos_0 = state["board"][0] pos_1 = state["board"][1] seq = [[0,12],[12,24],[24,36]] for s,f in seq: row = "" for i in range(s,f): if i in pos_0: row += "[x] " elif i in pos_1: row += "[o] " else: row += "[ ] " if i % 6 == 5: print(row) row="" print() def _set_board(self): self.__curve = np.array([ # small cw [ 1, -1, 6, 7, 8, 9, 10, 11, -1, 4, 10, 16, 22, 28, 34, -1, 29, 28, 27, 26, 25, 24, -1, 31, 25, 19, 13, 7, 1, -1, 6, 7, 8, 9, 10, 11, -1, 4, 10, 16, 22, 28, 34, -1, 29, 28, 27, 26, 25, 24, -1, 31, 25, 19, 13, 7], # large cw [ 2, -1, 12, 13, 14, 15, 16, 17, -1, 3, 9, 15, 21, 27, 33, -1, 23, 22, 21, 20, 19, 18, -1, 32, 26, 20, 14, 8, 2, -1, 12, 13, 14, 15, 16, 17, -1, 3, 9, 15, 21, 27, 33, -1, 23, 22, 21, 20, 19, 18, -1, 32, 26, 20, 14, 8], # small ccw [ 7, 13, 19, 25, 31, -1, 24, 25, 26, 27, 28, 29, -1, 34, 28, 22, 16, 10, 4, -1, 11, 10, 9, 8, 7, 6, -1, 1, 7, 13, 19, 25, 31, -1, 24, 25, 26, 27, 28, 29, -1, 34, 28, 22, 16, 10, 4, -1, 11, 10, 9, 8, 7, 6, -1, 1], # large ccw [ 8, 14, 20, 26, 32, -1, 18, 19, 20, 21, 22, 23, -1, 33, 27, 21, 15, 9, 3, -1, 17, 16, 15, 14, 13, 12, -1, 2, 8, 14, 20, 26, 32, -1, 18, 19, 20, 21, 22, 23, -1, 33, 27, 21, 15, 9, 3, -1, 17, 16, 15, 14, 13, 12, -1, 2]], dtype=np.int16) self.__g = np.array([ [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0]], dtype=np.int16)
View Source
class Surakarta(object): """ Inisial parameter berikut digunakan untuk membuat game baru. --- **Parameter:** - max_round (int): maksimum round yang diperbolehkan. Default: 100 - win_reward (float): reward yang diberikan kepada player yang menang. Default: 10. - lose_reward (float): reward yang diberikan kepada player yang kalah. Default: -10. - eat_reward (float): reward yang diberikan kepada player yang memakan bidak lawannya. Default: 0.5. """ def __init__(self, max_round=750, win_reward=10., lose_reward=-10., eat_reward=0.5): self.__dir_val = [-6, -5, +1, +7, +6, +5, -1, -7] # from top clockwise self.__version__ = 2.0 self.__max_round = max_round self._set_board() self.ACT_SPACE = 432 # 0..431 (36 x (8+4)) self.WIN_R = win_reward self.LOSE_R = lose_reward self.EAT_R = eat_reward self.reset() def observe(self, player_id=0): """ return observation dari game. Observation adalah state dari game yang tampak oleh salah satu player. --- **Parameter:** - player_id (int): player id yang akan mendapatkan observation. Defaultnya adalah player 0. **Return:** - observation (dict): observation dari game. - board (list): list of list of int berukuran 6x6 yang menggambarkan kondisi papan. Papan akan bernilai 0 untuk ruang kosong, 1 untuk bidak player_id dan, -1 untuk bidak lawannya. - current_player (int): player yang sedang bermain - round (int): round yang sedang berjalan - is_over (bool): `True` jika game sudah berakhir - score (list): score dari game. Format: `[score_player_0, score_player_1]` """ board = [[0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0]] color = [1, -1] if player_id==1: color = [-1, 1] for i in range(2): for j in range(len(self.board[i])): pos = self.board[i][j] board[pos//6][pos%6]=color[i] observed_state = { "score": deepcopy(self.score), "board": board, "round": deepcopy(self.round), "current_player" : deepcopy(self.get_current_player()), "is_over": self.gameover() } return observed_state def game_state(self): """ return state dari game. State adalah state dari game yang ditampilkan di layar (dari mata pihak ketiga). --- **Return:** - state (dict): state dari game. - board (list): list of list of int. Menampilkan daftar posisi bidak setiap pemain (ID Bidak). Tampak seperti penjelasan di deskripsi di atas. - current_player (int): player yang sedang bermain - round (int): round yang sedang berjalan - is_over (bool): `True` jika game sudah berakhir - score (list): score dari game. Format: `[score_player_0, score_player_1]` """ state = { "score": deepcopy(self.score), "board": deepcopy(self.board), "round": deepcopy(self.round), "current_player" : deepcopy(self.get_current_player()), "is_over": self.gameover() } return state def set_game_state(self, state): """ Set state dari game ke state yang diberikan. --- **Parameter:** - state (dictionary): state dari game. Format seperti yang dikembalikan oleh `game_state` atau `observe`. """ self.score = deepcopy(state["score"]) self.board = deepcopy(state["board"]) self.round = deepcopy(state["round"]) def reset(self): """ Reset game ke state awal saat game belum dimulai. Fungsi ini akan mengembalikan posisi bidak, mengubah nilai menjadi 0, dan mengubah giliran pemain ke pemain ke 0 """ self.score = [0, 0] self.board = [ [0,1,2,3,4,5,6,7,8,9,10,11], # player 0 [24,25,26,27,28,29,30,31,32,33,34,35] # player 1 ] self.round = 0 def all_data(self, c): ret_val = { "game_state" : self.state(c), "valid_act" : self.valid_act(c, compact=True), "next_pos": self.valid_act(c, compact=False), "our_eatable_pos":self.get_eatable_pos(c), "enm_eatable_pos":self.get_eatable_pos(1-c), "is_over" : self.gameover() } return ret_val def get_eatable_pos(self, c): """ return a list of tuple of eatable pos, without duplicates. tuple dengan format `(pawn_id, en_pawn_id, act)` --- **Parameter:** - c (int): player id (0 untuk pemain pertama, 1 untuk pemain kedua) """ pos_list = [] for p in self.board[c]: pos_list += self._get_eatacts(p) pos_list = list(set(pos_list)) return pos_list def valid_act(self, c, compact=True): """ return a list of valid actions for player `c`. Ada 2 format yang bisa dikembalikan: - Pada `compact=False` nilai yang dikembalikan adalah list yang berisi informasi setiap bidak. Pada setiap bidak ditampilkan posisi selanjutnya (`next_post_i`) jika melakukan aksi `act_i`. ``` [(pos_pawn_1, [(next_pos_1, act_1), (next_pos_2, act_2), ...]), (pos_pawn_2, [(next_pos_1, act_1), (next_pos_2, act_2), ...]), ... ] ``` - Pada `compact=True` nilai yang dikembalikan adalah list yang berisi informasi aksi yang valid untuk setiap bidak dengan format `act_id` yang dijelaskan di deskripsi. ``` [act_id_1, act_id_2, ...] ``` --- **Parameter:** - c (int): player id (0 untuk pemain pertama, 1 untuk pemain kedua) - compact (bool): format output. Default: True """ pawns_list = [] for p in self.board[c]: act_of_p = self._get_pawn_acts(p) if(len(act_of_p)>0): pawns_list.append((p, act_of_p)) if compact: drct_compact = [] for vm in pawns_list: for d in vm[1]: drct_compact.append(vm[0]*12+d[1]) return drct_compact else: return pawns_list def _get_pawn_acts(self, pawn_id): """ return a list of ALL available next position of pawn u. Method ini mengembalikan nliai seperti yang dikembalikan pada `valid_act` dengan compact=False namun hanya untuk bidak u. --- **Parameter:** - pawn_id (int): posisi bidak (ID Bidak) yang valid """ acts_list = [] for i in range(8): if self._move_check(pawn_id, pawn_id+self.__dir_val[i]): acts_list.append((pawn_id+self.__dir_val[i], i)) for i in range(36): flag, dir = self._eat_check(pawn_id, i) if flag: acts_list.append((i, dir)) return acts_list def _get_eatacts(self, pawn_id): """ return a list of tuple `(pawn_id, en_pawn_id, act)`. Method ini mengembalikan list pasangan nilai en_pawn_id (posisi bidak lawan) dan act (aksi yang harus dilakukan pion `pawn_id`) untuk dapat memakannya. --- **Parameter:** - pawn_id (int): posisi bidak (ID Bidak) yang valid """ acts_list = [] for i in range(36): flag, dir = self._eat_check(pawn_id, i) if flag: acts_list.append((pawn_id, i, dir)) return acts_list def get_score(self): """ return current score. Score ditampilkan dalam sebuah list dengan format `[score_player_0, score_player_1]` """ return self.score def get_current_player(self) -> int: """ return current player """ return int(self.round % 2) def get_round(self) -> int: """ return current round """ return self.round def gameover(self) -> bool: """ return `True` jika game sudah berakhir. Game berakhir jika salah satu poin ini terpenuhi: - sudah mencapai round maksimum - salah satu player kehabisan bidak - tidak ada aksi yang valid """ return int(12 in self.get_score() or self.round > self.__max_round or len(self.valid_act(0))==0 or len(self.valid_act(1))==0) def _empty_check(self, u): """ return true if position u is empty """ return u not in self.board[0] and u not in self.board[1] def _diff_check(self, u, v): """ return true if pawn u and pawn v is from different team """ return (u in self.board[0] and v in self.board[1]) or \ (u in self.board[1] and v in self.board[0]) def _eat_check(self, u, v): """ return true if u can eat v """ if not self._diff_check(u, v): return False, None flag = [-1, -1, -1, -1] dir = None for j in range(4): if u in self.__curve[j] and v in self.__curve[j]: for i in self.__curve[j]: if i == u and flag[j] == -1: flag[j] = 0 elif flag[j] == 0 and i == -1: flag[j] = 1 elif flag[j] == 1 and not self._empty_check(i) and i == v: flag[j] = 2 dir = j + 8 elif flag[j] == 0 and not self._empty_check(i) and i!=-1: flag[j] = -1 elif flag[j] == 1 and not self._empty_check(i) and i!=v and i!=u: flag[j] = -1 return 2 in flag, dir def _move_check(self,u, v): """ return true if u can move to v (NO EAT) """ return (u>=0 and u<36 and v>=0 and v<36 and self.__g[u][v]==1 and self._empty_check(v)) def step(self, c, act): """ Digunakan untuk mengaplikasikan suatu aksi ke environment. Hasil dari pemanggilan fungsi ini akan mengubah kondisi dari game state. --- **Parameter:** - c (int): id player yang akan mengambil aksi. 0 untuk player pertama, 1 untuk player kedua, dst. - act (int): id aksi yang akan diambil. `act_id` dapat dibaca di deskripsi di awal. **Return:** - confirmation (bool): `True` jika aksi berhasil dilakukan, `False` jika tidak. - reward (float): reward yang diberikan kepada player `c` jika aksi berhasil dilakukan. """ if type(act) == tuple and len(act)==2: cur_pos = act[0] act = act[1] elif type(act) == int: cur_pos = act//12 act = act%12 else: raise RuntimeError("Invalid Action Format") reward = 0 if not self.gameover(): if cur_pos not in self.board[c]: return False, reward if act < 8: next_pos = cur_pos + self.__dir_val[act] if self._move_check(cur_pos, next_pos): self.board[c].remove(cur_pos) self.board[c].append(next_pos) if(self.gameover()): reward += self.WIN_R self.round += 1 return True, reward else: return False, reward else: all_eat_acts = self.get_eatacts(cur_pos) for eat_act in all_eat_acts: next_pos = eat_act[0] next_dir = eat_act[1] if act == next_dir: self.score[c] += 1 self.board[1-c].remove(next_pos) reward += self.EAT_R self.board[c].remove(cur_pos) self.board[c].append(next_pos) if(self.gameover()): reward += self.WIN_R self.round += 1 return True, reward return False, reward else: print("THE GAME IS OVER, NO MORE ACTION!") return False, reward def render(self, state): """ Digunakan untuk menampilkan tampilan game sederhana di layar (terminal) pada suatu state. --- **Parameter:** - state (dict): state dari game yang akan ditampilkan. """ pos_0 = state["board"][0] pos_1 = state["board"][1] seq = [[0,12],[12,24],[24,36]] for s,f in seq: row = "" for i in range(s,f): if i in pos_0: row += "[x] " elif i in pos_1: row += "[o] " else: row += "[ ] " if i % 6 == 5: print(row) row="" print() def _set_board(self): self.__curve = np.array([ # small cw [ 1, -1, 6, 7, 8, 9, 10, 11, -1, 4, 10, 16, 22, 28, 34, -1, 29, 28, 27, 26, 25, 24, -1, 31, 25, 19, 13, 7, 1, -1, 6, 7, 8, 9, 10, 11, -1, 4, 10, 16, 22, 28, 34, -1, 29, 28, 27, 26, 25, 24, -1, 31, 25, 19, 13, 7], # large cw [ 2, -1, 12, 13, 14, 15, 16, 17, -1, 3, 9, 15, 21, 27, 33, -1, 23, 22, 21, 20, 19, 18, -1, 32, 26, 20, 14, 8, 2, -1, 12, 13, 14, 15, 16, 17, -1, 3, 9, 15, 21, 27, 33, -1, 23, 22, 21, 20, 19, 18, -1, 32, 26, 20, 14, 8], # small ccw [ 7, 13, 19, 25, 31, -1, 24, 25, 26, 27, 28, 29, -1, 34, 28, 22, 16, 10, 4, -1, 11, 10, 9, 8, 7, 6, -1, 1, 7, 13, 19, 25, 31, -1, 24, 25, 26, 27, 28, 29, -1, 34, 28, 22, 16, 10, 4, -1, 11, 10, 9, 8, 7, 6, -1, 1], # large ccw [ 8, 14, 20, 26, 32, -1, 18, 19, 20, 21, 22, 23, -1, 33, 27, 21, 15, 9, 3, -1, 17, 16, 15, 14, 13, 12, -1, 2, 8, 14, 20, 26, 32, -1, 18, 19, 20, 21, 22, 23, -1, 33, 27, 21, 15, 9, 3, -1, 17, 16, 15, 14, 13, 12, -1, 2]], dtype=np.int16) self.__g = np.array([ [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0]], dtype=np.int16)
Inisial parameter berikut digunakan untuk membuat game baru.
Parameter:
- max_round (int): maksimum round yang diperbolehkan. Default: 100
- win_reward (float): reward yang diberikan kepada player yang menang. Default: 10.
- lose_reward (float): reward yang diberikan kepada player yang kalah. Default: -10.
- eat_reward (float): reward yang diberikan kepada player yang memakan bidak lawannya. Default: 0.5.
View Source
def __init__(self, max_round=750, win_reward=10., lose_reward=-10., eat_reward=0.5): self.__dir_val = [-6, -5, +1, +7, +6, +5, -1, -7] # from top clockwise self.__version__ = 2.0 self.__max_round = max_round self._set_board() self.ACT_SPACE = 432 # 0..431 (36 x (8+4)) self.WIN_R = win_reward self.LOSE_R = lose_reward self.EAT_R = eat_reward self.reset()
View Source
def observe(self, player_id=0): """ return observation dari game. Observation adalah state dari game yang tampak oleh salah satu player. --- **Parameter:** - player_id (int): player id yang akan mendapatkan observation. Defaultnya adalah player 0. **Return:** - observation (dict): observation dari game. - board (list): list of list of int berukuran 6x6 yang menggambarkan kondisi papan. Papan akan bernilai 0 untuk ruang kosong, 1 untuk bidak player_id dan, -1 untuk bidak lawannya. - current_player (int): player yang sedang bermain - round (int): round yang sedang berjalan - is_over (bool): `True` jika game sudah berakhir - score (list): score dari game. Format: `[score_player_0, score_player_1]` """ board = [[0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0]] color = [1, -1] if player_id==1: color = [-1, 1] for i in range(2): for j in range(len(self.board[i])): pos = self.board[i][j] board[pos//6][pos%6]=color[i] observed_state = { "score": deepcopy(self.score), "board": board, "round": deepcopy(self.round), "current_player" : deepcopy(self.get_current_player()), "is_over": self.gameover() } return observed_state
return observation dari game. Observation adalah state dari game yang tampak oleh salah satu player.
Parameter:
- player_id (int): player id yang akan mendapatkan observation. Defaultnya adalah player 0.
Return:
- observation (dict): observation dari game.
- board (list): list of list of int berukuran 6x6 yang menggambarkan kondisi papan. Papan akan bernilai 0 untuk ruang kosong, 1 untuk bidak player_id dan, -1 untuk bidak lawannya.
- current_player (int): player yang sedang bermain
- round (int): round yang sedang berjalan
- is_over (bool):
True
jika game sudah berakhir - score (list): score dari game. Format:
[score_player_0, score_player_1]
View Source
def game_state(self): """ return state dari game. State adalah state dari game yang ditampilkan di layar (dari mata pihak ketiga). --- **Return:** - state (dict): state dari game. - board (list): list of list of int. Menampilkan daftar posisi bidak setiap pemain (ID Bidak). Tampak seperti penjelasan di deskripsi di atas. - current_player (int): player yang sedang bermain - round (int): round yang sedang berjalan - is_over (bool): `True` jika game sudah berakhir - score (list): score dari game. Format: `[score_player_0, score_player_1]` """ state = { "score": deepcopy(self.score), "board": deepcopy(self.board), "round": deepcopy(self.round), "current_player" : deepcopy(self.get_current_player()), "is_over": self.gameover() } return state
return state dari game. State adalah state dari game yang ditampilkan di layar (dari mata pihak ketiga).
Return:
- state (dict): state dari game.
- board (list): list of list of int. Menampilkan daftar posisi bidak setiap pemain (ID Bidak). Tampak seperti penjelasan di deskripsi di atas.
- current_player (int): player yang sedang bermain
- round (int): round yang sedang berjalan
- is_over (bool):
True
jika game sudah berakhir - score (list): score dari game. Format:
[score_player_0, score_player_1]
View Source
def set_game_state(self, state): """ Set state dari game ke state yang diberikan. --- **Parameter:** - state (dictionary): state dari game. Format seperti yang dikembalikan oleh `game_state` atau `observe`. """ self.score = deepcopy(state["score"]) self.board = deepcopy(state["board"]) self.round = deepcopy(state["round"])
Set state dari game ke state yang diberikan.
Parameter:
- state (dictionary): state dari game. Format seperti yang dikembalikan oleh
game_state
atauobserve
.
View Source
def reset(self): """ Reset game ke state awal saat game belum dimulai. Fungsi ini akan mengembalikan posisi bidak, mengubah nilai menjadi 0, dan mengubah giliran pemain ke pemain ke 0 """ self.score = [0, 0] self.board = [ [0,1,2,3,4,5,6,7,8,9,10,11], # player 0 [24,25,26,27,28,29,30,31,32,33,34,35] # player 1 ] self.round = 0
Reset game ke state awal saat game belum dimulai. Fungsi ini akan mengembalikan posisi bidak, mengubah nilai menjadi 0, dan mengubah giliran pemain ke pemain ke 0
View Source
def all_data(self, c): ret_val = { "game_state" : self.state(c), "valid_act" : self.valid_act(c, compact=True), "next_pos": self.valid_act(c, compact=False), "our_eatable_pos":self.get_eatable_pos(c), "enm_eatable_pos":self.get_eatable_pos(1-c), "is_over" : self.gameover() } return ret_val
View Source
def get_eatable_pos(self, c): """ return a list of tuple of eatable pos, without duplicates. tuple dengan format `(pawn_id, en_pawn_id, act)` --- **Parameter:** - c (int): player id (0 untuk pemain pertama, 1 untuk pemain kedua) """ pos_list = [] for p in self.board[c]: pos_list += self._get_eatacts(p) pos_list = list(set(pos_list)) return pos_list
return a list of tuple of eatable pos, without duplicates.
tuple dengan format (pawn_id, en_pawn_id, act)
Parameter:
- c (int): player id (0 untuk pemain pertama, 1 untuk pemain kedua)
View Source
def valid_act(self, c, compact=True): """ return a list of valid actions for player `c`. Ada 2 format yang bisa dikembalikan: - Pada `compact=False` nilai yang dikembalikan adalah list yang berisi informasi setiap bidak. Pada setiap bidak ditampilkan posisi selanjutnya (`next_post_i`) jika melakukan aksi `act_i`. ``` [(pos_pawn_1, [(next_pos_1, act_1), (next_pos_2, act_2), ...]), (pos_pawn_2, [(next_pos_1, act_1), (next_pos_2, act_2), ...]), ... ] ``` - Pada `compact=True` nilai yang dikembalikan adalah list yang berisi informasi aksi yang valid untuk setiap bidak dengan format `act_id` yang dijelaskan di deskripsi. ``` [act_id_1, act_id_2, ...] ``` --- **Parameter:** - c (int): player id (0 untuk pemain pertama, 1 untuk pemain kedua) - compact (bool): format output. Default: True """ pawns_list = [] for p in self.board[c]: act_of_p = self._get_pawn_acts(p) if(len(act_of_p)>0): pawns_list.append((p, act_of_p)) if compact: drct_compact = [] for vm in pawns_list: for d in vm[1]: drct_compact.append(vm[0]*12+d[1]) return drct_compact else: return pawns_list
return a list of valid actions for player c
. Ada 2 format yang bisa dikembalikan:
- Pada
compact=False
nilai yang dikembalikan adalah list yang berisi informasi setiap bidak. Pada setiap bidak ditampilkan posisi selanjutnya (next_post_i
) jika melakukan aksiact_i
.
[(pos_pawn_1, [(next_pos_1, act_1), (next_pos_2, act_2), ...]),
(pos_pawn_2, [(next_pos_1, act_1), (next_pos_2, act_2), ...]),
... ]
- Pada
compact=True
nilai yang dikembalikan adalah list yang berisi informasi aksi yang valid untuk setiap bidak dengan formatact_id
yang dijelaskan di deskripsi.
[act_id_1, act_id_2, ...]
Parameter:
- c (int): player id (0 untuk pemain pertama, 1 untuk pemain kedua)
- compact (bool): format output. Default: True
View Source
def get_score(self): """ return current score. Score ditampilkan dalam sebuah list dengan format `[score_player_0, score_player_1]` """ return self.score
return current score. Score ditampilkan dalam sebuah list dengan format [score_player_0, score_player_1]
View Source
def get_current_player(self) -> int: """ return current player """ return int(self.round % 2)
return current player
View Source
def get_round(self) -> int: """ return current round """ return self.round
return current round
View Source
def gameover(self) -> bool: """ return `True` jika game sudah berakhir. Game berakhir jika salah satu poin ini terpenuhi: - sudah mencapai round maksimum - salah satu player kehabisan bidak - tidak ada aksi yang valid """ return int(12 in self.get_score() or self.round > self.__max_round or len(self.valid_act(0))==0 or len(self.valid_act(1))==0)
return True
jika game sudah berakhir. Game berakhir jika salah satu poin ini terpenuhi:
- sudah mencapai round maksimum
- salah satu player kehabisan bidak
- tidak ada aksi yang valid
View Source
def step(self, c, act): """ Digunakan untuk mengaplikasikan suatu aksi ke environment. Hasil dari pemanggilan fungsi ini akan mengubah kondisi dari game state. --- **Parameter:** - c (int): id player yang akan mengambil aksi. 0 untuk player pertama, 1 untuk player kedua, dst. - act (int): id aksi yang akan diambil. `act_id` dapat dibaca di deskripsi di awal. **Return:** - confirmation (bool): `True` jika aksi berhasil dilakukan, `False` jika tidak. - reward (float): reward yang diberikan kepada player `c` jika aksi berhasil dilakukan. """ if type(act) == tuple and len(act)==2: cur_pos = act[0] act = act[1] elif type(act) == int: cur_pos = act//12 act = act%12 else: raise RuntimeError("Invalid Action Format") reward = 0 if not self.gameover(): if cur_pos not in self.board[c]: return False, reward if act < 8: next_pos = cur_pos + self.__dir_val[act] if self._move_check(cur_pos, next_pos): self.board[c].remove(cur_pos) self.board[c].append(next_pos) if(self.gameover()): reward += self.WIN_R self.round += 1 return True, reward else: return False, reward else: all_eat_acts = self.get_eatacts(cur_pos) for eat_act in all_eat_acts: next_pos = eat_act[0] next_dir = eat_act[1] if act == next_dir: self.score[c] += 1 self.board[1-c].remove(next_pos) reward += self.EAT_R self.board[c].remove(cur_pos) self.board[c].append(next_pos) if(self.gameover()): reward += self.WIN_R self.round += 1 return True, reward return False, reward else: print("THE GAME IS OVER, NO MORE ACTION!") return False, reward
Digunakan untuk mengaplikasikan suatu aksi ke environment. Hasil dari pemanggilan fungsi ini akan mengubah kondisi dari game state.
Parameter:
- c (int): id player yang akan mengambil aksi. 0 untuk player pertama, 1 untuk player kedua, dst.
- act (int): id aksi yang akan diambil.
act_id
dapat dibaca di deskripsi di awal.
Return:
- confirmation (bool):
True
jika aksi berhasil dilakukan,False
jika tidak. - reward (float): reward yang diberikan kepada player
c
jika aksi berhasil dilakukan.
View Source
def render(self, state): """ Digunakan untuk menampilkan tampilan game sederhana di layar (terminal) pada suatu state. --- **Parameter:** - state (dict): state dari game yang akan ditampilkan. """ pos_0 = state["board"][0] pos_1 = state["board"][1] seq = [[0,12],[12,24],[24,36]] for s,f in seq: row = "" for i in range(s,f): if i in pos_0: row += "[x] " elif i in pos_1: row += "[o] " else: row += "[ ] " if i % 6 == 5: print(row) row="" print()
Digunakan untuk menampilkan tampilan game sederhana di layar (terminal) pada suatu state.
Parameter:
- state (dict): state dari game yang akan ditampilkan.