-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathentities.py
1309 lines (1057 loc) · 49.9 KB
/
entities.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
#Other libs
from random import randint
from config import *
class Card(object):
"""A playing card. Has a suit and a value with ranges that should be defined in config.py"""
#At a later stage, it might be convenient to be able to display symbols for suits
#and shorter description for ranks.
def __init__(self, suit, value):
"""Create a card with the given suit and rank. Raises a ValueError if any of the arguments are invalid."""
#Make sure that the given parameters are valid
if(suit in SUITS):
self._suit = suit
else:
raise ValueError("suit must correspond to a member of SUITS in config.py")
if value >= MIN_RANK and value <= MAX_RANK:
self._value = value
else:
raise ValueError("value must be between MIN_RANK and MAX_RANK in config.py")
def getSuit(self):
"""Getter for _suit"""
return self._suit
def getValue(self):
"""Getter for _value"""
return self._value
def strSuit(self):
"""Return a string corresponding to the suit of the card."""
if self._suit == CLUBS:
return "clubs"
elif self._suit == DIAMONDS:
return "diamonds"
elif self._suit == HEARTS:
return "hearts"
elif self._suit == SPADES:
return "spades"
else:
return "illegal suit"
def strValue(self, context = 0):
"""Return a string corresponding to the rank of the card."""
if(self._value == 2):
return "deuce"
elif(self._value == 3):
return "trey"
elif(self._value == 4):
return "four"
elif(self._value == 5):
return "five"
elif(self._value == 6):
if context == 0:
return "six"
else:
return "sixe" #hack to get the correct plural
elif(self._value == 7):
return "seven"
elif(self._value == 8):
return "eight"
elif(self._value == 9):
return "nine"
elif(self._value == 10):
return "ten"
elif(self._value == 11):
return "jack"
elif(self._value == 12):
return "queen"
elif(self._value == 13):
return "king"
elif(self._value == 14):
return "ace"
else:
return "illegal rank"
def printCard(self):
"""Print info about the card (rank and suit)."""
print("\t" + self.strValue() + " of " + self.strSuit())
class HandRange(object):
"""A class for hand ranges and similar info about how a player has played a hand."""
def __init__(self, mean = 50, spread = 50):
"""Initialization of the range"""
self._mean = mean
self._spread = spread
#might include measures of aggression/commitment if needed
def adjustMean(self, factor):
"""Adjust mean by the factor"""
self._mean *= factor
def getMean(self):
"""Getter for _mean"""
return self._mean
def adjustSpread(self, factor):
"""Adjust spread by the factor"""
self._spread *= factor
def getSpread(self):
"""Getter for _spread"""
return self._spread
class PokerPerson(object):
"""An adt class for persons sitting at a poker table (players and dealer)."""
def __init__(self):
"""Default initialization of the ADT."""
print("Please don't try to initialize an object of this ADT.")
def processHand(self, hand):
"""Default method for reading, sorting and classifying a poker hand."""
print("Please make sure to implement a processHand method for the descendant of this ADT.")
def sortHand(self, hand, drawing = False, testMode = False):
"""Sorts the hand. Prints what hand it is. Then returns the sorted hand."""
cards = list(hand) #cards to be sorted/classified
foot = 0 #Index of the first unprocessed card
head = len(cards) - 1 #Index of the last unprocessed card
counter = 0 #To count the number of cards having the same rank as the card at foot
pattern = HICARD #Look in config.py for an overview of the possible patterns
#Sort, with aces first and deuces last
cards.sort(key = lambda card: card._value, reverse = True)
#Check for hands containing more than one card of equal rank (pairs, trips, two pair etc.)
#The cards will be sorted so paired cards appear before unpaired and trips before pairs (in a full house)
while(foot <= head):
counter = sum(c.getValue() == cards[foot].getValue() for c in cards)
if counter == 1:
#Move the card to the back. Move head forward, so the card will not be considered again.
#Since all unpaired are treated this way, their order is preserved.
cards.append(cards.pop(foot))
head -= 1
else:
if counter == 4:
pattern = QUADS
foot = head + 1 #With quads at the foot, the hand is already sorted, so no need to loop again
elif counter == 3:
if pattern == PAIR:
#A pair has already been processed. Since the trips are more interesting, the pair is moved to the end of the list
pattern = FULL_HOUSE
cards.append(cards.pop(0))
cards.append(cards.pop(0))
foot = head + 1 #With a full house, all cards have been sorted, so no need to loop again
else:
pattern = TRIPS
foot += counter #The trips are in the right place. Move the foot to the first card that doesn't match their rank
elif counter == 2:
if pattern == TRIPS:
#The trips have been processed, and the pair is therefore correctly placed
pattern = FULL_HOUSE
foot = head + 1 #With a full house, all cards have been sorted, so no need to loop again
elif pattern == PAIR:
#Since none of the pairs are moved, their order is preserved
pattern = TWO_PAIR
foot = head + 1 #With two pairs, the fifth card will always be in the correct place by now
else:
#The pair is likely to be correctly placed, so move the foot to the first card that doesn't match the rank
pattern = PAIR
foot += counter
else:
#It is assumed that there are four suits and no jokers, so the count should never be outside the interval [1,4]
pattern = NO_HAND
foot = head + 1
print("Illegal hand")
#If no cards were of equal rank, the hand is either a straigh flush, a flush, a straight or a hi-card hand.
if(pattern == HICARD):
#Order a wheel (5-high straight) correctly
if cards[0].getValue() == 14 and cards[1].getValue() == 5:
cards.append(cards.pop(0))
#Check for straights and flushes
pattern = self.findSequence(cards, drawing)
elif(pattern == PAIR) and drawing:
#Check for straight- and flush-draws
pattern = self.findSequence(cards, drawing)
hiDraw = None
#If the hand contained a draw, sort it so the first card in the hand is not part of the draw
if drawing:
if pattern == UNPSF:
temp = cards.pop(1)
cards.insert(0, temp)
pattern = PSTRFLDRAW
elif pattern == UNPFL:
temp = cards.pop(1)
cards.insert(0, temp)
pattern = PFLDRAW
elif pattern == UNSTRFL:
if cards[0].getValue() == 14 and cards[2].getValue() <= 5: #To rearrange wheel flush draws
temp = cards.pop(0)
cards.append(temp)
else:
temp = cards.pop(-1)
cards.insert(0, temp)
pattern = STRFLDRAW
elif pattern == UNFL:
suit = cards[0].getSuit()
for i in range(1, len(cards)):
if cards[i].getSuit() != suit:
temp = cards.pop(i)
cards.insert(0, temp)
pattern = FLDRAW
break
elif pattern == UNSTR:
temp = cards.pop(-1)
cards.insert(0, temp)
pattern = STRDRAW
#If there's a wheel flush draw, put the ace in the last position (if it's not paired)
if pattern == PSTRFLDRAW:
if cards[2].getValue() == 14 and cards[0].getValue() <= 5:
temp = cards.pop(2)
cards.append(temp)
#If there's a broadway draw, put the small card in the first position
if pattern == BWDRAW:
temp = cards.pop(-1)
cards.insert(0, temp)
if pattern > HICARD and pattern < PAIR:
if pattern >= PBWDRAW:
hiDraw = cards[2]
else:
hiDraw = cards[1]
#Print each card in the hand if in test mode.
if testMode:
for card in cards:
card.printCard()
self.printHandInfo(pattern, cards[0], hiDraw)
return (cards, pattern)
def findSequence(self, hand, drawing = False):
"""Returns the pattern found (straight, flush, straightflush or hicard). Unless drawing is False, it also looks at drawing patterns. Make sure to follow the rules given below.
hand: The hand to be examined. It must be sorted and follow the rules given below. Look for a sortHand() method to sort the hand correctly.
drawing: If true, look for drawing patterns. Otherwise, only consider the current value/category of the hand.
If drawing = False, the hand must not contain any duplicate ranks (pairs, trips, quads).
If drawing = True, the hand may contain one pair but no other duplicate ranks.
The method does not test that the provided hand follows these rules, but the return value can not be trusted if the rules are not followed.
"""
suitCount = sum(card.getSuit() == hand[0].getSuit() for card in hand) #Number of cards having the same suit as the first card
firstRank = hand[0].getValue()
swap = False #Determine if a pair needs to be swapped
#Test for straights/flushes
if(firstRank == 5) or (firstRank - hand[4].getValue() == 4):
if suitCount == 5:
return STRFL
elif firstRank != hand[1].getValue(): #To verify that the hand does not in fact contain a pair (invalidating the possibility of a straight)
return STRAIGHT
elif suitCount == 5:
return FLUSH
#Test for draws
if drawing:
#Test for flush draws
if suitCount == 4:
flushing = True
swap = True
elif sum(card.getSuit() == hand[1].getSuit() for card in hand) == 4:
flushing = True
else:
flushing = False
#If paired, test the last four cards for straight/flush draws (else test all five cards)
if firstRank == hand[1].getValue():
thirdRank = hand[2].getValue()
fifthRank = hand[4].getValue()
#Broadway?
if fifthRank >= 10 and (firstRank == 14 or (thirdRank == 14 and firstRank >= 10)):
if flushing:
if swap:
return UNPSF
else:
return PSTRFLDRAW
else:
return PBWDRAW
#Open ended?
if(firstRank != 14 and thirdRank != 14): #Cannot be openended if it contains an ace
if((thirdRank - fifthRank == 2 and (firstRank == thirdRank + 1 or firstRank == fifthRank - 1))
or thirdRank - fifthRank == 3 and firstRank < thirdRank and firstRank > fifthRank):
if flushing:
if swap:
return UNPSF
else:
return PSTRFLDRAW
else:
return PSTRDRAW
#Flush draw with or without gutshot?
if flushing:
if((thirdRank == fifthRank + 2 and (firstRank == thirdRank + 2 or firstRank == fifthRank - 2)) or
(thirdRank == fifthRank + 3 and (firstRank == thirdRank + 1 or firstRank == fifthRank - 1)) or
(thirdRank == fifthRank + 4 and (firstRank < thirdRank and firstRank > fifthRank)) or
(thirdRank == 14 and firstRank <= 5 and hand[3].getValue() <= 5) or
(firstRank == 14 and thirdRank <= 5)):
if swap:
return UNPSF
else:
return PSTRFLDRAW
else:
if swap:
return UNPFL
else:
return PFLDRAW
#Otherwise, the hand is assumed to be a pair (since the caller should not call this method if the hand contains quads, trips or more than one pair)
return PAIR
else:
#Unpaired hand...
fourthRank = hand[3].getValue()
#Open ended?
if hand[1].getValue() - hand[4].getValue() == 3:
if flushing:
if hand[0].getSuit() != hand[1].getSuit() and hand[0].getSuit() != hand[4].getSuit():
return STRFLDRAW
elif ((hand[4].getSuit() != hand[0].getSuit() and firstRank - hand[1].getValue() == 2) or
(firstRank == 14 and hand[1].getValue() == 6 and hand[1].getSuit() != hand[0].getSuit())):
return UNSTRFL
else:
return UNFL
else:
return STRDRAW
if firstRank - fourthRank == 3 and firstRank < 14:
if flushing:
if hand[4].getSuit() != hand[0].getSuit() and hand[4].getSuit() != hand[3].getSuit():
return UNSTRFL
elif hand[0].getSuit() != hand[4].getSuit():
if fourthRank - hand[4].getValue() == 2:
return STRFLDRAW
else:
return FLDRAW
else:
return UNFL
else:
return UNSTR
#Broadway? (must check open-ended first, since a hand can both be open ended and have a broadway draw)
if firstRank == 14 and (fourthRank >= 10):
if flushing:
if hand[4].getSuit() != hand[0].getSuit() and hand[4].getSuit() != hand[3].getSuit():
return UNSTRFL
elif hand[0].getSuit() != hand[4].getSuit():
return FLDRAW
else:
return UNFL
else:
return BWDRAW
#Flush draw with or without gutshot?
if flushing:
if ((firstRank - fourthRank == 4 and hand[4].getSuit() != hand[0].getSuit() and hand[4].getSuit() != hand[3].getSuit()) or
(firstRank == 14 and hand[2].getValue() <= 5 and hand[0].getSuit() == hand[4].getSuit() and hand[1].getSuit() != hand[0].getSuit())):
return UNSTRFL
if hand[1].getValue() - hand[4].getValue() == 4 and hand[0].getSuit() != hand[1].getSuit() and hand[0].getSuit() != hand[4].getSuit():
return STRFLDRAW
#If there was no straight draw containing the same cards as the flush draw, classify this as a flush draw
if hand[0].getSuit() != hand[1].getSuit() and hand[0].getSuit() != hand[4].getSuit():
return FLDRAW
else:
return UNFL
return HICARD
def printHandInfo(self, category, firstCard, firstDraw):
"""Prints info about the hand based on its pattern/category and the most significant card."""
msg = "" #Message to be printed about the hand
if category < 0:
#Illegal value
msg = "Illegal value for the hand category."
elif category == HICARD:
msg = "high card " + firstCard.strValue() + "."
elif category == BWDRAW:
msg = "broadway straight draw."
elif category == STRDRAW:
msg = firstDraw.strValue() + " high open-ender."
elif category == FLDRAW:
msg = firstDraw.strValue() + " high flush draw."
elif category == STRFLDRAW:
msg = firstDraw.strValue() + " high straight flush draw."
elif category <= PAIR:
msg = "a pair of " + firstCard.strValue(-1) + "s"
if category < PAIR and firstCard.getValue() > firstDraw.getValue() and (firstCard.getValue() < 14 or firstDraw.getValue() > 5):
hiPair = True #The expression in the parantheses are to avoid that wheel draws are considered ace-high.
else:
hiPair = False
if category == PBWDRAW:
msg += " with a broadway straight draw."
elif category == PSTRDRAW:
msg += " with a(n) "
if hiPair:
msg += firstCard.strValue()
else:
msg += firstDraw.strValue()
msg += " high open-ender."
elif category == PFLDRAW:
msg += " with a(n) "
if hiPair:
msg += firstCard.strValue()
else:
msg += firstDraw.strValue()
msg += " high flush draw."
elif category == PSTRFLDRAW:
msg += " with a(n) "
if hiPair:
msg += firstCard.strValue()
else:
msg += firstDraw.strValue()
msg += " high straight flush draw."
elif category == PAIR:
msg += "."
elif category == TWO_PAIR:
msg = firstCard.strValue(-1) + "s up."
elif category == TRIPS:
msg = "trip " + firstCard.strValue(-1) + "s."
elif category == STRAIGHT:
msg = firstCard.strValue() + "-high straight."
elif category == FLUSH:
msg = firstCard.strValue() + "-high flush."
elif category == FULL_HOUSE:
msg = firstCard.strValue(-1) + "s full."
elif category == QUADS:
msg = "quad " + firstCard.strValue(-1) + "s."
elif category == STRFL:
msg = firstCard.strValue() + "-high straight flush."
else:
msg = "Unknown hand type."
print(msg)
class Dealer(PokerPerson):
"""A poker dealer.
_cards: A full deck of cards that should be immutable.
_deck: An actual deck of cards that can be used(mutated).
It seems unnecessary to actually shuffle the cards at the start of a new round, so the dealer should just
pick (pop) a random card from the deck for each dealt card.
Since the deck will no longer contain the cards that was drawn during the latest round, a
resetDeck() method can be called to reassemble the deck.
"""
def __init__(self):
"""Create a dealer with a dummy version of a deck (that can't be altered), and an actual deck."""
#Set up a full deck that can be copied into an actual deck when starting a new hand
#This seemed easier than collecting cards from the muck before a new deal.
self._cards = list()
self._initCards()
#Initialize the actual deck
self._deck = list()
self.resetDeck()
def _initCards(self):
"""Create the cards of a deck. Only planned use is for the constructor."""
for s in SUITS:
for v in range(MIN_RANK, MAX_RANK + 1):
newCard = Card(s, v)
self._cards.append(newCard)
def resetDeck(self):
"""Reassemble the deck by making _deck a copy of _cards."""
#Copy the dummy deck
self._deck = list(self._cards)
def dealHand(self):
"""Deal a hand from the deck.
returns a list containg the cards of the dealt hand.
"""
return self.processHand(self.dealCards())
def dealCards(self, n = HAND_SIZE):
"""Deal a given number of cards from the deck.
n: Number of cards to deal.
returns a list containing the dealt cards.
"""
hand = list()
for i in range(n):
hand.append(self._deck.pop(randint(0, len(self._deck) - 1)))
return hand
def processHand(self, hand):
"""Sorts the hand. Prints what hand it is. Then returns the sorted hand."""
cards = self.sortHand(hand)
return cards[0]
def showDown(self, players, potsize):
"""Awards the pot to the player with the best hand."""
bestHand = self._createHand(NUTLOW)
cmpVal = 0
winners = list()
for player in players:
hand = self.processHand(player.getHand())
cmpVal = self.cmpHands(hand, bestHand)
if cmpVal > 0:
winners = list() #Remove beaten players from the list
winners.append(player)
bestHand = hand
elif cmpVal == 0:
winners.append(player)
#print("winners: " + str(len(winners)) + "\n")
winnings = potsize // len(winners)
rest = potsize % len(winners)
while rest != 0:
winners[randint(0, len(winners) - 1)].chipUp(1)
rest -= 1
for winner in winners:
winner.chipUp(winnings)
def cmpHands(self, firstHand, lastHand):
"""Return 1 if the first hand is best, 0 if they're equal and -1 otherwise."""
first = self.rateHand(firstHand)
last = self.rateHand(lastHand)
if first > last:
return 1
elif first < last:
return -1
else:
for i in range(len(firstHand)):
if firstHand[i].getValue() > lastHand[i].getValue():
return 1
elif firstHand[i].getValue() < lastHand[i].getValue():
return -1
return 0
def rateHand(self, hand):
"""Returns a number telling what category the hand belongs in"""
counter = sum(card.getValue() == hand[0].getValue() for card in hand)
if counter == 4:
return QUADS
elif counter == 3:
if hand[3].getValue() == hand[4].getValue():
return FULL_HOUSE
else:
return TRIPS
elif counter == 2:
if hand[2].getValue() == hand[3].getValue():
return TWO_PAIR
else:
return PAIR
elif counter != 1:
return NO_HAND
else:
return self.findSequence(hand)
def _printCards(self):
"""Print the cards of the entire deck (for testing)"""
for card in self._cards:
card.printCard()
def printDeck(self):
"""Print the cards left in the actual deck"""
for card in self._deck:
card.printCard()
def _createHand(self, template):
"""Method that creates specific hands. Details omitted to avoid unauthorized use."""
#Deal a hand.
#The template argument should contain a list of indices corresponding to the position of the cards in a newly reset deck.
#It is easiest to sort the list descending, since popping messes with the indices of the following elements.
#The method provides the possibility of a backdoor/cheat, so should probably be removed if used in a game.
cards = list()
#Reset the deck to make sure that the intended cards are drawn
self.resetDeck()
for ind in template:
cards.append(self._deck.pop(ind))
#Sort the hand and display what kind of hand it is. Then return it.
return self.processHand(cards)
class Player(PokerPerson):
"""A poker player"""
def __init__(self, type = '', balance = MIN_STAKE * BUY_IN):
"""Setting up variables for the player."""
self._chips = balance #Default should be one buy-in at the smallest stakes unless something else is given
self._cash = 0 #Cash that is not converted to chips (if moving up, the remainder of cash that cannot be used to buy new chips end up here)
self._wager = 0 #Chips that is on the table, but is not yet in the pot
self._type = type
self._strat = self.setStrat() #To hold a list that determines the strategy of the player
self._hand = list()
self._sorted = False #Should only be true if processHand() has been called since last time the player was dealt something.
self._pattern = -1 #Category of hand held (see config.py for the range)
self._rating = 0 #The player's rating of the hand
self._played = 0 #Number of hands played
self._status = MOVE_UP #For determining if the player is seated/moving up/moving down or busto
self._ups = 0 #Number of times the player has moved up in stakes
self._downs = 0 #Number of times the player has moved down in stakes
def getChips(self):
"""Return the number of chips the player has."""
return self._chips
def chipUp(self, amount):
"""Increases the player's chips by the given amount. A negative value will decrease the player's chips."""
self._chips += amount #Currently, it is assumed that the caller specifies a correct/reasonable amount.
def takeChips(self):
"""Sets _chips to 0. Intended for use by cashiers."""
self._chips = 0
def getCash(self):
"""Getter for _cash"""
return self._cash
def setCash(self, value):
"""Setter for _cash"""
self._cash = value
def addCash(self, value):
"""Alternative setter for _cash. Increments it by the given value."""
self._cash += value
def getWager(self):
"""Getter for _wager"""
return self._wager
def setWager(self, value):
"""Setter for _wager"""
self._wager = value
def getType(self):
"""Return a string saying what type of player this is."""
if len(self._type) == 0:
return 'unknown'
else:
return self._type
def setStrat(self):
"""Initilize player strat according to the file for its player type"""
if len(self._type) == 0:
print('Error: Undefined player type has no strat\n')
return
command = 'from ' + self._type + ' import STRAT'
exec(command)
self._strat = STRAT
def getHand(self):
"""Return the player's hand"""
return self._hand
def setHand(self, hand):
"""Give a hand to the player. The player will then process the hand."""
self._sorted = False
cards = self.processHand(hand)
if cards is not None:
self._hand = cards
self._played += 1 #If using this when drawing cards, this line must be made conditional
def getRating(self):
"""Return the player's rating of the hand."""
return self._rating
def setRating(self, value):
"""Setter for _rating"""
self._rating = value
def countHand(self):
"""Setter for _played. Increments it by 1."""
self._played += 1
def getStatus(self):
"""Return the current status of the player."""
return self._status
def setStatus(self, status):
"""Set the status of the player."""
self._status = status
def moveUp(self):
"""Increments _ups by 1"""
self._ups += 1
def getUps(self):
"""Getter for _ups"""
return self._ups
def moveDown(self):
"""Increments _downs by 1"""
self._downs += 1
def getDowns(self):
"""Getter for _downs"""
return self._downs
def moveIn(self):
"""Bet the maximum amount on a hand. Used for testing. The finished simulator should be based on a limit structure."""
self.chipUp(MIN_STAKE * -6) #2 big bets predraw, 4 postdraw
def processHand(self, hand):
"""Sorts the hand. Prints what hand it is. Updates _pattern and _sorted accordingly."""
if self._sorted:
return None #The hand is already sorted
cards = self.sortHand(hand, False)
self._pattern = cards[1]
self._sorted = True
return cards[0]
def rateHand(self):
"""Give a rating to the hand based on global constants. If the categories in the config file is changed, this function needs to be changed too."""
firstRank = self._hand[0].getValue()
if self._pattern > TRIPS:
self._rating = TRIP_K
elif self._pattern == TRIPS:
if firstRank >= 13:
self._rating = TRIP_K
elif firstRank >= 7:
self._rating = TRIP_G
else:
self._rating = A_UP
elif self._pattern == TWO_PAIR:
if firstRank >= 14:
self._rating = A_UP
elif firstRank >= 13:
self._rating = K_UP
elif firstRank >= 11:
self._rating = J_UP
elif firstRank >= 9:
self._rating = I_UP
elif firstRank >= 7:
self._rating = G_UP
else:
self._rating = ACES[0]
elif self._pattern == PAIR:
self._rating = PAIRS[firstRank][1]
# elif self._pattern >= PBWDRAW:
# pair = P_A + P_DELTA * (14 - firstRank)
# if self._pattern == PSTRFLDRAW:
# self._rating = min(pair, SFDRAW)
# elif self._pattern == PFLDRAW or self._pattern == PSTRDRAW:
# self._rating = min(pair, SEQDRAW)
# elif self._rating == PBWDRAW:
# self._rating = pair #The pair will always be worth more than the broadway draw
# else:
# self._rating = TRASH #This should never happen, since there should not be any other categories within this elseif
# elif self._pattern > HICARD:
# if self._pattern == STRFLDRAW:
# self._rating = SFDRAW
# elif self._pattern == FLDRAW or self._pattern == STRDRAW:
# self._rating = SEQDRAW
# elif self._pattern == BWDRAW:
# self._rating = BRDRAW
# else:
# self._rating = TRASH #This should never happen, since there should not be any other categories within this elseif
##Suggested cleanup: Do secondRank init in an else, and nest the cases inside
elif firstRank == 14:
secondRank = self._hand[1].getValue()
if secondRank == 13:
self._rating = AK_HI[1]
elif secondRank == 12:
self._rating = AQ_HI[1]
elif secondRank == 11:
self._rating = AJ_HI[1]
elif secondRank == 10:
self._rating = AT_HI[1]
else:
self._rating = A_HI[1]
elif firstRank == 13:
secondRank = self._hand[1].getValue()
if secondRank == 12:
self._rating = KQ_HI[1]
elif secondRank == 11:
self._rating = KJ_HI[1]
elif secondRank == 10:
self._rating = KT_HI[1]
else:
self._rating = K_HI[1]
elif firstRank == 12:
secondRank = self._hand[1].getValue()
if secondRank == 11:
self._rating = QJ_HI[1]
elif secondRank == 10:
self._rating = QT_HI[1]
else:
self._rating = Q_HI[1]
elif firstRank == 11:
if self._hand[1].getValue() == 10:
self._rating = JT_HI[1]
else:
self._rating = J_HI[1]
elif firstRank == 10:
self._rating = T_HI[1]
else:
self._rating = I_HI[1]
def actPre(self, wagers):
"""Decide on waging before the draw. Dependent on exact ordering of the player's strat list."""
waged = self.getWager()
rating = self.getRating()
if(wagers == 2): #No raise yet
if rating > self._strat[0]: #Since limping has not been implemented, this is the least aggressive strat parameter
if waged < 2: #Unless in big blind, fold and concede chips to the pot
self.chipUp(-waged)
else:
self.setWager(4)
elif(wagers == 4): #The pot has been raised
if rating > self._strat[3]:
self.chipUp(-waged)
elif rating > self._strat[1]:
self.setWager(4)
else:
self.setWager(6)
elif(wagers == 6): #The pot has been reraised
if rating > self._strat[4]:
if((rating <= self._strat[3]) and (waged == 4)): #Only needs to call a single raise
self.setWager(6)
else: #Fold
self.chipUp(-waged)
elif rating > self._strat[2]:
self.setWager(6)
else:
self.setWager(8)
elif(wagers != 8): #An incorrect argument has been given
return wagers #Assumes that error handling detects this case in the caller
else: #The betting has been capped
if rating > self._strat[5]:
if((rating <= self._strat[3]) and (waged == 6)): #Only needs to call a single raise
self.setWager(8)
else: #Fold
self.chipUp(-waged)
else:
self.setWager(8)
return self.getWager()
class Table(object):
"""A poker table.
_players: A list of the players that are seated here
_pot: The amount of chips that have been collected for the winner(s) of the current round.
_button: The index of the player that has the dealer button at the table.
_rounds: The number of rounds/hands that have been played at the table.
"""
def __init__(self):
"""Create a new poker table."""
self._players = list()
self._pot = 0 #Amount of chips in the middle
self._button = 0 #The position of the button based on the index of the corresponding player
self._rounds = 0
def getPot(self):
"""Getter for _pot"""
return self._pot
def addToPot(self, amount):
"""Add chips to _pot.
amount: The number of chips to add
"""
self._pot += amount
def resetPot(self):
"""Set _pot to 0. Should only be called after the pot has been given to the winner(s)."""
self._pot = 0
def getButton(self):
"""Getter for _button."""
return self._button
def passButton(self):
"""Setter for _button. Passes the button one position clockwise, and increments rounds by 1."""
self._button = (self._button + 1) % SEATS
self._rounds += 1
def getRounds(self):
"""Getter for _rounds"""
return self._rounds
def seat(self, player, position = -1):
"""Seat a player at the table.
player: The player that is to be seated.
position: The index where the player should be inserted in the list of players.
If a negative position is given, the player is seated in the position where the big blind will be posted.
The status of the player will be set to SEATED.
There is currently no error checking for trying to seat a player at an invalid position or a full table.
"""
if position < 0:
pos = (self.getButton() + 2) % 5
else:
pos = position
player.setStatus(SEATED) #seated
self._players.insert(pos, player)
def playHand(self, stake, dealer = None):
"""Play one hand at the table.
stake: The amount of cash that corresponds to a big blind.
dealer: A dealer must be supplied to give players their hands. Otherwise, they'll just bet the max amount and flip for the pot.
returns: The player that is about to post the big blind if that player doesn't meet the requirements for playing at the table.
Otherwise, it returns None.
Cash isn't really used at the table, but the method needs to know if the table is at the lowest stakes available.
"""
wagers = 0 #The amount that the players must match to stay in the hand
#If there's no dealer, players just move in and draws for who wins the pot
if(dealer == None):
for player in self._players:
player.moveIn()
player.countHand()
self.addToPot(6 * MIN_STAKE)
#Award the pot to a random player
self._players[randint(0, SEATS - 1)].chipUp(self.getPot())
self.resetPot()
else:
#Post SB
target = (self.getButton() + 1) % SEATS
self._players[target].setWager(1)
#Post BB
target = (target + 1) % SEATS
self._players[target].setWager(2)
#Deal hands
target = (target + 1) % SEATS