pacai.gridworld.board

  1import typing
  2
  3import pacai.core.board
  4
  5MARKER_TERMINAL: pacai.core.board.Marker = pacai.core.board.Marker('T')
  6MARKER_DISPLAY_VALUE: pacai.core.board.Marker = pacai.core.board.Marker('V')
  7MARKER_DISPLAY_QVALUE: pacai.core.board.Marker = pacai.core.board.Marker('Q')
  8MARKER_SEPARATOR: pacai.core.board.Marker = pacai.core.board.Marker('X')
  9
 10AGENT_MARKER: pacai.core.board.Marker = pacai.core.board.MARKER_AGENT_0
 11""" The fixed marker of the only agent. """
 12
 13BOARD_COL_DELIM: str = ','
 14
 15class Board(pacai.core.board.Board):
 16    """
 17    A board for GridWorld.
 18
 19    In addition to walls, grid worlds also have terminal states.
 20    These will be represented as `T-?\\d`, so a 'T' followed by the value of the terminal state,
 21    e.g., `T1`, `T100`, `T-5`.
 22    """
 23
 24    def __init__(self, *args: typing.Any,
 25            additional_markers: list[str] | None = None,
 26            qdisplay: bool = False,
 27            **kwargs: typing.Any) -> None:
 28        """
 29        Construct a GridWorld board.
 30        If qdisplay is true, then the existing board will be extended to display Q-Values and policies.
 31        """
 32
 33        if (additional_markers is None):
 34            additional_markers = []
 35
 36        additional_markers += [
 37            MARKER_TERMINAL,
 38        ]
 39
 40        kwargs['strip'] = False
 41
 42        self._terminal_values: dict[pacai.core.board.Position, int] = {}
 43        """ Values for each terminal position. """
 44
 45        # Ensure that super's init() is called after self._terminal_values exists.
 46        super().__init__(*args, additional_markers = additional_markers, **kwargs)  # type: ignore
 47
 48        self._original_height: int = self.height
 49        """ The original height for this board. """
 50
 51        self._original_width: int = self.width
 52        """ The original width for this board. """
 53
 54        if (qdisplay):
 55            self._add_qvalue_display()
 56
 57    def _split_line(self, line: str) -> list[str]:
 58        # Skip empty lines.
 59        if (len(line.strip()) == 0):
 60            return []
 61
 62        return line.split(BOARD_COL_DELIM)
 63
 64    def _translate_marker(self, text: str, position: pacai.core.board.Position) -> pacai.core.board.Marker | None:
 65        # GridWorld markers may have additional whitespace or repeated characters.
 66        # Only simple markers (not terminals) can have repeated characters.
 67
 68        text = text.strip()
 69        if (len(text) == 0):
 70            text = ' '
 71
 72        if (not text.startswith(MARKER_TERMINAL)):
 73            return super()._translate_marker(text[0], position)
 74
 75        value = int(text[1:])
 76        self._terminal_values[position] = value
 77
 78        return MARKER_TERMINAL
 79
 80    def is_terminal_position(self, position: pacai.core.board.Position) -> bool:
 81        """ Check if the given position is a terminal. """
 82
 83        return (position in self._terminal_values)
 84
 85    def get_terminal_value(self, position: pacai.core.board.Position) -> int:
 86        """ Get the value of this terminal position (or raise an exception if the position is not a terminal). """
 87
 88        return self._terminal_values[position]
 89
 90    def display_qvalues(self) -> bool:
 91        """ Check if this board is displaying Q-Values. """
 92
 93        return len(self.get_marker_positions(MARKER_SEPARATOR)) > 0
 94
 95    def _add_qvalue_display(self) -> None:
 96        """
 97        Add a Q-Value display, which includes a section for values and q-values.
 98        The board will be doubled in size (along each dimension).
 99        The top-left will have the original board (with agent),
100        the top-right will have the values and policies,
101        and the bottom-left will have the q-values.
102        """
103
104        if (len(self.get_marker_positions(MARKER_SEPARATOR)) > 0):
105            raise ValueError("Already added Q-Value display.")
106
107        self._original_height = self.height
108        self._original_width = self.width
109        base_walls = self._walls.copy()
110
111        # Grow the board.
112        self.height = (self._original_height * 2) + 1
113        self.width = (self._original_width * 2) + 1
114
115        offset_values = pacai.core.board.Position(0, self._original_width + 1)  # Values (Top-Right)
116        offset_qvalues = pacai.core.board.Position(self._original_height + 1, 0)  # Q-Values (Bottom-Left)
117
118        # Add separators to quarter off sections of the new board.
119
120        for row in range(self.height):
121            self.place_marker(MARKER_SEPARATOR, pacai.core.board.Position(row, self._original_width))
122
123        for col in range(self.width):
124            self.place_marker(MARKER_SEPARATOR, pacai.core.board.Position(self._original_height, col))
125
126        # Empty out the blank section.
127        for row in range(self._original_height):
128            for col in range(self._original_width):
129                position = pacai.core.board.Position(self._original_height + 1 + row, self._original_width + 1 + col)
130                self.place_marker(MARKER_SEPARATOR, position)
131
132        # Duplicate the walls on the new sections.
133
134        for offset in [offset_values, offset_qvalues]:
135            for base_wall in base_walls:
136                self._walls.add(base_wall.add(offset))
137
138        # Place the markers to display values/q-values.
139
140        for base_row in range(self._original_height):
141            for base_col in range(self._original_width):
142                base_position = pacai.core.board.Position(base_row, base_col)
143                if (self.is_wall(base_position) or self.is_marker(MARKER_TERMINAL, base_position)):
144                    continue
145
146                self.place_marker(MARKER_DISPLAY_VALUE, base_position.add(offset_values))
147                self.place_marker(MARKER_DISPLAY_QVALUE, base_position.add(offset_qvalues))
148
149        # Copy terminal markers.
150
151        for (base_position, value) in list(self._terminal_values.items()):
152            for offset in [offset_values, offset_qvalues]:
153                position = base_position.add(offset)
154                self._terminal_values[position] = value
155                self.place_marker(MARKER_TERMINAL, position)
156
157    def to_dict(self) -> dict[str, typing.Any]:
158        data = super().to_dict()
159        data['_terminal_values'] = [(position.to_dict(), value) for (position, value) in self._terminal_values.items()]
160        return data
161
162    @classmethod
163    def from_dict(cls, data: dict[str, typing.Any]) -> typing.Any:
164        board = super().from_dict(data)
165        board._terminal_values = {pacai.core.board.Position.from_dict(raw): value for (raw, value) in data['_terminal_values']}
166        return board
MARKER_TERMINAL: pacai.core.board.Marker = 'T'
MARKER_DISPLAY_VALUE: pacai.core.board.Marker = 'V'
MARKER_DISPLAY_QVALUE: pacai.core.board.Marker = 'Q'
MARKER_SEPARATOR: pacai.core.board.Marker = 'X'
AGENT_MARKER: pacai.core.board.Marker = '0'

The fixed marker of the only agent.

BOARD_COL_DELIM: str = ','
class Board(pacai.core.board.Board):
 16class Board(pacai.core.board.Board):
 17    """
 18    A board for GridWorld.
 19
 20    In addition to walls, grid worlds also have terminal states.
 21    These will be represented as `T-?\\d`, so a 'T' followed by the value of the terminal state,
 22    e.g., `T1`, `T100`, `T-5`.
 23    """
 24
 25    def __init__(self, *args: typing.Any,
 26            additional_markers: list[str] | None = None,
 27            qdisplay: bool = False,
 28            **kwargs: typing.Any) -> None:
 29        """
 30        Construct a GridWorld board.
 31        If qdisplay is true, then the existing board will be extended to display Q-Values and policies.
 32        """
 33
 34        if (additional_markers is None):
 35            additional_markers = []
 36
 37        additional_markers += [
 38            MARKER_TERMINAL,
 39        ]
 40
 41        kwargs['strip'] = False
 42
 43        self._terminal_values: dict[pacai.core.board.Position, int] = {}
 44        """ Values for each terminal position. """
 45
 46        # Ensure that super's init() is called after self._terminal_values exists.
 47        super().__init__(*args, additional_markers = additional_markers, **kwargs)  # type: ignore
 48
 49        self._original_height: int = self.height
 50        """ The original height for this board. """
 51
 52        self._original_width: int = self.width
 53        """ The original width for this board. """
 54
 55        if (qdisplay):
 56            self._add_qvalue_display()
 57
 58    def _split_line(self, line: str) -> list[str]:
 59        # Skip empty lines.
 60        if (len(line.strip()) == 0):
 61            return []
 62
 63        return line.split(BOARD_COL_DELIM)
 64
 65    def _translate_marker(self, text: str, position: pacai.core.board.Position) -> pacai.core.board.Marker | None:
 66        # GridWorld markers may have additional whitespace or repeated characters.
 67        # Only simple markers (not terminals) can have repeated characters.
 68
 69        text = text.strip()
 70        if (len(text) == 0):
 71            text = ' '
 72
 73        if (not text.startswith(MARKER_TERMINAL)):
 74            return super()._translate_marker(text[0], position)
 75
 76        value = int(text[1:])
 77        self._terminal_values[position] = value
 78
 79        return MARKER_TERMINAL
 80
 81    def is_terminal_position(self, position: pacai.core.board.Position) -> bool:
 82        """ Check if the given position is a terminal. """
 83
 84        return (position in self._terminal_values)
 85
 86    def get_terminal_value(self, position: pacai.core.board.Position) -> int:
 87        """ Get the value of this terminal position (or raise an exception if the position is not a terminal). """
 88
 89        return self._terminal_values[position]
 90
 91    def display_qvalues(self) -> bool:
 92        """ Check if this board is displaying Q-Values. """
 93
 94        return len(self.get_marker_positions(MARKER_SEPARATOR)) > 0
 95
 96    def _add_qvalue_display(self) -> None:
 97        """
 98        Add a Q-Value display, which includes a section for values and q-values.
 99        The board will be doubled in size (along each dimension).
100        The top-left will have the original board (with agent),
101        the top-right will have the values and policies,
102        and the bottom-left will have the q-values.
103        """
104
105        if (len(self.get_marker_positions(MARKER_SEPARATOR)) > 0):
106            raise ValueError("Already added Q-Value display.")
107
108        self._original_height = self.height
109        self._original_width = self.width
110        base_walls = self._walls.copy()
111
112        # Grow the board.
113        self.height = (self._original_height * 2) + 1
114        self.width = (self._original_width * 2) + 1
115
116        offset_values = pacai.core.board.Position(0, self._original_width + 1)  # Values (Top-Right)
117        offset_qvalues = pacai.core.board.Position(self._original_height + 1, 0)  # Q-Values (Bottom-Left)
118
119        # Add separators to quarter off sections of the new board.
120
121        for row in range(self.height):
122            self.place_marker(MARKER_SEPARATOR, pacai.core.board.Position(row, self._original_width))
123
124        for col in range(self.width):
125            self.place_marker(MARKER_SEPARATOR, pacai.core.board.Position(self._original_height, col))
126
127        # Empty out the blank section.
128        for row in range(self._original_height):
129            for col in range(self._original_width):
130                position = pacai.core.board.Position(self._original_height + 1 + row, self._original_width + 1 + col)
131                self.place_marker(MARKER_SEPARATOR, position)
132
133        # Duplicate the walls on the new sections.
134
135        for offset in [offset_values, offset_qvalues]:
136            for base_wall in base_walls:
137                self._walls.add(base_wall.add(offset))
138
139        # Place the markers to display values/q-values.
140
141        for base_row in range(self._original_height):
142            for base_col in range(self._original_width):
143                base_position = pacai.core.board.Position(base_row, base_col)
144                if (self.is_wall(base_position) or self.is_marker(MARKER_TERMINAL, base_position)):
145                    continue
146
147                self.place_marker(MARKER_DISPLAY_VALUE, base_position.add(offset_values))
148                self.place_marker(MARKER_DISPLAY_QVALUE, base_position.add(offset_qvalues))
149
150        # Copy terminal markers.
151
152        for (base_position, value) in list(self._terminal_values.items()):
153            for offset in [offset_values, offset_qvalues]:
154                position = base_position.add(offset)
155                self._terminal_values[position] = value
156                self.place_marker(MARKER_TERMINAL, position)
157
158    def to_dict(self) -> dict[str, typing.Any]:
159        data = super().to_dict()
160        data['_terminal_values'] = [(position.to_dict(), value) for (position, value) in self._terminal_values.items()]
161        return data
162
163    @classmethod
164    def from_dict(cls, data: dict[str, typing.Any]) -> typing.Any:
165        board = super().from_dict(data)
166        board._terminal_values = {pacai.core.board.Position.from_dict(raw): value for (raw, value) in data['_terminal_values']}
167        return board

A board for GridWorld.

In addition to walls, grid worlds also have terminal states. These will be represented as T-?\d, so a 'T' followed by the value of the terminal state, e.g., T1, T100, T-5.

Board( *args: Any, additional_markers: list[str] | None = None, qdisplay: bool = False, **kwargs: Any)
25    def __init__(self, *args: typing.Any,
26            additional_markers: list[str] | None = None,
27            qdisplay: bool = False,
28            **kwargs: typing.Any) -> None:
29        """
30        Construct a GridWorld board.
31        If qdisplay is true, then the existing board will be extended to display Q-Values and policies.
32        """
33
34        if (additional_markers is None):
35            additional_markers = []
36
37        additional_markers += [
38            MARKER_TERMINAL,
39        ]
40
41        kwargs['strip'] = False
42
43        self._terminal_values: dict[pacai.core.board.Position, int] = {}
44        """ Values for each terminal position. """
45
46        # Ensure that super's init() is called after self._terminal_values exists.
47        super().__init__(*args, additional_markers = additional_markers, **kwargs)  # type: ignore
48
49        self._original_height: int = self.height
50        """ The original height for this board. """
51
52        self._original_width: int = self.width
53        """ The original width for this board. """
54
55        if (qdisplay):
56            self._add_qvalue_display()

Construct a GridWorld board. If qdisplay is true, then the existing board will be extended to display Q-Values and policies.

def is_terminal_position(self, position: pacai.core.board.Position) -> bool:
81    def is_terminal_position(self, position: pacai.core.board.Position) -> bool:
82        """ Check if the given position is a terminal. """
83
84        return (position in self._terminal_values)

Check if the given position is a terminal.

def get_terminal_value(self, position: pacai.core.board.Position) -> int:
86    def get_terminal_value(self, position: pacai.core.board.Position) -> int:
87        """ Get the value of this terminal position (or raise an exception if the position is not a terminal). """
88
89        return self._terminal_values[position]

Get the value of this terminal position (or raise an exception if the position is not a terminal).

def display_qvalues(self) -> bool:
91    def display_qvalues(self) -> bool:
92        """ Check if this board is displaying Q-Values. """
93
94        return len(self.get_marker_positions(MARKER_SEPARATOR)) > 0

Check if this board is displaying Q-Values.

def to_dict(self) -> dict[str, typing.Any]:
158    def to_dict(self) -> dict[str, typing.Any]:
159        data = super().to_dict()
160        data['_terminal_values'] = [(position.to_dict(), value) for (position, value) in self._terminal_values.items()]
161        return data

Return a dict that can be used to represent this object. If the dict is passed to from_dict(), an identical object should be reconstructed.

A general (but inefficient) implementation is provided by default.

@classmethod
def from_dict(cls, data: dict[str, typing.Any]) -> Any:
163    @classmethod
164    def from_dict(cls, data: dict[str, typing.Any]) -> typing.Any:
165        board = super().from_dict(data)
166        board._terminal_values = {pacai.core.board.Position.from_dict(raw): value for (raw, value) in data['_terminal_values']}
167        return board

Return an instance of this subclass created using the given dict. If the dict came from to_dict(), the returned object should be identical to the original.

A general (but inefficient) implementation is provided by default.