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)
#   class Surakarta:
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.
#   Surakarta(max_round=750, win_reward=10.0, lose_reward=-10.0, eat_reward=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()
#   def observe(self, player_id=0):
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]
#   def game_state(self):
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]
#   def set_game_state(self, state):
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 atau observe.
#   def reset(self):
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

#   def all_data(self, c):
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
#   def get_eatable_pos(self, c):
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)
#   def valid_act(self, c, compact=True):
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 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
#   def get_score(self):
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]

#   def get_current_player(self) -> int:
View Source
    def get_current_player(self) -> int:
        """
        return current player
        """
        return int(self.round % 2)

return current player

#   def get_round(self) -> int:
View Source
    def get_round(self) -> int:
        """
        return current round
        """
        return self.round

return current round

#   def gameover(self) -> bool:
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
#   def step(self, c, act):
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.
#   def render(self, state):
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.