erkennt jetzt ob das spiel fertig ist
[darcs-mirror-ozeanien.git] / engine.py
1 # Spielregelklasse
2 # encoding:utf8
3
4 SHIPPING = 1
5 PLACING  = 2
6
7
8 # Imports
9 import random
10
11 # Global Variables (Buh! Buh! Pfui!)
12 state = SHIPPING
13
14
15 class Ship:
16     def __init__(self):
17         self.rotation = 0
18         self.guiinfo = None
19
20 class Slot:
21     def __init__(self):
22         self.guiinfo = None
23         self.x = None
24         self.y = None
25         self.rotateable = None
26         self.card = None
27         self.ship = None
28         self.is_ocean = False
29         self.highlighted = False
30
31     def is_empty(self):
32         return not self.card and not self.ship
33
34     def set_card(self, card):
35         assert not self.card
36         self.card = card
37
38         pass
39
40     def move_ship_from(self, slot):
41         assert slot
42         assert slot.ship
43         assert not self.ship
44         self.ship = slot.ship
45         slot.ship = None
46
47 class BoardSlot(Slot):
48     def __init__(self, x, y, is_ocean):
49         Slot.__init__(self)
50         self.x = x
51         self.y = y
52         self.rotateable = True
53         self.is_ocean = is_ocean
54         self.is_reachable = not is_ocean
55
56     def can_drop_ship(self):
57         if not self.is_reachable:
58             return False
59         if not self.is_ocean:
60             for  neigh in self.adj:
61                 if neigh and neigh.is_ocean and not neigh.card:
62                     return True
63         else:
64             if not self.card:
65                 return False
66             for dir in self.card.sea_conn():
67                 neigh = self.adj[dir]
68                 if neigh and neigh.is_ocean and not neigh.card:
69                     return True
70         return False
71         
72     def can_drop_card(self, card):
73         if not self.is_ocean:
74             return False
75         if self.card:
76             return False
77
78         # Fits?
79         # Land?
80         for dir in card.land_conn():
81             neigh = self.adj[dir]
82             if neigh:
83                 if not neigh.is_ocean:
84                     return False
85                 if neigh.card:
86                     if (dir + 2)%4 not in neigh.card.land_conn():
87                         return False
88             else: # Border is not land
89                 return False
90         # Sea?
91         for dir in card.sea_conn():
92             neigh = self.adj[dir]
93             if neigh and neigh.card:
94                 if (dir + 2)%4 not in neigh.card.sea_conn():
95                     return False
96
97         # Ship close and connected through sea?
98         ship_nearby = False
99         for dir in card.sea_conn():
100             if self.adj[dir] and self.adj[dir].ship:
101                 ship_nearby = True
102         if not ship_nearby:
103             return False
104
105         return True
106
107     def move_ship_from(self, slot):
108         Slot.move_ship_from(self,slot)
109         for dir in range(4):
110             if slot == self.adj[dir]:
111                 self.ship.rotation = (dir+2)%4
112
113     def set_card(self, card):
114         Slot.set_card(self, card)
115
116         self.card.placed = True
117
118         # Reachable
119         for dir in card.sea_conn():
120             neigh = self.adj[dir]
121             if neigh and neigh.is_reachable:
122                 if not neigh.is_ocean:
123                     self.is_reachable = True
124                 if neigh.card and (dir + 2)%4 in neigh.card.sea_conn():
125                     self.is_reachable = True
126
127         # Move Ship
128         if self.is_reachable:
129             for neigh in self.adj:
130                 if neigh and neigh.ship:
131                     self.move_ship_from(neigh)
132
133         # Auto-Fill neighbors
134         for neigh in self.adj:
135             if neigh and neigh.is_ocean and not neigh.card:
136                 neigh.autofill()
137
138         # Time to set the ship
139         global state
140         state = SHIPPING
141
142
143     def autofill(self):
144         land = []
145         surrounded = True
146         for dir in range(4):
147             neigh = self.adj[dir]
148             if neigh and neigh.is_ocean:
149                 if not neigh.card:
150                     surrounded = False
151                 else:
152                     if (dir + 2)%4 in neigh.card.land_conn():
153                         land.append(dir)
154                 
155         if not surrounded:
156             self.autofill_land()
157             return
158         
159         card = Card()
160         if not land:
161             card.islands = []
162         elif len(land) == 2 and (land[0] + 2) % 4 == (land[1] % 4):
163             # Separated Land Strips
164             card.islands = [ [x] for x in land ]
165         else:
166             # Connected Land Strips
167             card.islands = [land]
168
169         self.card = card
170
171     def autofill_land(self):
172         seen = []
173         todo = [self]
174
175         while todo:
176             slot = todo.pop()
177             for dir in range(4):
178                 neigh = slot.adj[dir]
179                 if not neigh:
180                     # Border = no Land
181                     return
182                 if neigh.card:
183                     if (dir+2)%4 in neigh.card.sea_conn():
184                         # Still sea-connection
185                         return
186                 else:
187                     if neigh not in seen:
188                         todo.append(neigh)
189             seen.append(slot)
190
191         for slot in seen:
192             card = Card()
193             card.islands = [ [0,1,2,3] ]
194             slot.card = card
195
196 class BlackHole(Slot):
197     def __init__(self):
198         Slot.__init__(self)
199         self.count = 0
200
201     def set_card(self,card):
202         self.count += 1
203         # Time to set the ship
204         global state
205         state = SHIPPING
206         if self.guiinfo:
207             self.guiinfo.fall_card(card)
208
209     def can_drop_card(self,card):
210         return True
211
212 class Pile(Slot):
213     def __init__(self):
214         Slot.__init__(self)
215         self.rotateable = True
216         self.setup()
217
218     def setup(self):
219         self.cards = []
220         # Wasser Wasser Land Land
221         for i in range(11):
222             card = Card()
223             card.rotation= random.randint(0,3)
224             card.islands = [ [0, 1] ]
225             self.cards.append(card)
226
227         # Wasser Land Wasser Land
228         for i in range(7):
229             card = Card()
230             card.rotation= random.randint(0,3)
231             card.islands = [ [0], [2] ]
232             self.cards.append(card)
233
234         # Wasser Wasser Wasser Land
235         for i in range(7):
236             card = Card()
237             card.rotation= random.randint(0,3)
238             card.islands = [ [1] ]
239             self.cards.append(card)
240
241         # Wasser Wasser Wasser Wasser
242         for i in range(7):
243             card = Card()
244             card.rotation= random.randint(0,3)
245             card.islands = [ ]
246             self.cards.append(card)
247
248         # Wasser Land Land Land
249         for i in range(3):
250             card = Card()
251             card.rotation= random.randint(0,3)
252             card.islands = [ [0,1,2] ]
253             self.cards.append(card)
254
255         random.shuffle(self.cards)
256         del self.cards[32:]
257         assert len(self.cards) == 32
258
259     def can_flip_card(self):
260         return self.cards and not self.card
261
262     def flip_card(self):
263         global state
264
265         assert self.cards
266         assert not self.card
267         assert state == SHIPPING
268         state = PLACING
269         self.card = self.cards.pop()
270         if self.guiinfo:
271             self.guiinfo.card_flipped()
272
273     def can_drop_card(self, card):
274         return False
275
276     def can_drop_ship(self):
277         return False
278
279     def is_empty(self):
280         return (len(self.cards) == 0)
281
282     def rotate_right(self):
283         if self.card:
284             if self.guiinfo:
285                 self.guiinfo.start_rotation(-1)
286             self.card.rotate_right()
287
288     def rotate_left(self):
289         if self.card:
290             if self.guiinfo:
291                 self.guiinfo.start_rotation(1)
292             self.card.rotate_left()
293         
294 class HandSlot(Slot):
295     def __init__(self):
296         Slot.__init__(self)
297         self.rotateable = False
298
299     def move_to(self,x,y):
300         self.x = x
301         self.y = y
302
303     def set_card(self, card):
304         assert not self.ship
305         Slot.set_card(self,card)
306
307 class Card:
308     def __init__(self):
309         self.islands = [] 
310         self.rotation = 0
311         self.placed = False
312         # Example:
313         # Island top, right, bottom, with R and B connected:
314         #self.islands = [ [0], [1,2] ] 
315         self.guiinfo = None
316
317     def sea_conn(self):
318         conn = []
319         for dir in range(4):
320             sea = True
321             for island in self.rot_islands():
322                 if dir in island:
323                     sea = False
324             if sea:
325                 conn.append(dir)
326         return conn
327
328     def land_conn(self):
329         conn = []
330         sea = self.sea_conn()
331         for dir in range(4):
332             if dir not in sea:
333                 conn.append(dir)
334         return conn
335
336     def has_sea(self):
337         return len(self.sea_conn()) > 0
338
339     def rot_islands(self):
340         return map( lambda i: map (lambda d: (d + self.rotation)%4, i), self.islands)
341
342     def rotate_right(self):
343         self.rotation = (self.rotation - 1) % 4
344
345     def rotate_left(self):
346         self.rotation = (self.rotation + 1) % 4
347
348 class Board:
349     def __init__(self):
350         self.guiinfo = None
351
352         self.slots = []
353
354         for x in range(9):
355             col = []
356             for y in range(6):
357                col.append(
358                     BoardSlot(
359                         x,
360                         y,
361                         0 < x < 8 and y < 5 # Border Fields
362                     ))
363             self.slots.append(col)
364         for x in range(9):
365             for y in range(6):
366                 self.slots[x][y].adj = []
367                 if (x+1) < 9:
368                     self.slots[x][y].adj.append( self.slots[x+1][y] )
369                 else:
370                     self.slots[x][y].adj.append( None )
371                 if (y-1) >= 0:
372                     self.slots[x][y].adj.append( self.slots[x][y-1] )
373                 else:
374                     self.slots[x][y].adj.append( None )
375                 if (x-1) >= 0:
376                     self.slots[x][y].adj.append( self.slots[x-1][y] )
377                 else:
378                     self.slots[x][y].adj.append( None )
379                 if (y+1) < 6:
380                     self.slots[x][y].adj.append( self.slots[x][y+1] )
381                 else:
382                     self.slots[x][y].adj.append( None )
383                 assert len(self.slots[x][y].adj) == 4
384         self.hand = HandSlot()
385         self.pile = Pile()
386         self.bh = BlackHole()
387         self.points = 0
388
389         self.slots[0][0].ship = Ship()
390
391         self.calc_points()
392
393     def get_state(self):
394         return state
395
396     def calc_points(self):
397         points = 0
398
399         # Non-Placed Fields
400         for col in self.slots:
401             for slot in col:
402                 if slot.is_ocean and not slot.card:
403                     points += -20
404
405         # Islands
406         seen = [] # Tupel: (slot,dir)
407         for col in self.slots:
408             for slot in col:
409                 if slot.card:
410                     for dir in slot.card.land_conn():
411                         if (slot,dir) not in seen:
412                             # For every unchecked land connection
413                             ok = True
414                             todo = [(slot,dir)]
415                             seen_now = []
416                             slots = []
417                             while ok and todo:
418                                 (slot,dir) = todo.pop()
419
420                                 # Not Land, No Points
421                                 if not slot.card or not dir in slot.card.land_conn():
422                                     ok = False
423                                     continue
424
425                                 # Been there, Done that
426                                 if (slot,dir) not in seen_now:
427                                     seen_now.append((slot,dir))
428
429                                     # Check Neighbor:
430                                     assert slot.adj[dir], "Land to Border? Huh"
431                                     if (slot.adj[dir], (dir + 2)%4) not in seen_now:
432                                         todo.append((slot.adj[dir], (dir + 2)%4))
433
434                                     # Count this slot
435                                     if slot not in slots:
436                                         slots.append(slot)
437
438                                     # Other connections on this land
439                                     for island in slot.card.rot_islands():
440                                         if dir in island:
441                                             for other_dir in island:
442                                                 if other_dir != dir:
443                                                     if (slot,other_dir) not in seen_now:
444                                                         todo.append((slot, other_dir))
445
446                             if ok:
447                                 points += len(slots) * len(slots)
448                             seen.extend(seen_now)
449
450
451         self.points = points
452         print self.finished()
453         #self.pd.set_points(points)
454     
455     def finished(self):
456
457         if (state == SHIPPING and self.pile.is_empty()):
458             return True
459         
460         for col in self.slots:
461             for slot in col:
462                 if slot.can_drop_ship(): return False
463         
464         return True
465              
466
467
468 # vim:ts=4:sw=4:sts=4:et