Ability to move player with location.
[naja.git] / naja / player.py
1 from naja.constants import BITS, MOVES
2
3
4 class PlayerBits(object):
5     """
6     A glorified byte.
7     """
8
9     def __init__(self, bits):
10         self.bits = bits
11
12     @property
13     def bits(self):
14         return self._bits
15
16     @bits.setter
17     def bits(self, value):
18         assert 0 <= value <= 0xff
19         self._bits = value
20
21     # Operate on individual bits
22
23     def check_bit(self, bit):
24         return bool(self.bits & (1 << bit))
25
26     def set_bit(self, bit):
27         self.bits |= (1 << bit)
28
29     def clear_bit(self, bit):
30         self.bits &= (0xff ^ (1 << bit))
31
32     def toggle_bit(self, bit):
33         self.bits ^= (1 << bit)
34
35     # Operate on sets of bits
36
37     def check_bits(self, bits):
38         return all(self.check_bit(bit) for bit in bits)
39
40     def set_bits(self, bits):
41         for bit in bits:
42             self.set_bit(bit)
43
44     def clear_bits(self, bits):
45         for bit in bits:
46             self.clear_bit(bit)
47
48     def toggle_bits(self, bits):
49         for bit in bits:
50             self.toggle_bit(bit)
51
52     def shift_bits_left(self, shift):
53         wrap = self.bits >> (8 - shift)
54         self.bits = (self.bits << shift & 0xff | wrap)
55
56     def shift_bits_right(self, shift):
57         wrap = self.bits << (8 - shift) & 0xff
58         self.bits = (self.bits >> shift | wrap)
59
60
61 class Player(object):
62     """
63     A representation of the player.
64     """
65
66     def __init__(self, bits, position, movement_mode=None, gameboard=None):
67         self.bits = PlayerBits(bits)
68         self.position = position
69         self.movement_mode = movement_mode if movement_mode else MOVES.ADJACENT
70         self.gameboard = gameboard
71
72     @classmethod
73     def import_player(cls, definition, gameboard=None):
74         return cls(
75             definition['bits'],
76             tuple(definition['position']),
77             definition['movement_mode'],
78             gameboard=gameboard)
79
80     def export(self):
81         return {
82             'bits': self.bits.bits,
83             'position': list(self.position),
84             'movement_mode': self.movement_mode,
85         }
86
87     def get_adjacent_positions(self):
88         positions = [self.position]
89
90         x, y = self.position
91
92         if self.bits.check_bit(BITS.NORTH) and y > 0:
93             positions.append((x, y - 1))
94         if self.bits.check_bit(BITS.SOUTH) and y < 4:
95             positions.append((x, y + 1))
96         if self.bits.check_bit(BITS.EAST) and x < 4:
97             positions.append((x + 1, y))
98         if self.bits.check_bit(BITS.WEST) and x > 0:
99             positions.append((x - 1, y))
100
101         return positions
102
103     def get_knight_positions(self):
104         positions = set([self.position])
105
106         x, y = self.position
107
108         for a in (2, -2):
109             for b in (1, -1):
110                 i, j = x + a, y + b
111                 if 0 <= i < 5 and 0 <= j < 5:
112                     positions.add((i, j))
113
114                 i, j = x + b, y + a
115                 if 0 <= i < 5 and 0 <= j < 5:
116                     positions.add((i, j))
117
118         return sorted(list(positions))
119
120     def get_bishop_positions(self):
121         positions = set()
122
123         x, y = self.position
124
125         for i in range(5):
126             j = i + y - x
127             if 0 <= j < 5:
128                 positions.add((i, j))
129
130             j = x + y - i
131             if 0 <= j < 5:
132                 positions.add((i, j))
133
134         return sorted(list(positions))
135
136     def get_castle_positions(self):
137         positions = set()
138
139         x, y = self.position
140
141         for i in range(5):
142             positions.add((x, i))
143             positions.add((i, y))
144
145         return sorted(list(positions))
146
147     def set_position(self, new_position):
148         if new_position in self.legal_moves():
149             self.position = new_position
150             self.movement_mode = MOVES.ADJACENT
151             return True
152         return False
153
154     def force_position(self, pos, delta=True):
155         if delta:
156             pos = (self.position[0] + pos[0],
157                    self.position[1] + pos[1])
158         if (0 <= pos[0] < 5 and 0 <= pos[1] < 5):
159             self.position = pos
160
161     def set_gameboard(self, gameboard):
162         self.gameboard = gameboard
163
164     def pos_has_action(self, pos):
165         card = self.gameboard.board_locations[pos]
166         for action in card.actions:
167             if self.bits.check_bits(action.required_bits):
168                 return True
169         return False
170
171     def filter_moves_with_no_actions(self, positions):
172         return [pos for pos in positions if self.pos_has_action(pos)]
173
174     def legal_moves(self):
175         POSITION_FUNCTION = {
176             MOVES.ADJACENT: self.get_adjacent_positions,
177             MOVES.KNIGHT: self.get_knight_positions,
178             MOVES.BISHOP: self.get_bishop_positions,
179             MOVES.CASTLE: self.get_castle_positions,
180         }
181         positions = POSITION_FUNCTION[self.movement_mode]()
182         return self.filter_moves_with_no_actions(positions)
183
184     def allow_chess_move(self, chesspiece):
185         self.movement_mode = chesspiece