-
Notifications
You must be signed in to change notification settings - Fork 5
/
Game Code.py
371 lines (307 loc) · 14.7 KB
/
Game Code.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
# =============================================================================
# All code from this file is written by Tarun Ravi
# Date Written: 6/7/2020
# =============================================================================
import pygame
import numpy as np
from collections import defaultdict
import pygame.freetype
height = 600
width = 1000
screen = pygame.display.set_mode((width, height))
backgroundImage = pygame.image.load('Cards/background.jpg')
backgroundImage = pygame.transform.scale(backgroundImage, (height, width)) #scales an image
pygame.init()
effect = pygame.mixer.Sound('Cards/cardSound.wav')
programIcon = pygame.image.load('Cards/playing_cards.png')
pygame.display.set_icon(programIcon)
actionSpace = [0,1] #These are the actions the AI can take, 0 for stick, 1 for hit
#Changeable Variables
dealerLimit = 18 #Sum needed for dealer to stop hitting.
breakTime = 2 #Delay (in seconds) between games
FPS = 30 #How many frames will be drawn every second.
volume = True
#Changeable AI Variables
AI = False #If False, you can play against the dealer without the AI
e = 0.1 #Epsilon Value for Monte Carlo Algorithm
gamma = 1 #Gamma Value for Monte Carlo Algorithm
alpha=0.02 #Alpha Value for Monte Carlo Algorithm
# =============================================================================
# Card Class
# =============================================================================
class card():
def __init__(self, x, y, player, show=True):
self.number = np.random.choice(range(1, 14)) #1: Ace, 2-10: Number Card, 11: Jack, 12: Queen, 13: King
self.x = x #X location of the card
self.y = y #Y location of the card
self.show = show #If false, the card will be face down. If true, card will be revealed.
self.player = player #Identify if the card is for the player's or dealer's
if self.player: #Different card image if player vs dealer
location = 'Cards/card_b_s'
else:
location = 'Cards/card_b_h'
self.image = pygame.image.load(location+str(self.number)+'.png')
self.w, self.h = self.image.get_rect().size #Width/height of card image
if self.number>10:self.number=10 #If a card is a facecard, its value is 10
if volume: #Play sound only if audio is turned on
effect.play()
def reveal(self): #The dealers second card is initially hidden, this will reveal that card
self.show = True
if volume: #Play sound only if audio is turned on
effect.play()
def draw(self): #Will draw the card to screen
if self.show:
screen.blit(self.image, (self.x,self.y))
else:
screen.blit(pygame.image.load('Cards/faceDown.png'), (self.x,self.y))
#Hit method, will create new card for either the dealer or player
def hit(cardCount, isPlayer):
if isPlayer:
return card(850-130*cardCount-5*cardCount, 378, True) #Will create a new card next to any existing cards
else:
return card(20 + 130*cardCount + 5*cardCount, 20, False) #Will create a new card next to any existing cards
#Will determine if the ace can be counted as 11, without it exceeding the 21st limit
def checkAce(playersCard, hidden=False):
isAce=False
sumOfList = 0
for i in playersCard:
sumOfList += i.number
if i.number == 1:
isAce = True
if isAce and sumOfList-1+11<=21:
return True
return False
#Will return the player/dealer's hand sum. This will return the highest sum possible, in the case of aces.
def checkSum(cards, hidden=False):
cardSum = 0
for i in cards:
if not hidden:
cardSum += i.number
else:
if i.show == True:
cardSum += i.number
if checkAce(cards, hidden):
cardSum = cardSum - 1 + 11
return cardSum
#Will determine who won/lost/draw
def whoWon(dealer,playersCard, dealersCards):
playersSum = checkSum(playersCard)
dealersSum = checkSum(dealersCards)
if playersSum>21:
return True, False #Game over, who won
if playersSum==21:
return True, True
if dealersSum == 21:
return True, False
if not dealer:
return False, False
if dealer and playersSum <= 21 and dealersSum>21:
return True, True #Game over, who won
if dealer and playersSum>dealersSum:
return True, True
if dealer and dealersSum>playersSum:
return True, False
if dealer and dealersSum==playersSum:
return True, None
else:
return False, False
#Given an action (0 or 1), will execute the action.
def aiStep(action, playersCard, dealersCards):
if action == 1: #Hit
playersCard.append(hit(len(playersCard), True))
else:
dealersCards[-1].reveal()
while checkSum(dealersCards) <= dealerLimit:
dealersCards.append(hit(len(dealersCards), False))
return playersCard, dealersCards
#Will follow an epsilon greedy policy, given Q to determine the next action
#Meaning it will determine the best action to take in the current moment
def genAction(state, e, Q):
probHit = Q[state][1]
probStick = Q[state][0]
if probHit>probStick:
probs = [e, 1-e]
elif probStick>probHit:
probs = [1-e, e]
else:
probs = [0.5, 0.5]
action = np.random.choice(np.arange(2), p=probs)
return action
#Will create the current state value.
#The state value is a tuple containing the player's current sum, dealer's current sum, and if the ace can be counted as 11
def createStateValues(playersCard, dealersCards):
return checkSum(playersCard), checkSum(dealersCards, True), checkAce(playersCard)
#Will change Q after each completed game/episode
#This is where the "learning" is taking place
def setQ(Q, currentEpisode, gamma, alpha):
for t in range(len(currentEpisode)):
#episode[t+1:,2] gives all the rewards in the episode from t+1 onwords
rewards = currentEpisode[t:,2]
#Create a list with the gamma rate increasing
discountRate = [gamma**i for i in range(1,len(rewards)+1)]
#Discounting the rewards from t+1 onwards
updatedReward = rewards*discountRate
#Summing up the discounted rewards to equal the return at time step t
Gt = np.sum(updatedReward)
#Calculating the actual Q table value of the state, actionn pair.
Q[currentEpisode[t][0]][currentEpisode[t][1]] += alpha *(Gt - Q[currentEpisode[t][0]][currentEpisode[t][1]])
return Q
#The actual main method
def main():
pygame.display.set_caption("Blackjack AI")
running = True
clock = pygame.time.Clock()
#Will create 2 random cards for player and dealer.
#The dealer's second card is hidden
#Each card is 5 pixels away from each other
dealersCards = [card(20,20, False), card(155,20, False, False)]
playersCard = [card(850, 378, True)]
playersCard.append(hit(len(playersCard), True))
gameOver = False #Is the game over or not?
dealer=False #Is it the dealer's turn or not?
winner=False #If the player won.
delayBetweenGames=0 #This is how I track how long the delay is
currentEpisode = [] #The list containing the moves of the game
Q = defaultdict(lambda: np.zeros(2)) #Initialling an empty dictionary for Q
gamesWon = gamesLost = gamesPlayed = 0 #Initially the games won and lost are 0
GAME_FONT = pygame.freetype.Font("Roboto-Light.ttf", 24)
while running:
clock.tick(FPS)
screen.fill(pygame.Color(33,42,49))
global volume
#Draw dealer's cards
for i in dealersCards:
i.draw()
for i in playersCard:
i.draw()
#Will display all the text
if AI:
name = "AI"
else:
name = "Player"
text_surface, rect = GAME_FONT.render((name + "'s Current Sum: " + str(checkSum(playersCard))), pygame.Color('white'))
if not AI:
screen.blit(text_surface, (722, 350))
else:
screen.blit(text_surface, (760, 350))
text_surface, rect = GAME_FONT.render(("Dealer's Current Sum: " + str(checkSum(dealersCards, True))), pygame.Color('white'))
screen.blit(text_surface, (23, 230))
text_surface, rect = GAME_FONT.render(("Games Won by " + name + ": " + str(gamesWon)), pygame.Color('white'))
screen.blit(text_surface, (23, 470))
text_surface, rect = GAME_FONT.render(("Games Lost: " + str(gamesLost)), pygame.Color('white'))
screen.blit(text_surface, (23, 510))
text_surface, rect = GAME_FONT.render(("Number of Rounds: " + str(gamesPlayed)), (232, 75, 61))
screen.blit(text_surface, (23, 550))
if volume:
screen.blit(pygame.image.load('Cards/audio.png'), (width-50-10,210))
else:
screen.blit(pygame.image.load('Cards/mute.png'), (width-50-10,210))
#Draws the Hit and Stick buttons, if AI mode is off
if not AI:
screen.blit(pygame.image.load('Cards/HIT.png'), (width-200,20))
screen.blit(pygame.image.load('Cards/STICK.png'), (width-200,110))
gameOver, winner = whoWon(dealer,playersCard,dealersCards)
#End of game mechanics
if gameOver and delayBetweenGames<=breakTime*FPS:
if delayBetweenGames==0:
gamesPlayed+=1
if None:
gamesWon = gamesWon
elif winner:
#print("You Won")
gamesWon+=1
#print(gamesWon, gamesLost)
else:
gamesLost+=1
#print(gamesWon, gamesLost)
'''
if gamesPlayed in [100000, 150000, 200000, 300000] :
from plot_utils import plot_blackjack_values
plot_blackjack_values(dict((k,np.max(v)) for k, v in Q.items()), gamesPlayed, gamesWon, gamesLost )
'''
delayBetweenGames+=1
if gameOver and delayBetweenGames>=breakTime*FPS:
dealersCards = [card(20,20, False), card(155,20, False, False)] #5 space
playersCard = [card(850, 378, True)]
playersCard.append(hit(len(playersCard), True))
gameOver = dealer = winner = False
delayBetweenGames = 0
#AI aspect, stores each state/reward of an episode. After an episode it updates the Q values
if AI and not gameOver and not dealer:
#print("AI MODE")
currentState = createStateValues(playersCard, dealersCards)
action = genAction(currentState, e, Q)
if action==0:
dealer=True
playersCard, dealersCards = aiStep(action, playersCard, dealersCards)
gameOver, winner = whoWon(dealer,playersCard,dealersCards)
if gameOver and winner:
reward = 1
elif gameOver and not winner:
reward = -1
else:
reward = 0
currentEpisode.append((currentState, action, reward))
if gameOver:
currentEpisode = np.array(currentEpisode)
Q = setQ(Q, currentEpisode, gamma, alpha)
currentEpisode= []
for event in pygame.event.get():
#Key Presses
#Pressing r will reset the current game
#Pressing p will plot the current plot
#Pressing w will print games won/lost
#Pressing t will reset the win/lose numbers
#Pressing h will do a hit (when not in AI mode)
#Pressing s will do a stick (when not in AI mode)
#Pressing m will mute sounds
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
dealersCards = [card(20,20, False), card(155,20, False, False)] #5 space
playersCard = [card(850, 378, True)]
gameOver = dealer = winner = False
delayBetweenGames = 0
currentEpisode = 0
if event.key == pygame.K_p:
from plot_utils import plot_blackjack_values
plot_blackjack_values(dict((k,np.max(v)) for k, v in Q.items()), gamesPlayed, gamesWon, gamesLost )
if event.key==pygame.K_w:
print("Wins:",gamesWon,"Losses:",gamesLost, "Games Played:",gamesPlayed)
if event.key==pygame.K_t:
gamesWon=gamesLost=0
if event.key == pygame.K_h and not gameOver and not AI:
playersCard.append(hit(len(playersCard), True))
if event.key == pygame.K_s and not gameOver and not AI:
dealer=True
for i in dealersCards:
i.draw()
i.reveal()
while checkSum(dealersCards) <= dealerLimit:
dealersCards.append(hit(len(dealersCards), False))
if event.key == pygame.K_m:
volume = not volume
#Mouse Clicks
#Click on Hit button to Hit
#Click on Stick button to Stick
#Click on Volume button to mute/unmute
if event.type == pygame.MOUSEBUTTONDOWN:
mouseX, mouseY = pygame.mouse.get_pos()
print(mouseX, mouseY)
if mouseX>=940 and mouseX<=990 and mouseY>=210 and mouseY<=260:
volume = not volume
if mouseX>=800 and mouseX<=990 and mouseY>=30 and mouseY<=120 and not AI:
playersCard.append(hit(len(playersCard), True))
if mouseX>=800 and mouseX<=990 and mouseY>=120 and mouseY<=210 and not AI:
dealer=True
for i in dealersCards:
i.draw()
i.reveal()
while checkSum(dealersCards) <= dealerLimit:
dealersCards.append(hit(len(dealersCards), False))
pygame.display.update()
pygame.display.quit()
#Will call the main method
if __name__ == "__main__":
main()