Various fixes
This commit is contained in:
parent
80c6220f77
commit
e94259e614
|
@ -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,6 +647,22 @@ 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}")
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
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
|
||||||
|
@ -653,6 +670,57 @@ async def check_special_cards(client: Client, game: UNOGame):
|
||||||
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.WILD | UNOCardType.WILD_DRAW:
|
case UNOCardType.NUMBER as k if card.kind != k:
|
||||||
result = True
|
# Same as above, without the number check
|
||||||
case _:
|
return same_color and not same_turn
|
||||||
result = False
|
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
|
result = result and top.stackable
|
||||||
return result
|
return result
|
||||||
|
case UNOCardType.WILD | UNOCardType.WILD_DRAW:
|
||||||
|
return False # TODO
|
||||||
|
case _:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@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
|
||||||
|
|
Loading…
Reference in New Issue