All puzzles to have cards without default actions.
[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):
74         return cls(
75             definition['bits'],
76             tuple(definition['position']),
77             definition['movement_mode'])
78
79     def export(self):
80         return {
81             'bits': self.bits.bits,
82             'position': list(self.position),
83             'movement_mode': self.movement_mode,
84         }
85
86     def get_adjacent_positions(self):
87         positions = [self.position]
88
89         x, y = self.position
90
91         if self.bits.check_bit(BITS.NORTH) and y > 0:
92             positions.append((x, y - 1))
93         if self.bits.check_bit(BITS.SOUTH) and y < 4:
94             positions.append((x, y + 1))
95         if self.bits.check_bit(BITS.EAST) and x < 4:
96             positions.append((x + 1, y))
97         if self.bits.check_bit(BITS.WEST) and x > 0:
98             positions.append((x - 1, y))
99
100         return positions
101
102     def get_knight_positions(self):
103         positions = set([self.position])
104
105         x, y = self.position
106
107         for a in (2, -2):
108             for b in (1, -1):
109                 i, j = x + a, y + b
110                 if 0 <= i < 5 and 0 <= j < 5:
111                     positions.add((i, j))
112
113                 i, j = x + b, y + a
114                 if 0 <= i < 5 and 0 <= j < 5:
115                     positions.add((i, j))
116
117         return sorted(list(positions))
118
119     def get_bishop_positions(self):
120         positions = set()
121
122         x, y = self.position
123
124         for i in range(5):
125             j = i + y - x
126             if 0 <= j < 5:
127                 positions.add((i, j))
128
129             j = x + y - i
130             if 0 <= j < 5:
131                 positions.add((i, j))
132
133         return sorted(list(positions))
134
135     def get_castle_positions(self):
136         positions = set()
137
138         x, y = self.position
139
140         for i in range(5):
141             positions.add((x, i))
142             positions.add((i, y))
143
144         return sorted(list(positions))
145
146     def set_position(self, new_position):
147         if new_position in self.legal_moves():
148             self.position = new_position
149             self.movement_mode = MOVES.ADJACENT
150             return True
151         return False
152
153     def set_gameboard(self, gameboard):
154         self.gameboard = gameboard
155
156     def pos_has_action(self, pos):
157         card = self.gameboard.board_locations[pos]
158         for action in card.actions:
159             if self.bits.check_bits(action.required_bits):
160                 return True
161         return False
162
163     def filter_moves_with_no_actions(self, positions):
164         return [pos for pos in positions if self.pos_has_action(pos)]
165
166     def legal_moves(self):
167         POSITION_FUNCTION = {
168             MOVES.ADJACENT: self.get_adjacent_positions,
169             MOVES.KNIGHT: self.get_knight_positions,
170             MOVES.BISHOP: self.get_bishop_positions,
171             MOVES.CASTLE: self.get_castle_positions,
172         }
173         positions = POSITION_FUNCTION[self.movement_mode]()
174         return self.filter_moves_with_no_actions(positions)
175
176     def allow_chess_move(self, chesspiece):
177         self.movement_mode = chesspiece