rev |
line source |
me@44
|
1 """Guts of snakes.""" |
me@44
|
2 |
me@34
|
3 import engine |
me@34
|
4 |
me@34
|
5 def preprocess(line): |
me@44
|
6 """Remove comments and junk spaces from line of snake definition file.""" |
me@34
|
7 if '//' in line: |
me@34
|
8 line = line[:line.index('//')] |
me@34
|
9 line = line.rstrip() |
me@34
|
10 return line |
martiran@30
|
11 |
me@109
|
12 class File(object): |
me@109
|
13 """Wrapper around file that saves the current line number.""" |
me@109
|
14 def __init__(self, file): |
me@109
|
15 self.file = file |
me@109
|
16 self.name = file.name |
me@109
|
17 self.line_no = 0 |
me@109
|
18 def __iter__(self): |
me@109
|
19 for line_no, line in enumerate(self.file, self.line_no): |
me@109
|
20 self.line_no = line_no |
me@109
|
21 yield line |
me@109
|
22 |
martiran@30
|
23 class Snake(object): |
me@44
|
24 """Snakes. |
me@44
|
25 |
me@44
|
26 Attributes: |
me@44
|
27 |
me@44
|
28 - `cells` -- list of cells belonging to the snake The first of these cells |
me@44
|
29 becomes head, the last one becomes tail, the rest ar body. If snake has |
me@44
|
30 only one cell, it is tail. |
me@44
|
31 - `color` -- color of snake |
me@44
|
32 - `rules` -- a list of Rule objects |
me@44
|
33 """ |
me@44
|
34 |
martiran@32
|
35 def __init__ (self, cells, color): |
martiran@32
|
36 self.cells = cells |
martiran@32
|
37 self.color = color |
martiran@32
|
38 self.rules = [] |
me@34
|
39 |
me@34
|
40 def load (self, file): |
me@44
|
41 """Load snake description from file. |
me@44
|
42 |
me@44
|
43 See program design docs for file syntax. |
me@44
|
44 """ |
me@109
|
45 file = File(file) |
me@109
|
46 try: |
me@109
|
47 self._load(file) |
me@109
|
48 except Exception, e: |
me@109
|
49 raise Exception("%s:%s: %s" % (file.name, file.line_no, e)) |
me@109
|
50 |
me@109
|
51 def _load (self, file): |
me@109
|
52 """Actually do the loading.""" |
me@65
|
53 for line in file: |
me@88
|
54 magic, self.name = preprocess(line).split(' ', 1) |
me@65
|
55 break |
me@34
|
56 assert magic == "snake", "This is not snake file" |
me@65
|
57 for line in file: |
me@65
|
58 line = preprocess(line) |
me@34
|
59 if line == 'end': |
me@34
|
60 break |
me@34
|
61 assert line == '', "Rules must be separated by empty lines" |
me@51
|
62 self.rules.append(Rule(self).load(file)) |
me@34
|
63 |
martiran@30
|
64 def fill (self): |
me@44
|
65 """Mark every cell in `self.cells` as belonging to self.""" |
martiran@32
|
66 for cell in self.cells: |
martiran@32
|
67 cell.snake = self |
martiran@111
|
68 for cell in self.cells: |
martiran@111
|
69 cell.type = 'body' |
Alex@75
|
70 self.cells[0].type = 'head' |
Alex@75
|
71 self.cells[-1].type = 'tail' |
martiran@31
|
72 return |
me@34
|
73 |
martiran@30
|
74 class Rule(object): |
me@44
|
75 """Rule defining possible behaviour of snake.""" |
me@34
|
76 |
me@34
|
77 codes = { |
me@34
|
78 'h': 'head', |
me@34
|
79 'b': 'body', |
me@34
|
80 't': 'tail', |
me@34
|
81 '#': 'wall', |
me@34
|
82 ' ': 'any', |
me@34
|
83 '-': 'empty', |
me@34
|
84 } |
me@34
|
85 |
martiran@32
|
86 def __init__ (self, snake): |
martiran@32
|
87 self.snake = snake |
me@80
|
88 self.direction = (0, -1) |
me@34
|
89 self.pattern = {} |
me@34
|
90 |
me@34
|
91 def load (self, file): |
me@70
|
92 """Load rule definition from file. |
me@70
|
93 |
me@70
|
94 Ignore any leading empty lines. |
me@70
|
95 Return self. |
me@70
|
96 """ |
me@34
|
97 y = 0 |
me@34
|
98 for line in file: |
me@34
|
99 line = preprocess(line) |
me@34
|
100 if y == 0 and line == '': |
me@34
|
101 continue |
me@41
|
102 assert len(line) == 8, "Rule lines must be exactly 7 chars long" |
me@34
|
103 assert line[-1] == ';', "Rule lines must end with semicolon" |
me@63
|
104 for x, char in enumerate(line[:7]): |
me@34
|
105 self.parse_cell(x, y, char) |
me@34
|
106 y += 1 |
me@68
|
107 if y == 7: |
me@68
|
108 break |
me@70
|
109 return self |
me@34
|
110 |
me@34
|
111 def parse_cell(self, x, y, char): |
me@44
|
112 """Parse definition of cell in rule file. |
me@44
|
113 |
me@44
|
114 Cell is defined by one character. |
me@44
|
115 """ |
me@73
|
116 is_my = char.islower() |
me@73
|
117 char = char.lower() |
me@73
|
118 assert char in self.codes, "Illegal symbol in rule: %s" % char |
me@51
|
119 cell = engine.Cell(x, y, self.snake) |
me@34
|
120 if char in 'htb': |
me@73
|
121 if is_my: |
me@51
|
122 cell.snake_type = 'my' |
me@34
|
123 else: |
me@51
|
124 cell.snake_type = 'enemy' |
me@37
|
125 if char == 'h': |
me@37
|
126 assert (x, y) == (3, 3), "Own head must in the center of rule" |
me@37
|
127 if (x, y) == (3, 3): |
me@37
|
128 assert char == 'h', "In the center of rule must be own head" |
me@73
|
129 cell.type = self.codes[char] |
me@34
|
130 self.pattern[x, y] = cell |
me@34
|
131 |
martiran@32
|
132 def applies (self, field, x, y): |
me@44
|
133 """True if the rule applies in the field at position (x,y).""" |
me@38
|
134 for px, fx in zip(range(7), range(x - 3, x + 4)): |
me@38
|
135 for py, fy in zip(range(7), range(y - 3, y + 4)): |
me@49
|
136 if (fx, fy) in field: |
me@43
|
137 if field[fx, fy] != self.pattern[px, py]: |
me@43
|
138 return False |
me@43
|
139 else: |
me@43
|
140 if self.pattern[px, py].type != 'any': |
me@43
|
141 return False |
me@38
|
142 return True |
me@34
|
143 |
me@104
|
144 def rotate (self, direction): |
me@104
|
145 """Rotate rule pattern to head in `direction`.""" |
me@104
|
146 for i in range(4): |
me@104
|
147 if self.direction == direction: |
me@104
|
148 return |
me@39
|
149 self.rotate_ccw() |
me@104
|
150 raise AssertionError("Illegal direction: %s" % direction) |
me@39
|
151 |
me@39
|
152 def rotate_ccw(self): |
me@44
|
153 """Rotate rule pattern one time counterclockwise.""" |
me@39
|
154 pattern = {} |
me@39
|
155 for x in range(7): |
me@39
|
156 for y in range(7): |
me@39
|
157 pattern[y, 6 - x] = self.pattern[x, y] |
me@39
|
158 self.pattern = pattern |
me@39
|
159 x, y = self.direction |
me@39
|
160 self.direction = y, -x |
me@34
|
161 |
me@34
|
162 # vim: set ts=4 sts=4 sw=4 et: |