-
Notifications
You must be signed in to change notification settings - Fork 0
/
catan.py
1144 lines (997 loc) · 37.9 KB
/
catan.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#! /usr/bin/env python
import time
import shlex
import random
import events
import mapmodel
from mapmodel import Tile
class Countdown(object):
def __init__(self, seconds):
self.startTime = time.time()
self.seconds = seconds
def pump(self):
if time.time() > self.startTime + self.seconds:
events.post('CountdownOver')
class StageChange(events.Event):
def __init__(self, newStage):
events.Event.__init__(self, newStage)
self.newStage = newStage
print self
def __repr__(self):
return '<StageChange Event %s>' % self.newStage
__str__ = __repr__
class Stages:
(
waitingForPlayers,
setup,
initialPlacement,
preRoll,
sevenRolledDiscard,
preRollRobberPlacement,
postRollRobberPlacement,
preRollChooseVictim,
postRollChooseVictim,
cardHarvest,
playerTurn,
gameOver,
) = [x[:-1] for x in shlex.split('''
waitingForPlayers,
setup,
initialPlacement,
preRoll,
sevenRolledDiscard,
preRollRobberPlacement,
postRollRobberPlacement,
preRollChooseVictim,
postRollChooseVictim,
cardHarvest,
playerTurn,
gameOver,
''')]
class TurnOptions:
(
playRobber,
build,
playYearOfPlenty,
playMonopoly,
trade,
#) = range(5)
) = [x[:-1] for x in shlex.split('''
playRobber,
build,
playYearOfPlenty,
playMonopoly,
trade,
''')]
class EventNotAllowedAtThisStage(Exception): pass
def allowedDuring(*allowedStages):
def decoratr(fn):
def wrappedfn(*args):
if not game:
raise ValueError('Game must be initialized before a function'
' decorated with allowedDuring can be called')
#print 'called ', fn
#print 'supposed to be in', allowedStages
if game.state.stage not in allowedStages:
print 'EVENT NOT ALLOWED', fn.__name__, game.state.stage
events.post('EventNotAllowedAtThisStage', fn.__name__,
game.state.stage,
allowedStages)
return
#raise EventNotAllowedAtThisStage(fn.__name__,
# game.state.stage,
# allowedStages)
retval = fn(*args)
return retval
wrappedfn.__name__ = fn.__name__
return wrappedfn
return decoratr
class GameState(object):
def __init__(self, game):
self.game = game
self._stage = Stages.waitingForPlayers
self._activePlayer = None
self._activeCountdown = None
self.awaitingDiscard = []
self.initialPlacementDirection = 1
events.registerListener(self)
def getActivePlayer(self):
return self._activePlayer
def setActivePlayer(self, player):
self._activePlayer = player
events.post('PlayerSet', player)
activePlayer = property(getActivePlayer, setActivePlayer)
def getStage(self):
return self._stage
def setStage(self, stage):
self._stage = stage
#events.post('StageChange', stage)
events.post(StageChange(stage))
stage = property(getStage, setStage)
def nextPlayer(self):
idx = self.game.players.index(self.activePlayer)
if self.stage == Stages.initialPlacement:
if idx == len(self.game.players)-1 and self.initialPlacementDirection == 1:
self.initialPlacementDirection = -1
return #keep the player the same
if idx == 0 and self.initialPlacementDirection == -1:
assert False, 'Should never get here'
idx += self.initialPlacementDirection
else:
idx += 1
idx %= len(self.game.players)
self.activePlayer = self.game.players[idx]
print 'NExT PLAYER', self.activePlayer
@allowedDuring(Stages.waitingForPlayers)
def onPlayerJoin(self, player):
print 'p jed', player
self.game.players.append(player)
if len(self.game.players) == 4:
self.stage = Stages.setup
def onCountdownOver(self):
self._activeCountdown = None
if self.stage == Stages.initialPlacement:
position = self.activePlayer.activeItem.findBestPosition()
self.activePlayer.activeItem.place(position)
if self.stage == Stages.preRoll:
# TODO ...force a roll
pass
@allowedDuring(Stages.setup)
def onBoardCreated(self, board):
self.activePlayer = self.game.players[0]
self.activePlayer.add(Settlement())
self.stage = Stages.initialPlacement
self._activeCountdown = Countdown(60)
@allowedDuring(Stages.initialPlacement, Stages.playerTurn)
def onItemPlaced(self, item):
if self.stage == Stages.initialPlacement:
if ( isinstance(item, Road)
and self.activePlayer == self.game.players[0]
and self.initialPlacementDirection == -1 ):
self.stage = Stages.preRoll
self._activeCountdown = Countdown(60)
elif isinstance(item, Road):
self.nextPlayer()
self.activePlayer.add(Settlement())
self._activeCountdown = Countdown(60)
elif isinstance(item, Settlement):
self.activePlayer.add(Road())
self._activeCountdown = Countdown(60)
else:
print 'FAIL'
else:
if item.owner.points >= 10:
self.stage = Stages.gameOver
@allowedDuring(Stages.preRoll)
def onDiceRoll(self, d1, d2):
rollValue = d1+d2
if rollValue == 7:
found = False
for player in self.game.players:
if player.mustDiscard():
found = True
self.awaitingDiscard.append(player)
if found:
self.stage = Stages.sevenRolledDiscard
else:
self.stage = Stages.postRollRobberPlacement
self.activePlayer.placeRobber()
else:
self.stage = Stages.cardHarvest
@allowedDuring(Stages.sevenRolledDiscard)
def onDiscard(self, player):
self.awaitingDiscard.remove(player)
if self.awaitingDiscard:
return # someone still needs to discard
self.stage = Stages.postRollRobberPlacement
self.activePlayer.placeRobber()
@allowedDuring(Stages.setup, Stages.preRollRobberPlacement,
Stages.postRollRobberPlacement)
def onRobberPlaced(self, robber):
if self.stage == Stages.setup:
return
if self.stage == Stages.postRollRobberPlacement:
self.stage = Stages.postRollChooseVictim
elif self.stage == Stages.preRollRobberPlacement:
self.stage = Stages.preRollChooseVictim
@allowedDuring(Stages.preRollChooseVictim, Stages.postRollChooseVictim)
def onRobRequest(self, thief, victim):
if thief == self.activePlayer:
if victim.cards:
card = random.choice(victim.cards)
victim.cards.remove(card)
thief.cards.append(card)
events.post('Rob', thief, victim, card)
if self.stage == Stages.postRollChooseVictim:
self.stage = Stages.playerTurn
elif self.stage == Stages.preRollChooseVictim:
self.stage = Stages.preRoll
@allowedDuring(Stages.preRollChooseVictim, Stages.postRollChooseVictim)
def onSkipRobRequest(self, thief):
if thief != self.activePlayer:
return
if self.stage == Stages.preRollChooseVictim:
self.stage = Stages.preRoll
elif self.stage == Stages.postRollChooseVictim:
self.stage = Stages.playerTurn
@allowedDuring(Stages.playerTurn)
def onChooseTwoCardsRequest(self, player, cardClasses):
if player == self.activePlayer:
cards = []
for cls in cardClasses:
card = cls()
cards.append(card)
player.cards.append(card)
events.post('ChooseTwoCards', player, cards)
@allowedDuring(Stages.playerTurn)
def onMonopolyRequest(self, player, cardClass):
print ' In MONO'
if player == self.activePlayer:
events.post('Monopoly', player, cardClass)
@allowedDuring(Stages.cardHarvest)
def onCardHarvestOver(self):
self.stage = Stages.playerTurn
def onPlayerDrewVictoryCard(self, player, card):
if player.points >= 10:
self.stage = Stages.gameOver
@allowedDuring(Stages.playerTurn)
def onTurnFinishRequest(self, player):
if player == self.activePlayer:
self.nextPlayer()
self.stage = Stages.preRoll
debugRolls = [(1,2), (4,2), (3,3), (2,1), (2,1), (1,2), (3,4)]
class Dice(object):
def __init__(self):
events.registerListener(self)
self.lastRoll = None
def onDiceRollRequest(self, player):
import random
if debugRolls:
a,b = debugRolls.pop(0)
else:
a = random.randrange(1,7)
b = random.randrange(1,7)
self.lastRoll = (a,b)
if (player != game.state.activePlayer
or game.state.stage not in [Stages.preRoll]
):
print 'illegal dice roll request', player
else:
print 'Dice roll:', a, b
events.post('DiceRoll', a, b)
class Robber(object):
def __init__(self):
events.registerListener(self)
self._tile = None
@allowedDuring(Stages.postRollRobberPlacement, Stages.preRollRobberPlacement)
def onRobberPlaceRequest(self, player, tile):
if player != game.state.activePlayer:
print 'illegal robber place request', player
return
self.placeOnTile(tile)
def placeOnTile(self, tile):
if self._tile:
self._tile.robber = None
self._tile = tile
self._tile.robber = self
print 'Robber placed:', tile
events.post('RobberPlaced', self)
def getTile(self):
return self._tile
tile = property(getTile)
class Card(object):
def __str__(self):
return '<Card %s %s>' % (self.__class__.__name__, id(self))
__repr__ = __str__
class Stone(Card): pass
class Brick(Card): pass
class Grain(Card): pass
class Sheep(Card): pass
class Wood(Card): pass
class Terrain(object): pass
class Desert(Terrain): pass
class Mountain(Terrain):
def getCardClass(self):
return Stone
class Mud(Terrain):
def getCardClass(self):
return Brick
class Wheat(Terrain):
def getCardClass(self):
return Grain
class Grass(Terrain):
def getCardClass(self):
return Sheep
class Forest(Terrain):
def getCardClass(self):
return Wood
class FiniteGameObject(object):
'''A game object of which there are a finite amount
eg, there are only 5 Settlements per player
'''
@staticmethod
def takeOne(cls, player, attrName):
'''A factory method which returns a new object of class 'cls', but
first checks to see if there are any available
'''
if len(getattr(player, attrName)) >= cls.maxPerPlayer:
raise LookupError('Player has the maximum number of %s' % attrName)
return cls()
class Settlement(FiniteGameObject):
cost = [Wood, Sheep, Grain, Brick]
maxPerPlayer = 5 #TODO check this
def __init__(self):
self.owner = None
self.location = None
@classmethod
def takeOne(cls, player):
return FiniteGameObject.takeOne(cls, player, 'smallSettlements')
@classmethod
def canBeBought(cls, player):
spots = player.findFreeCornersForSettlement()
return bool(spots)
class City(Settlement):
cost = [Grain, Grain, Stone, Stone, Stone]
maxPerPlayer = 5 #TODO check this
@classmethod
def takeOne(cls, player):
return FiniteGameObject.takeOne(cls, player, 'cities')
@classmethod
def canBeBought(cls, player):
spots = [x.location for x in player.smallSettlements]
return bool(spots)
class Road(FiniteGameObject):
cost = [Wood, Brick]
maxPerPlayer = 13 #TODO check this
def __init__(self):
self.owner = None
self.location = None
@classmethod
def takeOne(cls, player):
return FiniteGameObject.takeOne(cls, player, 'roads')
@classmethod
def canBeBought(cls, player):
spots = player.findFreeEdgesForRoad()
return bool(spots)
class VictoryCard(FiniteGameObject):
cost = [Grain, Sheep, Stone]
def __str__(self):
return '<VictoryCard %s %s>' % (self.__class__.__name__, id(self))
__repr__ = __str__
@classmethod
def takeOne(cls, player):
# TODO: shuffle
try:
subclass = allVictoryCardClasses.pop(0)
except IndexError:
raise IndexError('There are no more victory cards in the deck')
return subclass()
class PointCard(VictoryCard): pass
class Cathedral(PointCard): pass
class University(PointCard): pass
class Soldier(VictoryCard):
def action(self, player):
if game.state.stage == Stages.preRoll:
game.state.stage = Stages.preRollRobberPlacement
else:
game.state.stage = Stages.postRollRobberPlacement
player.placeRobber()
class YearOfPlenty(VictoryCard):
def action(self, player):
if game.state.stage != Stages.playerTurn:
raise Exception('should only play during player turn')
events.post('ShowPlayerChooseTwoCards', player)
class Monopoly(VictoryCard):
def action(self, player):
if game.state.stage != Stages.playerTurn:
raise Exception('should only play during player turn')
events.post('ShowMonopoly', player)
# TODO: use the official distribution
allVictoryCardClasses = [Monopoly, Soldier, Soldier, Soldier, Cathedral, University, Soldier, YearOfPlenty, Monopoly]
terrainClasses = [Wheat]*4 + [Mud]*3 + [Mountain]*3 + [Grass]*4 + [Forest]*4 + [Desert]
class Pip(object):
def __init__(self, value):
self.value = value
class Port(tuple):
def __hash__(self):
return id(self)
def __repr__(self):
if self[0]:
return '<'+ str(self[0].__name__) + '/' + str(self[1]) + '>'
else:
return '<'+ str(self[0]) + '/' + str(self[1]) + '>'
class Board(object):
'''
The .tiles attribute is laid out graphically like so:
[12]
[ 8] [11]
[10] [ 3] [16]
[ 2] [ 5]
[ 9] [ 1] [15]
[ 4] [ 7]
[14] [ 6] [19]
[13] [17]
[18]
'''
# this is the order to walk around the mapmodel so that it results
# in a clockwise spiral, starting at 18
spiralWalk = [18,13,14,9,10,8,12,11,16,15,19,17,6,4,2,3,5,7,1]
# graphical positions relative to the center tile, given in order
# of the spiral walk
graphicalPositions = [ (0,-4), (-1,-3),
(-2,-2), (-2,0),
(-2,2), (-1,3),
(0,4), (1,3),
(2,2), (2,0),
(2,-2), (1,-3),
(0,-2), (-1,-1),
(-1,1), (0,2),
(1,1), (1,-1),
(0,0)
]
def __init__(self):
pips = [Pip(i) for i in [5,2,6,3,8,10,9,12,11,4,8,10,9,4,5,6,3,11]]
random.shuffle(terrainClasses)
self.tiles = []
self.robber = Robber()
mapmodel.build()
for i, cls in enumerate(terrainClasses):
tile = mapmodel.allTiles[Board.spiralWalk[i]-1]
tile.graphicalPosition = Board.graphicalPositions[i]
tile.terrain = cls()
if cls == Desert:
self.robber.placeOnTile(tile)
else:
tile.pip = pips.pop(0)
self.tiles.append(tile)
self.ports = [Port((None,3)), Port((None,3)),
Port((None,3)), Port((None,3)),
Port((Grain,2)), Port((Brick,2)),
Port((Stone,2)), Port((Sheep,2)),
Port((Wood,2))]
random.shuffle(self.ports)
self.layOutPorts()
events.post('BoardCreated', self)
def layOutPorts(self):
positions = [(36,37), (39,40), (48,50),
(41,42), (34,35), (43,45),
(53,54), (25,27), (28,30),
]
# TODO, don't hardcode this, calculate it. All it would take
# for this to break would be the allCorners list changing order
for i, port in enumerate(self.ports):
num1, num2 = positions[i]
c1 = mapmodel.allCorners[num1-1]
c2 = mapmodel.allCorners[num2-1]
c1.port = port
c2.port = port
def populateGraphicalPositions(self):
for i in range(len(self.tiles)):
tile = mapmodel.allTiles[Board.spiralWalk[i]-1]
tile.graphicalPosition = Board.graphicalPositions[i]
class Game(object):
def __init__(self):
self._largestArmyPlayer = None
self._longestRoadPlayer = None
self._longestRoadLength = 0
self._largestArmySize = 0
self.state = GameState(self)
self.players = []
self.dice = Dice()
self.board = None
events.registerListener(self)
def getLargestArmyPlayer(self):
self.calculateLargestArmy()
return self._largestArmyPlayer
largestArmyPlayer = property(getLargestArmyPlayer)
def getLongestRoadPlayer(self):
self.calculateLongestRoad()
return self._longestRoadPlayer
longestRoadPlayer = property(getLongestRoadPlayer)
def calculateLargestArmy(self):
for player in self.players:
armySize = player.armySize()
#print 'player', player, 'len', armySize
if armySize > self._largestArmySize and armySize >= 3:
self._largestArmySize = armySize
self._largestArmyPlayer = player
def calculateLongestRoad(self, newRoad=None):
for player in self.players:
roadLen = player.longestRoadLength()
#print 'player', player, 'road len', roadLen
if roadLen > self._longestRoadLength and roadLen >= 5:
self._longestRoadLength = roadLen
self._longestRoadPlayer = player
def onStageChange(self, newStage):
if game.state.stage == Stages.setup:
self.board = Board()
if game.state.stage == Stages.cardHarvest:
for tile in self.board.tiles:
if tile.pip == None:
continue
if tile.pip.value == sum(self.dice.lastRoll):
cardClass = tile.terrain.getCardClass()
for corner in tile.corners:
if not corner.settlement:
continue
owner = corner.settlement.owner
if isinstance(corner.settlement, City):
# Cities get 2 cards
cards = [cardClass(), cardClass()]
else:
# Regular settlements get 1 card
cards = [cardClass()]
events.post('Harvest', cards, tile, owner)
events.post('CardHarvestOver')
def onItemPlaced(self, item):
if isinstance(item, Road):
self.calculateLongestRoad(item)
class Player(object):
colors = [ (250,0,0), (250,250,250), (250,150,0), (10,20,250) ]
def __init__(self, identifier):
self.identifier = identifier
i = int(identifier)
#self.color = (50*i, 10, (255-40*i))
self.color = Player.colors[i-1]
self.items = []
self.cards = []
self.victoryCards = []
self.playedVictoryCards = []
self.victoryCardPlayedThisTurn = False
self.victoryCardsBoughtThisTurn = []
self.offer = []
self.wants = []
self.latestItem = None
self.activeItem = None
events.registerListener(self)
def __str__(self):
return '<Player %s>' % str(self.identifier)
def __repr__(self):
return str(self)
def getProposal(self):
offerClasses = [card.__class__ for card in self.offer]
return [offerClasses, self.wants]
proposal = property(getProposal)
def getPoints(self):
points = 0
for item in self.items:
if not item.location:
# item was bought, but hasn't been set down yet
continue
if isinstance(item, Settlement):
if isinstance(item, City):
points += 2
else:
points += 1
for vcard in self.victoryCards:
if isinstance(vcard, PointCard):
points += 1
if self.hasLongestRoad:
points += 2
if self.hasLargestArmy:
points += 2
return points
points = property(getPoints)
allPoints = None
visiblePoints = None
def getRoads(self):
return [item for item in self.items
if isinstance(item, Road)]
roads = property(getRoads)
def getSmallSettlements(self):
return [item for item in self.items
if isinstance(item, Settlement)
and not isinstance(item, City)]
smallSettlements = property(getSmallSettlements)
def getCities(self):
return [item for item in self.items
if isinstance(item, City)]
cities = property(getCities)
def getHasLargestArmy(self):
return game.largestArmyPlayer == self
hasLargestArmy = property(getHasLargestArmy)
def getHasLongestRoad(self):
return game.longestRoadPlayer == self
hasLongestRoad = property(getHasLongestRoad)
def getPorts(self): #TODO
ports = set()
for s in self.smallSettlements + self.cities:
if s.location.port:
ports.add(s.location.port)
defaultPort = Port((None,4))
return list(ports) + [defaultPort]
ports = property(getPorts)
def armySize(self):
return len( [c for c in self.playedVictoryCards
if isinstance(c, Soldier)] )
def longestRoadLength(self):
# a player's road network can be thought of as a
# (possibly disconnected) graph that can have cycles
def visitRoad(road):
edge = road.location
if not edge:
# player hasn't placed this road on the board yet
return 0
lCorner = edge.corners[0]
rCorner = edge.corners[1]
visitedEdges = [edge]
lLen = 1 + max([walkLen(lCorner, e, visitedEdges)
for e in lCorner.edges])
visitedEdges = [edge]
rLen = 1 + max([walkLen(rCorner, e, visitedEdges)
for e in rCorner.edges])
return max([lLen, rLen])
def walkLen(fromCorner, edge, visitedEdges):
if edge == None:
# corners have 3 edges, but water-adjacent corners can have
# one of the corners == None
return 0
if not edge.road:
return 0
if edge.road.owner != self:
return 0
if edge in visitedEdges:
return 0
toCorner = edge.otherCorner(fromCorner)
visitedEdges = visitedEdges[:]
visitedEdges.append(edge)
return 1 + max([walkLen(toCorner, e, visitedEdges)
for e in toCorner.edges])
if not self.roads:
return 0
return max([visitRoad(r) for r in self.roads])
def add(self, item):
if isinstance(item, VictoryCard):
self.victoryCards.append(item)
self.victoryCardsBoughtThisTurn.append(item)
events.post('PlayerDrewVictoryCard', self, item)
else:
item.owner = self
self.items.append(item)
self.activeItem = item
events.post('PlayerPlacing', self, item)
def buy(self, itemClass):
price, needs = self.takePrice(itemClass, self.cards)
assert not needs
def neededCardClasses(self, itemClass):
handCopy = self.cards[:]
price, neededCardClasses = self.takePrice(itemClass, handCopy)
return neededCardClasses
def takePrice(self, itemClass, cards):
'''return (price, neededCardClasses)'''
price = []
neededCardClasses = []
for cardClass in itemClass.cost:
satisfiers = [card for card in cards
if card.__class__ == cardClass]
if not satisfiers:
neededCardClasses.append(cardClass)
continue
cards.remove(satisfiers[0])
price.append(satisfiers[0])
return price, neededCardClasses
def mustDiscard(self):
return len(self.cards) > 7
def getVictoryCardOfClass(self, victoryCardClass):
for c in self.victoryCards:
if isinstance(c, victoryCardClass):
return c
return None
@allowedDuring(Stages.playerTurn)
def onBuyRequest(self, player, itemClass):
if player != self or self != game.state.activePlayer:
return
needs = self.neededCardClasses(itemClass)
if needs:
events.post('PlayerCannotAfford', self, itemClass, needs)
return
if hasattr(itemClass, 'canBeBought'):
if not itemClass.canBeBought(self):
msg = '%s constraints not satisfied' % itemClass
events.post('PlayerCannotBuy', self, itemClass, msg)
return
try:
item = itemClass.takeOne(self)
except LookupError, e:
msg = str(e)
events.post('PlayerCannotBuy', self, itemClass, msg)
print msg
return
self.buy(itemClass)
self.add(item)
@allowedDuring(Stages.playerTurn, Stages.preRoll)
def onPlayVictoryCardRequest(self, player, victoryCardClass):
if player != self or self != game.state.activePlayer:
return
foundCard = None
for vcard in self.victoryCards:
if isinstance(vcard, victoryCardClass):
foundCard = vcard
break
if not foundCard:
events.post('Error', 'Player does not have requested card.')
return
if not hasattr(foundCard, 'action'):
events.post('Error', 'Card has no associated action.')
return
if self.victoryCardPlayedThisTurn:
events.post('Error', 'Only one victory card per turn.')
return
if foundCard in self.victoryCardsBoughtThisTurn:
events.post('Error', 'Victory card was bought this turn')
return
if (game.state.stage == Stages.preRoll
and not victoryCardClass == Soldier):
events.post('Error', 'Only soldier cards during pre-roll.')
return
self.victoryCardPlayedThisTurn = True
self.victoryCards.remove(foundCard)
self.playedVictoryCards.append(foundCard)
foundCard.action(self)
def placeRobber(self):
self.activeItem = game.board.robber
events.post('PlayerPlacing', self, self.activeItem)
def findPossibleVictims(self):
victims = set()
for c in game.board.robber.tile.corners:
if not c.settlement:
continue
if c.settlement.owner != self:
victims.add(c.settlement.owner)
return list(victims)
def findFreeCornersForSettlement(self):
freeCorners = []
for c in mapmodel.allCorners:
if c.settlement:
continue
freeCorners.append(c)
print 'free', freeCorners
freeCorners = self.filterUnblockedCorners(freeCorners)
print 'free', freeCorners
if game.state.stage == Stages.initialPlacement:
return freeCorners
freeCorners = self.filterRoadConnectedCorners(freeCorners)
print 'free', freeCorners
return freeCorners
def findCornersForCity(self):
corners = []
for item in self.items:
if isinstance(item, Settlement) and not isinstance(item, City):
corners.append(item.location)
return corners
def filterRoadConnectedCorners(self, corners):
connectedCorners = []
for corner in corners:
for e in corner.edges:
if not e or not e.road:
continue
if e.road.owner == self:
connectedCorners.append(corner)
return connectedCorners
def filterUnblockedCorners(self, corners):
# remove any corner that is adjacent to a settled corner
return [corner for corner in corners
if not
[peer for peer in corner.getPeers()
if peer.settlement]
]
def findFreeEdgesOfSettlement(self, settlement):
corner = settlement.location
edges = corner.getEdges()
return [e for e in edges if not e.road]
def findFreeEdgesForRoad(self):
freeEdges = []
for e in mapmodel.allEdges:
# if an edge has a road it is definitely not free
if e.road:
continue
for corner in e.corners:
# i can build on an edge next to my house
if corner.settlement:
settlement = corner.settlement
if settlement.owner == self:
freeEdges.append(e)
break
# i can build on an edge next to my road, as long
# as there's no opponent house in the way
else:
otherEdges = [edge for edge in corner.edges
if edge != e and edge != None]
for otherEdge in otherEdges:
if otherEdge.road:
if otherEdge.road.owner == self:
freeEdges.append(e)
break
return freeEdges
def findFreeTilesForRobber(self):
freeTiles = []
for t in mapmodel.allTiles:
if isinstance(t.terrain, Desert):
continue
if t.robber:
continue
freeTiles.append(t)
return freeTiles
@allowedDuring(Stages.sevenRolledDiscard)
def onDiscardRequest(self, player, discards):
if self != player:
return
print 'player %s discards %d from %d' %\
(self, len(discards), len(self.cards))
if not all([card in self.cards for card in discards]):
print 'Player tried to discard cards that he did not own'
return
if not len(discards) == len(self.cards)/2:
print 'Player tried to discard the wrong amout of cards'
return
for card in discards:
self.cards.remove(card)
events.post('Discard', self)
def onHarvest(self, cards, sourceTile, recipient):
if recipient == self:
self.cards += cards
def onMaritimeTrade(self, player, playerCards, portCards):
if self == player:
for card in playerCards:
self.cards.remove(card)
for card in portCards:
self.cards.append(card)
self.offer = []
self.wants = []
def onMonopolyGive(self, donor, receiver, cards):
print 'Give seen'
if self == receiver:
print 'I got', cards
for card in cards:
self.cards.append(card)
def onMonopoly(self, player, cardClass):
if player == self:
return
stolen = [card for card in self.cards if isinstance(card, cardClass)]
for card in stolen:
self.cards.remove(card)
events.post('MonopolyGive', self, player, stolen)
# -----------------------------------------------------------------------------
class HumanPlayer(Player):
def onPlayerSet(self, newPlayer):
if newPlayer == self:
self.victoryCardPlayedThisTurn = False
self.victoryCardsBoughtThisTurn = []
def onClickCorner(self, corner):
if game.state.activePlayer != self:
return
if not self.activeItem:
print 'NO ACTIVE ITEM!!!!!!!!!!!!!!!!!!'
return