-
Notifications
You must be signed in to change notification settings - Fork 0
/
game.py
369 lines (337 loc) · 18.1 KB
/
game.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
import random
import discord
from discord.ext import commands
from player import Player
from menus import ShootMenu, StealMenu
from asyncio import sleep
class Game:
"""Class that represents the game"""
def __init__(self, p1: Player, p2: Player):
"""Initialize the game"""
self.p1 = p1 # Player 1
self.p2 = p2 # Player 2
self.bullet_number = random.randint(2, 8) # Number of bullets
self.over = False # State of the game
self.item_info = {
"Expired Medicine": "Heal 1 - 2 LIFE or lose 1 LIFE but not more than your initial LIFE",
"Inverter": "Switch the color of all bullets",
"Cigarette": "Heal 1 LIFE but not more than your initial LIFE",
"Burner Phone": "Reveal the color of a random bullet",
"Adrenaline": "Steal an item from your opponent",
"Magnifying Glass": "Reveal the color of the current bullet",
"Beer": "Eject the current bullet",
"Hand Saw": "Double the damage of the next shot",
"Handcuffs": "Force your opponent to skip their next turn"
}
# Number of red bullets
if self.bullet_number < 4:
self.red_bullet = random.randint(1, 2)
else:
half = int(self.bullet_number / 2)
self.red_bullet = random.randint(half - 1, half + 1)
self.blue_bullet = self.bullet_number - self.red_bullet # Number of blue bullets
# Represents the gun with all the bullets loaded
self.gun = ['r' for _ in range(self.red_bullet)] + ['b' for _ in range(self.blue_bullet)]
self.bullet_index = 0
self.hand_saw = False
self.hand_cuffs = False
async def basic_game(self, interaction_ctx: discord.Interaction | commands.Context, advanced=False):
"""Simulate the basic game"""
self.first_mover() # Decide the first player to move
bullet_embed = self.bullet_display() # Representation of the bullets
# Display the bullets
if isinstance(interaction_ctx, discord.Interaction): # If it's interaction
display_msg = await interaction_ctx.followup.send(embed=bullet_embed)
else: # If it's context
display_msg = await interaction_ctx.channel.send(embed=bullet_embed)
await sleep(5)
random.shuffle(self.gun) # Shuffle all the bullets
if advanced: # Load items for players if the game mode is advanced
self.p1.reload_item()
self.p2.reload_item()
while not self.over: # If the game is not over
current_player = self.p1 if self.p1.turn else self.p2 # Player to move
opponent = self.p2 if current_player == self.p1 else self.p1 # Player not to move
# Reload the gun if it runs out of bullets and the game is not over
if self.bullet_number == 0 and not self.over:
self.reload()
await display_msg.edit(embed=self.bullet_display())
await sleep(5)
random.shuffle(self.gun)
self.bullet_index = 0
if advanced:
self.p1.reload_item()
self.p2.reload_item()
# Description of the embed
description = f"**{current_player.profile.display_name}**\n:heart: **LIFE**: {current_player.lives}"
if advanced:
description += "\n\n**ITEMS**:"
for item, amount in current_player.items.items():
if amount:
description += f"\n**{item}** x{amount}"
description += f"\n\n**{opponent.profile.display_name}**\n:heart: **LIFE**: {opponent.lives}"
if advanced:
description += "\n\n**ITEMS**:"
for item, amount in opponent.items.items():
if amount:
description += f"\n**{item}** x{amount}"
# Main representation of the game
embed = discord.Embed(
colour=discord.Colour.from_str("#ff2c55") if current_player == self.p1
else discord.Colour.from_str("#6ac5fe"),
description=description
)
embed.set_author(name=f"{current_player.profile.display_name}'s turn",
icon_url=current_player.profile.display_avatar.url)
view = ShootMenu(current_player.profile.id, self.item_info, current_player.items) # Shoot buttons
await display_msg.edit(embed=embed, view=view) # Edit the original embed
timed_out = await view.wait() # Check to see if the user reaches timeout
# Disable all buttons
view.disable_buttons()
await display_msg.edit(view=view)
await sleep(2)
if timed_out: # User ran out of time
if isinstance(interaction_ctx, discord.Interaction):
await interaction_ctx.followup.send(
f"**{current_player.profile.display_name}** lost due to inactivity!")
await interaction_ctx.followup.send(embed=self.winner_display(opponent))
else:
await interaction_ctx.channel.send(
f"**{current_player.profile.display_name}** lost due to inactivity!")
await interaction_ctx.channel.send(embed=self.winner_display(opponent))
self.over = True
else: # User chose a button
bullet = self.gun[self.bullet_index]
if view.selected is None: # Move to the next bullet
self.bullet_index += 1
self.bullet_number -= 1
# Result of using an item
item_description = await self.use_item(view.selected, current_player, opponent, interaction_ctx)\
if view.selected else ""
await display_msg.edit(
embed=self.round_display(bullet, view.shot_yourself, view.selected, item_description, self.hand_saw,
current_player, opponent),
view=None) # Display the result of the round
await sleep(5)
if view.selected: # User chose to use an item
continue # Skip all the actions below
if view.shot_yourself: # User chose to shoot himself
if bullet == 'r': # If the bullet is red
current_player.lives -= 1 # User lost 1 life
if self.hand_saw: # Double the damage
current_player.lives -= 1
if current_player.lives <= 0: # If user dies
if isinstance(interaction_ctx, discord.Interaction):
await interaction_ctx.followup.send(embed=self.winner_display(opponent))
else:
await interaction_ctx.channel.send(embed=self.winner_display(opponent))
self.over = True # Game over
if not self.hand_cuffs:
self.change_turn()
else: # User chose to shoot their opponent
if bullet == 'r':
opponent.lives -= 1
if self.hand_saw:
opponent.lives -= 1
if opponent.lives <= 0:
if isinstance(interaction_ctx, discord.Interaction):
await interaction_ctx.followup.send(embed=self.winner_display(current_player))
else:
await interaction_ctx.channel.send(embed=self.winner_display(current_player))
self.over = True
if not self.hand_cuffs:
self.change_turn()
# These 2 are available only for 1 shooting turn
self.hand_cuffs = False
self.hand_saw = False
def reload(self):
"""Reload the gun with a new set of bullets"""
self.bullet_number = random.randint(2, 8)
if self.bullet_number < 4:
self.red_bullet = random.randint(1, 2)
else:
half = int(self.bullet_number / 2)
self.red_bullet = random.randint(half - 1, half + 1)
self.blue_bullet = self.bullet_number - self.red_bullet
self.gun = ['r' for _ in range(self.red_bullet)] + ['b' for _ in range(self.blue_bullet)]
def first_mover(self):
"""Choose the first player to move randomly"""
if random.randint(0, 1):
self.p1.turn = True
else:
self.p2.turn = True
def change_turn(self):
"""Change turn between 2 players"""
self.p1.turn, self.p2.turn = self.p2.turn, self.p1.turn
def bullet_display(self):
"""Representation of bullets in the gun"""
blue = "is" if self.blue_bullet == 1 else "are"
red = "is" if self.red_bullet == 1 else "are"
bullet_str = f"There are **{self.bullet_number}** bullets in the gun.\n\n**{self.red_bullet}** of them {red}" \
f" **RED**.\n**{self.blue_bullet}** of them {blue} **BLUE**.\n\n"
for bullet in self.gun:
bullet_str += ":red_square: " if bullet == 'r' else ":blue_square: "
return discord.Embed(
colour=discord.Colour.gold(),
title="Bullets in the gun",
description=bullet_str
)
def round_display(self, bullet, shot_yourself, selected_item, item_result, hand_saw,
player: Player, opponent: Player):
"""Create an embed to display the result of the round"""
if selected_item: # If user chose to use an item
description = f"**{player.profile.display_name} used {selected_item}.**\n\n"
description += item_result
return discord.Embed(
colour=discord.Colour.gold(),
title=f"You used an item",
description=description
)
tobe = "is" if self.bullet_number == 1 else "are" # Grammar
if bullet == "r": # If the bullet is red
dmg = 2 if hand_saw else 1
if shot_yourself: # If the player chose to shoot himself
title = "You shot yourself with a :red_square: RED bullet"
description = f"**{player.profile.display_name} lost {dmg} :heart: LIFE.**\n\n"
else:
title = "You shot your opponent with a :red_square: RED bullet"
description = f"**{opponent.profile.display_name} lost {dmg} :heart: LIFE.**\n\n"
else:
if shot_yourself:
title = "You shot yourself with a :blue_square: BLUE bullet"
description = "**Next turn is still yours!**\n\n"
else:
title = "You shot your opponent with a :blue_square: BLUE bullet"
description = "**You missed!**\n\n"
return discord.Embed(
colour=discord.Colour.brand_red() if bullet == "r" else discord.Colour.blue(),
title=title,
description=description + f"There {tobe} **{self.bullet_number}** bullet(s) left.\n"
)
def winner_display(self, winner: Player):
"""Create an embed to display the winner"""
embed = discord.Embed(
colour=discord.Colour.from_str("#fff46b"),
description=f"\nChallenge is over!\n\nThe winner is **{winner.profile.display_name}**"
)
embed.set_thumbnail(url=winner.profile.display_avatar.url)
return embed
def challenge_display(self):
"""Create an embed to display the challenge message"""
embed = discord.Embed(
colour=discord.Colour.purple(),
title="Duckshot Challenge",
description=f"**{self.p1.profile.display_name}** challenged **{self.p2.profile.display_name}** "
f"in a gun fight!\n\nDo you accept this challenge?")
embed.set_author(name=self.p1.profile.display_name, icon_url=self.p1.profile.display_avatar.url)
return embed
async def use_item(self, item, player: Player, opponent: Player,
interaction_ctx: discord.Interaction | commands.Context):
"""Process the item selected by the player"""
player.items[item] -= 1 # Remove that item after used
if item == "Expired Medicine":
if random.randint(0, 1): # Heal hp
if random.randint(0, 1): # Heal 2 hp
# Already at max hp
result = f"**{player.profile.display_name}** is already at max :heart: **LIFE**."
if player.lives < player.max_lives: # But not more than the initial hp
if player.lives == player.max_lives - 1:
player.lives += 1
result = f"**{player.profile.display_name}** has gained 1 :heart: **LIFE**."
else:
player.lives += 2
result = f"**{player.profile.display_name}** has gained 2 :heart: **LIFE**."
else: # Heal 1 hp
result = f"**{player.profile.display_name}** is already at max :heart: **LIFE**"
if player.lives < player.max_lives:
player.lives += 1
result = f"**{player.profile.display_name}** has gained 1 :heart: **LIFE**"
else: # Lose hp
player.lives -= 1
result = f"**{player.profile.display_name}** has lost 1 :heart: **LIFE**"
if player.lives <= 0: # If user dies
if isinstance(interaction_ctx, discord.Interaction):
await interaction_ctx.followup.send(embed=self.winner_display(opponent))
else:
await interaction_ctx.channel.send(embed=self.winner_display(opponent))
self.over = True
elif item == "Inverter":
for i in range(len(self.gun)):
if self.gun[i] == "r":
self.gun[i] = "b"
else:
self.gun[i] = "r"
result = "All bullets' colors are inverted."
elif item == "Cigarette":
result = f"**{player.profile.display_name}** is already at max :heart: **LIFE**"
if player.lives < player.max_lives:
player.lives += 1
result = f"**{player.profile.display_name}** has gained 1 :heart: **LIFE**"
elif item == "Burner Phone":
index = random.randint(self.bullet_index, len(self.gun) - 1)
color = ":red_square: **RED**" if self.gun[index] == "r" else ":blue_square: **BLUE**"
if index + 1 == 1:
ordinal = f"{index + 1}st"
elif index + 1 == 2:
ordinal = f"{index + 1}nd"
elif index + 1 == 3:
ordinal = f"{index + 1}rd"
else:
ordinal = f"{index + 1}th"
await player.profile.send(f"The {ordinal} bullet is {color}")
result = f"**{player.profile.display_name}** knows the color of a random bullet."
elif item == "Adrenaline":
view = StealMenu(player.profile.id, self.item_info, opponent.items)
embed = discord.Embed(
colour=discord.Colour.from_str("#ff2c55") if player == self.p1
else discord.Colour.from_str("#6ac5fe"),
description=f"**{player.profile.display_name}** is choosing an item to steal."
)
# Display the message for user to choose an item to steal
if isinstance(interaction_ctx, discord.Interaction):
steal_msg = await interaction_ctx.followup.send(embed=embed, view=view)
else:
steal_msg = await interaction_ctx.channel.send(embed=embed, view=view)
timed_out = await view.wait()
if timed_out: # User ran out of time
await steal_msg.delete()
if isinstance(interaction_ctx, discord.Interaction):
await interaction_ctx.followup.send(
f"**{player.profile.display_name}** lost due to inactivity!")
await interaction_ctx.followup.send(embed=self.winner_display(opponent))
else:
await interaction_ctx.channel.send(
f"**{player.profile.display_name}** lost due to inactivity!")
await interaction_ctx.channel.send(embed=self.winner_display(opponent))
self.over = True
result = f"**{player.profile.display_name}** ran out of time to choose."
else: # User chose an item to steal
chosen_item = view.selected
await steal_msg.delete()
player.items[chosen_item] += 1
opponent.items[chosen_item] -= 1
result = f"**{player.profile.display_name}** chose to steal a(n) **{chosen_item}**."
elif item == "Magnifying Glass":
color = ":red_square: **RED**" if self.gun[self.bullet_index] == "r" else ":blue_square: **BLUE**"
result = f"The current bullet's color is {color}."
elif item == "Beer":
color = ":red_square: **RED**" if self.gun[self.bullet_index] == "r" else ":blue_square: **BLUE**"
self.bullet_index += 1
self.bullet_number -= 1
tobe = "is" if self.bullet_number == 1 else "are"
result = f"A {color} bullet has been ejected.\n There {tobe} **{self.bullet_number}** bullet(s) left."
elif item == "Hand Saw":
if self.hand_saw:
player.items[item] += 1
result = f"You already used **{item}**."
else:
self.hand_saw = True
result = "The current bullet's damage is doubled."
else:
if self.hand_cuffs:
player.items[item] += 1
result = f"You already used **{item}**."
else:
self.hand_cuffs = True
result = f"Next turn is still **{player.profile.display_name}**'s turn."
return result