gapoera.envs.mancala

Mancala Game.

Permainan Mancala di Indonesia dikenal dengan nama Dakon / Congklak. Sebuah mancala memiliki tujuh buah lubang / lumbung di kedua sisinya. Selain itu ada dua lumbung yang berada di sisi kanan dan kiri yang digunakan untuk penilaian. Pemain secara bergiliran memilih salah satu lubang di antara tujuh lubang di depannya, lalu mengambil seluruh biji di dalamnya; dan satu-persatu memasukkan ke lubang lain secara berlawanan arah jarum jam. Ada banyak variasi aturan yang dikenal masyarakat. Pada permainan ini digunakan aturan "Kalah".

Mancala Board Representation:

Pada ilustrasi di bawah ini Mancala diilustrasikan dengan bentuk sederhana dengna tujuh buah lubang / lumbung di kedua sisinya. Setiap lubang dinomori dari 1 sammpai 7 dari kiri ke kanan, dan lumbung penilaian yang berada di ujung kanan diberi nomor 8. Penomoran dilakukan pada kedua sisi dari sudut pandang pemain. Sehingga tampak seperti di bawah. Tanda ">" dan "<" menandankan arah pergerakan biji.

    <<<<< player 1 <<<<<
    [7, 6, 5, 4, 3, 2, 1]                  player 0                   player 1
[8]                     [8]   -->   [[1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7, 8]]
    [1, 2, 3, 4, 5, 6, 7]
    >>>>> player 0 >>>>>

Pada setiap waktu di permainan, kita mendapat informasi:

  • board (list): list of list of int. Tampak seperti di atas bagian kanan.
  • 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]

Mancala Action Representation (act_id):

Pada Mancala, aksi yang dapat dipilih adalah nomor lubang dimana dia akan mengambil biji-bijinya [1..len_board]. Lubang paling kiri di sisi pemain berindeks 1.

Jika suatu lubang tidak memiliki biji di dalamnya, maka lubang tersebut tidak dapat dipilih.


View Source
"""
## Mancala Game. 

Permainan Mancala di Indonesia dikenal dengan nama Dakon / Congklak. Sebuah mancala memiliki tujuh buah lubang / lumbung di kedua sisinya.
Selain itu ada dua lumbung yang berada di sisi kanan dan kiri yang digunakan untuk penilaian. Pemain secara bergiliran memilih 
salah satu lubang di antara tujuh lubang di depannya, lalu mengambil seluruh biji di dalamnya; dan satu-persatu memasukkan ke lubang lain secara 
berlawanan arah jarum jam. Ada banyak variasi aturan yang dikenal masyarakat. Pada permainan ini digunakan [aturan "Kalah"](https://en.wikipedia.org/wiki/Kalah_(board_game)).

### Mancala Board Representation:

Pada ilustrasi di bawah ini Mancala diilustrasikan dengan bentuk sederhana dengna tujuh buah lubang / lumbung di kedua sisinya.
Setiap lubang dinomori dari 1 sammpai 7 dari kiri ke kanan, dan lumbung penilaian yang berada di ujung kanan diberi nomor 8. Penomoran
dilakukan pada kedua sisi dari sudut pandang pemain. Sehingga tampak seperti di bawah. Tanda ">" dan "<" menandankan arah pergerakan biji.

```
    <<<<< player 1 <<<<<
    [7, 6, 5, 4, 3, 2, 1]                  player 0                   player 1
[8]                     [8]   -->   [[1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7, 8]]
    [1, 2, 3, 4, 5, 6, 7]
    >>>>> player 0 >>>>>
```

Pada setiap waktu di permainan, kita mendapat informasi:

- **board (list)**: list of list of int. Tampak seperti di atas bagian kanan.
- **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]`

### Mancala Action Representation (act_id):

Pada Mancala, aksi yang dapat dipilih adalah nomor lubang dimana dia akan mengambil biji-bijinya [1..`len_board`].
Lubang paling kiri **di sisi pemain** berindeks 1.

Jika suatu lubang tidak memiliki biji di dalamnya, maka lubang tersebut tidak dapat dipilih.

---
"""

import numpy as np
from copy import deepcopy


class Mancala(object):
    
    def __init__(self, max_round=100, 
                win_reward=10.,
                lose_reward=-10.,
                len_board=7,
                stone_each=7):

        """
        Inisial parameter berikut digunakan untuk membuat game baru. Game mancala pada Gapoera dapat diatur berapa banyak lubang pada
        setiap sisinya dan berapa banyak biji yang awalnya ada pada lubang.

        ---

        **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.
        - len_board (int): panjang board. Default: 7
        - stone_each (int): biji yang diberikan pada setiap lubang di sisi player. Default: 7

        """
        
        self.__version__ = 1.0
        self.__max_round = max_round        
        self.in_simulation = False

        self.WIN_R = win_reward
        self.LOSE_R = lose_reward

        self.len_board=len_board
        self.stone_each=stone_each
        
        self.reset()

    def observe(self, player_id=0) -> dict:
        """
        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. dimensi 0: player 0, dimensi 1: player 1
            - 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 = deepcopy(self.board)
        if player_id==1:
            board = [self.board[1], self.board[0]]

        observed_state = {
            "board" : board,
            "current_player" : deepcopy(self.current_player),
            "round" : deepcopy(self.round),
            "is_over": self.gameover(),
            "score": self.get_score()
        }
        return observed_state

    def game_state(self) -> dict:
        """
        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. dimensi 0: player 0, dimensi 1: player 1
            - 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 = {
            "board" : deepcopy(self.board),
            "current_player" : deepcopy(self.current_player),
            "round" : deepcopy(self.round),
            "is_over": self.gameover(),
            "score": self.get_score()
        }
        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.board = deepcopy(state["board"])
        self.current_player = deepcopy(state["current_player"])
        self.round = deepcopy(state["round"])

    def reset(self):
        """
        Reset game ke state awal saat game belum dimulai. Fungsi ini akan mengambalikan posisi biji, mengubah nilai menjadi 0, dan mengubah giliran pemain ke pemain ke 0
        """
        self.board = [[self.stone_each]*(self.len_board+1),
                      [self.stone_each]*(self.len_board+1)]
        self.board[0][self.len_board]=0
        self.board[1][self.len_board]=0
        self.current_player = 0
        self.round = 0


    def valid_act(self, c) -> list:
        """
        return list of valid actions for player `c`. List yang diberikan berisi elemen dengan nilai antara 1..`len_board` yang menunjukkan
        represnetasi lubang mana yang valid untuk dimainkan (jumlah biji tidak 0).

        ---

        **Parameter:**
        - c (int): player id

        """
        acts = []
        for i in range(self.len_board):
            if self.board[c][i]!=0:
                acts.append(i+1) # +1 for readability
        return acts

    def get_score(self) -> list:
        """
        return score. Score ditampilkan dalam sebuah list dengan format `[score_player_0, score_player_1]`
        
        """
        return [self.board[0][self.len_board], self.board[1][self.len_board]]

    def get_current_player(self) -> int:
        """
        return current player, 0 untuk player pertama, 1 untuk player kedua.
        """
        return self.current_player
    
    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
        - player meletakkan biji terakhir di sisinya ke kotak paling kanan
        - tidak ada aksi yang dapat dipilih oleh salah satu player
        """
        return int((sum(self.get_score()) == self.len_board*2*self.stone_each) or \
                (self.round > self.__max_round) or \
                (len(self.valid_act(0))==0 or len(self.valid_act(1))==0))

    def step(self, c, act) -> tuple:
        """
        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. Deskripsi id aksi dapat dibaca di `valid_act()`.

        **Return:**
        - confirmation (bool): `True` jika aksi berhasil dilakukan, `False` jika tidak.
        - reward (float): reward yang diberikan kepada player `c` jika aksi berhasil dilakukan.
    
        """

        act -= 1  # -1 for readability
        
        if act < 0 or act > self.len_board-1:
            return False, 0

        if self.board[c][act]==0:
            return False, 0
        
        if self.current_player != c:
            return False, 0
        
        self.round += 1
        bonus_move = False
        taken = self.board[c][act]
        self.board[c][act] = 0
        row = c
        i = act+1
        prev = self.board[row][self.len_board]
        while taken!=0:
            if row==c or (row!=c and i!=self.len_board): # put the stone or not
                self.board[row][i] += 1
                taken -= 1

            if taken==0 and i==self.len_board and row==c: # get the bonus turn or not
                bonus_move = True

            elif taken==0 and self.board[row][i]==1 and row==c: # take enemy stone or not
                self.board[row][self.len_board] += self.board[1-row][self.len_board-i-1]+1
                self.board[1-row][self.len_board-i-1] = 0
                self.board[row][i] = 0
            
            elif (row==c and i==self.len_board) or (row!=c and i==self.len_board-1): # changing row
                row = 1-row
                i = -1
            
            if taken==0 and (len(self.valid_act(0))==0 or len(self.valid_act(1))==0):
                for j in range(2):
                    for i in range(self.len_board):
                        self.board[j][self.len_board] += self.board[j][i]
                        self.board[j][i] = 0

            i += 1

        if not bonus_move:
            self.current_player = 1-c

        reward = self.board[c][self.len_board] - prev
        if self.gameover():
            if self.board[c][self.len_board] > self.board[1-c][self.len_board]:
                reward += self.WIN_R
            else:
                reward += self.LOSE_R

        return True, reward

    def render(self, state=None):
        """
        Digunakan untuk menampilkan tampilan game sederhana di layar (terminal) pada suatu state.

        ---

        **Parameter:**
        - state (dict): state dari game yang akan ditampilkan. Defaultnya akan ambil dari `self.game_state()`
    
        """
        if state is None:
            state = self.game_state()
        print(" p0  ", end="")
        for i in range(self.len_board):
            print("["+" "*int(state["board"][0][self.len_board-i-1]<=9)+str(state["board"][0][self.len_board-i-1])+"]",end="")
        print("\n   ["+str(state["board"][0][self.len_board])+"]" + " "*(self.len_board*4-2) + "["+str(state["board"][1][self.len_board])+"]")
        print(" p1  ", end="")
        for i in range(self.len_board):
            print("["+" "*int(state["board"][1][i]<=9)+str(state["board"][1][i])+"]",end="")
        print()
#   class Mancala:
View Source
class Mancala(object):
    
    def __init__(self, max_round=100, 
                win_reward=10.,
                lose_reward=-10.,
                len_board=7,
                stone_each=7):

        """
        Inisial parameter berikut digunakan untuk membuat game baru. Game mancala pada Gapoera dapat diatur berapa banyak lubang pada
        setiap sisinya dan berapa banyak biji yang awalnya ada pada lubang.

        ---

        **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.
        - len_board (int): panjang board. Default: 7
        - stone_each (int): biji yang diberikan pada setiap lubang di sisi player. Default: 7

        """
        
        self.__version__ = 1.0
        self.__max_round = max_round        
        self.in_simulation = False

        self.WIN_R = win_reward
        self.LOSE_R = lose_reward

        self.len_board=len_board
        self.stone_each=stone_each
        
        self.reset()

    def observe(self, player_id=0) -> dict:
        """
        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. dimensi 0: player 0, dimensi 1: player 1
            - 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 = deepcopy(self.board)
        if player_id==1:
            board = [self.board[1], self.board[0]]

        observed_state = {
            "board" : board,
            "current_player" : deepcopy(self.current_player),
            "round" : deepcopy(self.round),
            "is_over": self.gameover(),
            "score": self.get_score()
        }
        return observed_state

    def game_state(self) -> dict:
        """
        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. dimensi 0: player 0, dimensi 1: player 1
            - 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 = {
            "board" : deepcopy(self.board),
            "current_player" : deepcopy(self.current_player),
            "round" : deepcopy(self.round),
            "is_over": self.gameover(),
            "score": self.get_score()
        }
        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.board = deepcopy(state["board"])
        self.current_player = deepcopy(state["current_player"])
        self.round = deepcopy(state["round"])

    def reset(self):
        """
        Reset game ke state awal saat game belum dimulai. Fungsi ini akan mengambalikan posisi biji, mengubah nilai menjadi 0, dan mengubah giliran pemain ke pemain ke 0
        """
        self.board = [[self.stone_each]*(self.len_board+1),
                      [self.stone_each]*(self.len_board+1)]
        self.board[0][self.len_board]=0
        self.board[1][self.len_board]=0
        self.current_player = 0
        self.round = 0


    def valid_act(self, c) -> list:
        """
        return list of valid actions for player `c`. List yang diberikan berisi elemen dengan nilai antara 1..`len_board` yang menunjukkan
        represnetasi lubang mana yang valid untuk dimainkan (jumlah biji tidak 0).

        ---

        **Parameter:**
        - c (int): player id

        """
        acts = []
        for i in range(self.len_board):
            if self.board[c][i]!=0:
                acts.append(i+1) # +1 for readability
        return acts

    def get_score(self) -> list:
        """
        return score. Score ditampilkan dalam sebuah list dengan format `[score_player_0, score_player_1]`
        
        """
        return [self.board[0][self.len_board], self.board[1][self.len_board]]

    def get_current_player(self) -> int:
        """
        return current player, 0 untuk player pertama, 1 untuk player kedua.
        """
        return self.current_player
    
    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
        - player meletakkan biji terakhir di sisinya ke kotak paling kanan
        - tidak ada aksi yang dapat dipilih oleh salah satu player
        """
        return int((sum(self.get_score()) == self.len_board*2*self.stone_each) or \
                (self.round > self.__max_round) or \
                (len(self.valid_act(0))==0 or len(self.valid_act(1))==0))

    def step(self, c, act) -> tuple:
        """
        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. Deskripsi id aksi dapat dibaca di `valid_act()`.

        **Return:**
        - confirmation (bool): `True` jika aksi berhasil dilakukan, `False` jika tidak.
        - reward (float): reward yang diberikan kepada player `c` jika aksi berhasil dilakukan.
    
        """

        act -= 1  # -1 for readability
        
        if act < 0 or act > self.len_board-1:
            return False, 0

        if self.board[c][act]==0:
            return False, 0
        
        if self.current_player != c:
            return False, 0
        
        self.round += 1
        bonus_move = False
        taken = self.board[c][act]
        self.board[c][act] = 0
        row = c
        i = act+1
        prev = self.board[row][self.len_board]
        while taken!=0:
            if row==c or (row!=c and i!=self.len_board): # put the stone or not
                self.board[row][i] += 1
                taken -= 1

            if taken==0 and i==self.len_board and row==c: # get the bonus turn or not
                bonus_move = True

            elif taken==0 and self.board[row][i]==1 and row==c: # take enemy stone or not
                self.board[row][self.len_board] += self.board[1-row][self.len_board-i-1]+1
                self.board[1-row][self.len_board-i-1] = 0
                self.board[row][i] = 0
            
            elif (row==c and i==self.len_board) or (row!=c and i==self.len_board-1): # changing row
                row = 1-row
                i = -1
            
            if taken==0 and (len(self.valid_act(0))==0 or len(self.valid_act(1))==0):
                for j in range(2):
                    for i in range(self.len_board):
                        self.board[j][self.len_board] += self.board[j][i]
                        self.board[j][i] = 0

            i += 1

        if not bonus_move:
            self.current_player = 1-c

        reward = self.board[c][self.len_board] - prev
        if self.gameover():
            if self.board[c][self.len_board] > self.board[1-c][self.len_board]:
                reward += self.WIN_R
            else:
                reward += self.LOSE_R

        return True, reward

    def render(self, state=None):
        """
        Digunakan untuk menampilkan tampilan game sederhana di layar (terminal) pada suatu state.

        ---

        **Parameter:**
        - state (dict): state dari game yang akan ditampilkan. Defaultnya akan ambil dari `self.game_state()`
    
        """
        if state is None:
            state = self.game_state()
        print(" p0  ", end="")
        for i in range(self.len_board):
            print("["+" "*int(state["board"][0][self.len_board-i-1]<=9)+str(state["board"][0][self.len_board-i-1])+"]",end="")
        print("\n   ["+str(state["board"][0][self.len_board])+"]" + " "*(self.len_board*4-2) + "["+str(state["board"][1][self.len_board])+"]")
        print(" p1  ", end="")
        for i in range(self.len_board):
            print("["+" "*int(state["board"][1][i]<=9)+str(state["board"][1][i])+"]",end="")
        print()
#   Mancala( max_round=100, win_reward=10.0, lose_reward=-10.0, len_board=7, stone_each=7 )
View Source
    def __init__(self, max_round=100, 
                win_reward=10.,
                lose_reward=-10.,
                len_board=7,
                stone_each=7):

        """
        Inisial parameter berikut digunakan untuk membuat game baru. Game mancala pada Gapoera dapat diatur berapa banyak lubang pada
        setiap sisinya dan berapa banyak biji yang awalnya ada pada lubang.

        ---

        **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.
        - len_board (int): panjang board. Default: 7
        - stone_each (int): biji yang diberikan pada setiap lubang di sisi player. Default: 7

        """
        
        self.__version__ = 1.0
        self.__max_round = max_round        
        self.in_simulation = False

        self.WIN_R = win_reward
        self.LOSE_R = lose_reward

        self.len_board=len_board
        self.stone_each=stone_each
        
        self.reset()

Inisial parameter berikut digunakan untuk membuat game baru. Game mancala pada Gapoera dapat diatur berapa banyak lubang pada setiap sisinya dan berapa banyak biji yang awalnya ada pada lubang.


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.
  • len_board (int): panjang board. Default: 7
  • stone_each (int): biji yang diberikan pada setiap lubang di sisi player. Default: 7
#   def observe(self, player_id=0) -> dict:
View Source
    def observe(self, player_id=0) -> dict:
        """
        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. dimensi 0: player 0, dimensi 1: player 1
            - 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 = deepcopy(self.board)
        if player_id==1:
            board = [self.board[1], self.board[0]]

        observed_state = {
            "board" : board,
            "current_player" : deepcopy(self.current_player),
            "round" : deepcopy(self.round),
            "is_over": self.gameover(),
            "score": self.get_score()
        }
        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. dimensi 0: player 0, dimensi 1: player 1
    • 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) -> dict:
View Source
    def game_state(self) -> dict:
        """
        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. dimensi 0: player 0, dimensi 1: player 1
            - 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 = {
            "board" : deepcopy(self.board),
            "current_player" : deepcopy(self.current_player),
            "round" : deepcopy(self.round),
            "is_over": self.gameover(),
            "score": self.get_score()
        }
        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. dimensi 0: player 0, dimensi 1: player 1
    • 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.board = deepcopy(state["board"])
        self.current_player = deepcopy(state["current_player"])
        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 mengambalikan posisi biji, mengubah nilai menjadi 0, dan mengubah giliran pemain ke pemain ke 0
        """
        self.board = [[self.stone_each]*(self.len_board+1),
                      [self.stone_each]*(self.len_board+1)]
        self.board[0][self.len_board]=0
        self.board[1][self.len_board]=0
        self.current_player = 0
        self.round = 0

Reset game ke state awal saat game belum dimulai. Fungsi ini akan mengambalikan posisi biji, mengubah nilai menjadi 0, dan mengubah giliran pemain ke pemain ke 0

#   def valid_act(self, c) -> list:
View Source
    def valid_act(self, c) -> list:
        """
        return list of valid actions for player `c`. List yang diberikan berisi elemen dengan nilai antara 1..`len_board` yang menunjukkan
        represnetasi lubang mana yang valid untuk dimainkan (jumlah biji tidak 0).

        ---

        **Parameter:**
        - c (int): player id

        """
        acts = []
        for i in range(self.len_board):
            if self.board[c][i]!=0:
                acts.append(i+1) # +1 for readability
        return acts

return list of valid actions for player c. List yang diberikan berisi elemen dengan nilai antara 1..len_board yang menunjukkan represnetasi lubang mana yang valid untuk dimainkan (jumlah biji tidak 0).


Parameter:

  • c (int): player id
#   def get_score(self) -> list:
View Source
    def get_score(self) -> list:
        """
        return score. Score ditampilkan dalam sebuah list dengan format `[score_player_0, score_player_1]`
        
        """
        return [self.board[0][self.len_board], self.board[1][self.len_board]]

return 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, 0 untuk player pertama, 1 untuk player kedua.
        """
        return self.current_player

return current player, 0 untuk player pertama, 1 untuk player kedua.

#   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
        - player meletakkan biji terakhir di sisinya ke kotak paling kanan
        - tidak ada aksi yang dapat dipilih oleh salah satu player
        """
        return int((sum(self.get_score()) == self.len_board*2*self.stone_each) 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
  • player meletakkan biji terakhir di sisinya ke kotak paling kanan
  • tidak ada aksi yang dapat dipilih oleh salah satu player
#   def step(self, c, act) -> tuple:
View Source
    def step(self, c, act) -> tuple:
        """
        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. Deskripsi id aksi dapat dibaca di `valid_act()`.

        **Return:**
        - confirmation (bool): `True` jika aksi berhasil dilakukan, `False` jika tidak.
        - reward (float): reward yang diberikan kepada player `c` jika aksi berhasil dilakukan.
    
        """

        act -= 1  # -1 for readability
        
        if act < 0 or act > self.len_board-1:
            return False, 0

        if self.board[c][act]==0:
            return False, 0
        
        if self.current_player != c:
            return False, 0
        
        self.round += 1
        bonus_move = False
        taken = self.board[c][act]
        self.board[c][act] = 0
        row = c
        i = act+1
        prev = self.board[row][self.len_board]
        while taken!=0:
            if row==c or (row!=c and i!=self.len_board): # put the stone or not
                self.board[row][i] += 1
                taken -= 1

            if taken==0 and i==self.len_board and row==c: # get the bonus turn or not
                bonus_move = True

            elif taken==0 and self.board[row][i]==1 and row==c: # take enemy stone or not
                self.board[row][self.len_board] += self.board[1-row][self.len_board-i-1]+1
                self.board[1-row][self.len_board-i-1] = 0
                self.board[row][i] = 0
            
            elif (row==c and i==self.len_board) or (row!=c and i==self.len_board-1): # changing row
                row = 1-row
                i = -1
            
            if taken==0 and (len(self.valid_act(0))==0 or len(self.valid_act(1))==0):
                for j in range(2):
                    for i in range(self.len_board):
                        self.board[j][self.len_board] += self.board[j][i]
                        self.board[j][i] = 0

            i += 1

        if not bonus_move:
            self.current_player = 1-c

        reward = self.board[c][self.len_board] - prev
        if self.gameover():
            if self.board[c][self.len_board] > self.board[1-c][self.len_board]:
                reward += self.WIN_R
            else:
                reward += self.LOSE_R

        return True, 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. Deskripsi id aksi dapat dibaca di valid_act().

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=None):
View Source
    def render(self, state=None):
        """
        Digunakan untuk menampilkan tampilan game sederhana di layar (terminal) pada suatu state.

        ---

        **Parameter:**
        - state (dict): state dari game yang akan ditampilkan. Defaultnya akan ambil dari `self.game_state()`
    
        """
        if state is None:
            state = self.game_state()
        print(" p0  ", end="")
        for i in range(self.len_board):
            print("["+" "*int(state["board"][0][self.len_board-i-1]<=9)+str(state["board"][0][self.len_board-i-1])+"]",end="")
        print("\n   ["+str(state["board"][0][self.len_board])+"]" + " "*(self.len_board*4-2) + "["+str(state["board"][1][self.len_board])+"]")
        print(" p1  ", end="")
        for i in range(self.len_board):
            print("["+" "*int(state["board"][1][i]<=9)+str(state["board"][1][i])+"]",end="")
        print()

Digunakan untuk menampilkan tampilan game sederhana di layar (terminal) pada suatu state.


Parameter:

  • state (dict): state dari game yang akan ditampilkan. Defaultnya akan ambil dari self.game_state()