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 enum import Enum, auto
from typing import Optional
from matplotlib.colors import same_color
from pyrogram.types import Message
from pyrogram.errors import RPCError
from pyrogram.enums import ChatType
@ -71,6 +72,7 @@ class UNOCard:
placed_by: "UNOPlayer" = None
# Extra metadata
metadata: Optional[dict[str, int | str | bool]] = None
placed_when: int = -1
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 = []
for __ in range(n_decks):
for __ in range(0, n_decks, 1):
# Numbers: 1 to 9 for each color (twice for each color)
for color in [UNOCardColor.GREEN, UNOCardColor.RED,
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}))
# Wild and wild draw cards
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):
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)
return deque(result)
@ -139,6 +141,7 @@ class UNOGame:
current_pos: int = 0
current_player: Optional[UNOPlayer] = None
winners: list[UNOPlayer] = field(default_factory=list)
round: int = 0
@dataclass
@ -517,7 +520,7 @@ def pick_first_card(game: UNOGame):
break
def distribute_cards(game: UNOGame, n: int = 1):
def distribute_cards(game: UNOGame, n: int = 7):
"""
Distributes n cards to all players evenly
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):
for player in game.players:
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"))
@ -604,6 +604,7 @@ def card_to_string(card: UNOCard) -> str:
UNOCardColor.GREEN: "🟢",
UNOCardColor.YELLOW: "🟡",
}
print(maps)
result = ""
match card.kind:
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}")
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
sends some messages to users to make them aware of any
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"))
async def pass_turn(client: Client, message: Message):
@ -663,6 +731,8 @@ async def pass_turn(client: Client, message: Message):
try:
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)
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:
lobby = data[1]
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")
return
else:
while True:
if lobby.game.direction == UNODirection.CLOCKWISE:
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
pick_next_player(lobby.game)
lobby.game.round += 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 ''}"
next_name = lobby.game.current_player.name
@ -761,24 +822,31 @@ def check_next_throw(game: UNOGame, card: UNOCard) -> bool:
"""
top = game.table[0]
result = False
match card.kind:
case UNOCardType.NUMBER as k if top.kind == k:
if card.metadata["value"] == top.metadata["value"]:
result = True
elif card.color == top.color:
result = True
# Can't stack colors!
if top.placed_by and card.color == top.color and card.placed_by is top.placed_by:
result = False
case UNOCardType.DRAW | UNOCardType.REVERSE | UNOCardType.SKIP as k:
result = top.kind == k and top.color == card.color
top_color = top.color
card_color = card.color
if top.kind in {UNOCardType.WILD, UNOCardType.WILD_DRAW}:
top_color = top.metadata["color"]
if card.kind in {UNOCardType.WILD, UNOCardType.WILD_DRAW}:
card_color = card.metadata["color"]
same_color = card_color == top_color
same_turn = top.placed_when == game.round
match top.kind:
case UNOCardType.NUMBER as k if card.kind == k:
# Same number or same color in this turn
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:
result = True
return False # TODO
case _:
result = False
result = result and top.stackable
return result
return False
@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:
await message.reply_text(f"Invalid card: you can choose between 1 and {len(player.cards)}")
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]):
await message.reply_text("You can't throw that card")
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"
for i, card in enumerate(player.cards):
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:
logging.error(f"An unexpected RPC error occurred: {type(rpc_error).__name__} -> {rpc_error}")