Various fixes

This commit is contained in:
Mattia Giambirtone 2022-06-15 23:08:49 +02:00
parent 80c6220f77
commit e94259e614
1 changed files with 106 additions and 36 deletions

View File

@ -16,6 +16,7 @@ import logging
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum, auto from enum import Enum, auto
from typing import Optional from typing import Optional
from matplotlib.colors import same_color
from pyrogram.types import Message from pyrogram.types import Message
from pyrogram.errors import RPCError from pyrogram.errors import RPCError
from pyrogram.enums import ChatType from pyrogram.enums import ChatType
@ -71,6 +72,7 @@ class UNOCard:
placed_by: "UNOPlayer" = None placed_by: "UNOPlayer" = None
# Extra metadata # Extra metadata
metadata: Optional[dict[str, int | str | bool]] = None metadata: Optional[dict[str, int | str | bool]] = None
placed_when: int = -1
def get_default_deck(n_decks: int = 1) -> deque[UNOCard]: def get_default_deck(n_decks: int = 1) -> deque[UNOCard]:
@ -80,7 +82,7 @@ def get_default_deck(n_decks: int = 1) -> deque[UNOCard]:
""" """
result = [] result = []
for __ in range(n_decks): for __ in range(0, n_decks, 1):
# Numbers: 1 to 9 for each color (twice for each color) # Numbers: 1 to 9 for each color (twice for each color)
for color in [UNOCardColor.GREEN, UNOCardColor.RED, for color in [UNOCardColor.GREEN, UNOCardColor.RED,
UNOCardColor.YELLOW, UNOCardColor.BLUE]: UNOCardColor.YELLOW, UNOCardColor.BLUE]:
@ -104,9 +106,9 @@ def get_default_deck(n_decks: int = 1) -> deque[UNOCard]:
result.append(UNOCard(UNOCardType.DRAW, color, metadata={"exhausted": False})) result.append(UNOCard(UNOCardType.DRAW, color, metadata={"exhausted": False}))
# Wild and wild draw cards # Wild and wild draw cards
for _ in range(4): for _ in range(4):
result.append(UNOCard(UNOCardType.WILD, UNOCardColor.BLACK, stackable=False, metadata={"exhausted": False})) result.append(UNOCard(UNOCardType.WILD, UNOCardColor.BLACK, stackable=False, metadata={"exhausted": False, "color": None}))
for _ in range(4): for _ in range(4):
result.append(UNOCard(UNOCardType.WILD_DRAW, UNOCardColor.BLACK, stackable=False, metadata={"exhausted": False})) result.append(UNOCard(UNOCardType.WILD_DRAW, UNOCardColor.BLACK, stackable=False, metadata={"exhausted": False, "color": None}))
random.shuffle(result) random.shuffle(result)
return deque(result) return deque(result)
@ -139,6 +141,7 @@ class UNOGame:
current_pos: int = 0 current_pos: int = 0
current_player: Optional[UNOPlayer] = None current_player: Optional[UNOPlayer] = None
winners: list[UNOPlayer] = field(default_factory=list) winners: list[UNOPlayer] = field(default_factory=list)
round: int = 0
@dataclass @dataclass
@ -517,7 +520,7 @@ def pick_first_card(game: UNOGame):
break break
def distribute_cards(game: UNOGame, n: int = 1): def distribute_cards(game: UNOGame, n: int = 7):
""" """
Distributes n cards to all players evenly Distributes n cards to all players evenly
as if they were being given in real life as if they were being given in real life
@ -528,9 +531,6 @@ def distribute_cards(game: UNOGame, n: int = 1):
while not all(len(player.cards) == n for player in game.players): while not all(len(player.cards) == n for player in game.players):
for player in game.players: for player in game.players:
player.cards.append(game.deck.popleft()) player.cards.append(game.deck.popleft())
for player in game.players:
for card in player.cards:
card.placed_by = player
@Client.on_message(filters.command("begin")) @Client.on_message(filters.command("begin"))
@ -604,6 +604,7 @@ def card_to_string(card: UNOCard) -> str:
UNOCardColor.GREEN: "🟢", UNOCardColor.GREEN: "🟢",
UNOCardColor.YELLOW: "🟡", UNOCardColor.YELLOW: "🟡",
} }
print(maps)
result = "" result = ""
match card.kind: match card.kind:
case UNOCardType.NUMBER: case UNOCardType.NUMBER:
@ -646,13 +647,80 @@ async def info(_: Client, message: Message):
logging.error(f"An unexpected RPC error occurred: {type(rpc_error).__name__} -> {rpc_error}") logging.error(f"An unexpected RPC error occurred: {type(rpc_error).__name__} -> {rpc_error}")
async def check_special_cards(client: Client, game: UNOGame): def pick_next_player(game: UNOGame):
"""
Picks the next player in the queue and schedules
their turn
"""
while True:
game.current_pos += 1
game.current_player = game.players[game.current_pos % len(game.players)]
if game.current_player.won:
# Skip players who won already
continue
else:
break
async def check_special_cards(client: Client, game: UNOGame):
""" """
Performs some checks on the effects of special cards and Performs some checks on the effects of special cards and
sends some messages to users to make them aware of any sends some messages to users to make them aware of any
changes that occur changes that occur
""" """
try:
draw = 0
cards: list[UNOCard] = []
i = 0
while game.table[i].kind in {UNOCardType.WILD, UNOCardType.WILD_DRAW,
UNOCardType.REVERSE, UNOCardType.SKIP,
UNOCardType.DRAW} and not game.table[i].metadata["exhausted"]:
cards.append(game.table[i])
i += 1
for card in cards:
card.metadata["exhausted"] = True
match card.kind:
case UNOCardType.WILD | UNOCardType.WILD_DRAW as k:
await client.send_message(game.current_player.id, "Choose the color for the new top card between red, green and blue")
USERS[game.table[0].placed_by][0] = PlayerAction.CHOOSING_COLOR
found = False
if k == UNOCardType.WILD_DRAW:
for next_card in game.current_player.cards:
if next_card.kind == UNOCardType.WILD_DRAW:
found = True
break
if not found:
draw += 4
case UNOCardType.REVERSE:
for user in game.players:
if user != card.placed_by:
await client.send_message(user.id, "The direction of play reverses!")
game.players = list(reversed(game.players))
case UNOCardType.DRAW:
found = False
for next_card in game.current_player.cards:
if next_card.kind == UNOCardType.DRAW:
found = True
break
if not found:
draw += 2
case UNOCardType.SKIP:
for user in game.players:
if user != card.placed_by:
await client.send_message(user.id, "A turn has been skipped!")
pick_next_player(game)
if draw:
await client.send_message(game.current_player.id, f"You are foced to draw {draw} cards!")
for _ in range(draw):
if not game.deck:
reshuffle_table_in_deck(game)
game.current_player.cards.append(game.deck.popleft())
await client.send_message(game.current_player.id, f"You draw a card. It's {card_to_string(game.current_player.cards[-1])}")
except RPCError as rpc_error:
logging.error(f"An unexpected RPC error occurred: {type(rpc_error).__name__} -> {rpc_error}")
@Client.on_message(filters.command("pass")) @Client.on_message(filters.command("pass"))
async def pass_turn(client: Client, message: Message): async def pass_turn(client: Client, message: Message):
@ -663,6 +731,8 @@ async def pass_turn(client: Client, message: Message):
try: try:
if (data := USERS[message.from_user.id])[0] == PlayerAction.NONE: if (data := USERS[message.from_user.id])[0] == PlayerAction.NONE:
await message.reply_text("You haven't created or joined a game lobby yet. Send /play to create it or /join <id> to join an existing one", parse_mode=ParseMode.MARKDOWN) await message.reply_text("You haven't created or joined a game lobby yet. Send /play to create it or /join <id> to join an existing one", parse_mode=ParseMode.MARKDOWN)
elif data[0] == PlayerAction.CHOOSING_COLOR:
await message.reply_text("You need to choose a color between red, green, blue and yellow before passing your turn")
else: else:
lobby = data[1] lobby = data[1]
for player in lobby.game.players: for player in lobby.game.players:
@ -678,17 +748,8 @@ async def pass_turn(client: Client, message: Message):
await message.reply_text("You have to draw or throw a card before passing your turn") await message.reply_text("You have to draw or throw a card before passing your turn")
return return
else: else:
while True: pick_next_player(lobby.game)
if lobby.game.direction == UNODirection.CLOCKWISE: lobby.game.round += 1
lobby.game.current_pos += 1
else:
lobby.game.current_pos -= 1
lobby.game.current_player = lobby.game.players[lobby.game.current_pos % len(lobby.game.players)]
if lobby.game.current_player.won:
# Skip players who won already
continue
else:
break
USERS[message.from_user.id] = (PlayerAction.END_TURN, USERS[message.from_user.id][1]) USERS[message.from_user.id] = (PlayerAction.END_TURN, USERS[message.from_user.id][1])
name = f"{message.from_user.first_name or ''}{message.from_user.last_name or ''}" name = f"{message.from_user.first_name or ''}{message.from_user.last_name or ''}"
next_name = lobby.game.current_player.name next_name = lobby.game.current_player.name
@ -761,24 +822,31 @@ def check_next_throw(game: UNOGame, card: UNOCard) -> bool:
""" """
top = game.table[0] top = game.table[0]
result = False top_color = top.color
match card.kind: card_color = card.color
case UNOCardType.NUMBER as k if top.kind == k: if top.kind in {UNOCardType.WILD, UNOCardType.WILD_DRAW}:
if card.metadata["value"] == top.metadata["value"]: top_color = top.metadata["color"]
result = True if card.kind in {UNOCardType.WILD, UNOCardType.WILD_DRAW}:
elif card.color == top.color: card_color = card.metadata["color"]
result = True same_color = card_color == top_color
# Can't stack colors! same_turn = top.placed_when == game.round
if top.placed_by and card.color == top.color and card.placed_by is top.placed_by: match top.kind:
result = False case UNOCardType.NUMBER as k if card.kind == k:
case UNOCardType.DRAW | UNOCardType.REVERSE | UNOCardType.SKIP as k: # Same number or same color in this turn
result = top.kind == k and top.color == card.color return top.metadata["value"] == card.metadata["value"] or (same_color and not same_turn)
case UNOCardType.NUMBER as k if card.kind != k:
# Same as above, without the number check
return same_color and not same_turn
case UNOCardType.SKIP | UNOCardType.REVERSE | UNOCardType.DRAW:
result = same_color and not same_turn
if top.kind == card.kind:
# Can you stack this special card in a turn?
result = result and top.stackable
return result
case UNOCardType.WILD | UNOCardType.WILD_DRAW: case UNOCardType.WILD | UNOCardType.WILD_DRAW:
result = True return False # TODO
case _: case _:
result = False return False
result = result and top.stackable
return result
@Client.on_message(filters.command("throw")) @Client.on_message(filters.command("throw"))
@ -815,6 +883,8 @@ async def throw(client: Client, message: Message):
if card_no > len(player.cards) or card_no == 0: if card_no > len(player.cards) or card_no == 0:
await message.reply_text(f"Invalid card: you can choose between 1 and {len(player.cards)}") await message.reply_text(f"Invalid card: you can choose between 1 and {len(player.cards)}")
return return
player.cards[card_no - 1].placed_when = lobby.game.round
player.cards[card_no - 1].placed_by = player
if not check_next_throw(lobby.game, player.cards[card_no - 1]): if not check_next_throw(lobby.game, player.cards[card_no - 1]):
await message.reply_text("You can't throw that card") await message.reply_text("You can't throw that card")
return return
@ -864,6 +934,6 @@ async def throw(client: Client, message: Message):
msg = f"Game status: \n- Card on the table: {card_to_string(lobby.game.table[0])}\n\n- Current Player: {lobby.game.current_player.name}\n\n- Your cards:\n" msg = f"Game status: \n- Card on the table: {card_to_string(lobby.game.table[0])}\n\n- Current Player: {lobby.game.current_player.name}\n\n- Your cards:\n"
for i, card in enumerate(player.cards): for i, card in enumerate(player.cards):
msg += f"{i + 1}. {card_to_string(card)}\n" msg += f"{i + 1}. {card_to_string(card)}\n"
await client.send_message(lobby.game.current_player.id, msg) await client.send_message(lobby.game.current_player.id, msg)
except RPCError as rpc_error: except RPCError as rpc_error:
logging.error(f"An unexpected RPC error occurred: {type(rpc_error).__name__} -> {rpc_error}") logging.error(f"An unexpected RPC error occurred: {type(rpc_error).__name__} -> {rpc_error}")