diff --git a/README.md b/README.md index daf3893..5a2cace 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ imgwelcome - The repo can be found at: https://github.com/aikaterna/imgwelcome inspirobot - Fetch "inspirational" messages from inspirobot.me with [p]inspireme. +luigipoker - Play the Luigi Poker minigame from New Super Mario Brothers. Ported from the v2 version written by themario30. + noflippedtables - A v3 port of irdumb's v2 cog with a little extra surprise included. Unflip all the tables. nolinks - A very blunt hammer to remove anything that looks like a link. Roles can be whitelisted and it can watch multiple channels. diff --git a/luigipoker/__init__.py b/luigipoker/__init__.py new file mode 100644 index 0000000..d38bf2a --- /dev/null +++ b/luigipoker/__init__.py @@ -0,0 +1,5 @@ +from .luigipoker import LuigiPoker + + +def setup(bot): + bot.add_cog(LuigiPoker(bot)) diff --git a/luigipoker/info.json b/luigipoker/info.json new file mode 100644 index 0000000..4d172a7 --- /dev/null +++ b/luigipoker/info.json @@ -0,0 +1,8 @@ +{ + "author": ["aikaterna", "themario30"], + "description": "Play the Luigi Poker minigame from New Super Mario Brothers.", + "install_msg": "Thanks for installing.", + "short": "A Luigi poker minigame.", + "tags": ["poker", "game"] + "type": "COG" +} \ No newline at end of file diff --git a/luigipoker/luigipoker.py b/luigipoker/luigipoker.py new file mode 100644 index 0000000..419459b --- /dev/null +++ b/luigipoker/luigipoker.py @@ -0,0 +1,426 @@ +import asyncio +import logging +from random import randint +from redbot.core import commands, checks +from redbot.core.utils.chat_formatting import box +from redbot.core.utils.predicates import MessagePredicate + + +log = logging.getLogger("red.aikaterna.luigipoker") + + +class Card: + def __init__(self, card_number=None): + self._number = card_number if card_number else randint(1, 6) + self._suit = self._suit() + + def _suit(self): + if self._number == 1: + suit = "\N{CLOUD}\N{VARIATION SELECTOR-16}" + elif self._number == 2: + suit = "\N{MUSHROOM}" + elif self._number == 3: + suit = "\N{SUNFLOWER}" + elif self._number == 4: + suit = "\N{LARGE GREEN SQUARE}" + elif self._number == 5: + suit = "\N{LARGE RED SQUARE}" + elif self._number == 6: + suit = "\N{WHITE MEDIUM STAR}" + else: + suit = "Error!" + + return suit + + def __repr__(self): + return f"{self._suit}" + + def num(self): + return self._number + + def suit(self): + return self._suit + + +class Deck: + def __init__(self): + self._length = 5 + self._deck = self._create_deck() + self.first_pair = 0 + self.second_pair = 0 + self.new_deck() + + def _create_deck(self): + temp = [Card() for x in range(0, self._length)] + return temp + + def _new_card(self, i): + self._deck[i] = Card() + + def _sort_deck(self): + self._deck.sort(key=lambda x: x.num(), reverse=True) + + def new_deck(self): + self._deck = self._create_deck() + self._sort_deck() + + def deck(self): + return self._deck + + def num(self, i): + return self._deck[i].num() + + def swap(self, i): + for x in i: + self._new_card(int(x) - 1) + self._sort_deck() + + def suit(self, i): + return self._deck[i].suit() + + def len(self): + return self._length + + +class LuigiPoker(commands.Cog): + + __version__ = "0.1.0" + + def __init__(self, bot): + self.bot = bot + self._in_game = {} + self._hit = {} + self.player_deck = Deck() + self.dealer_deck = Deck() + + @commands.group() + async def poker(self, ctx): + """The Luigi Poker minigame from New Super Mario Brothers.""" + if ctx.invoked_subcommand is None: + space = "\N{EN SPACE}" + msg = ( + f"I'm Luigi, Number 1!\n" + f"This game plays the same as Luigi's " + f"Poker in Super Mario 64 DS Minigames.\n" + f"The card's worth is based on the suit.\n" + f"Starman > Mario > Luigi > Fire Flower > Mushroom > Cloud.\n" + f"{space*3}{Card(6)}{space*4}>{space*3}{Card(5)}{space*3}>{space*3}{Card(4)}{space*3}" + f">{space*6}{Card(3)}{space*6}>{space*4}{Card(2)}{space*5}>{space*4}{Card(1)}\n" + f"---------------------------------------------------------\n" + f"The following table represents the winning matches.\n" + f"For example, a Full House is greater than Three of a Kind, but " + f"less than a Four of a Kind.\n" + f"---------------------------------------------------------\n" + f"Flush: {Card(6)}{Card(6)}{Card(6)}{Card(6)}{Card(6)}\n" + f"Four of a Kind: {Card(6)}{Card(6)}{Card(6)}{Card(6)}\n" + f"Full House: {Card(6)}{Card(6)}{Card(6)}{Card(3)}{Card(3)}\n" + f"Three of a Kind: {Card(6)}{Card(6)}{Card(6)}\n" + f"Two Pairs: {Card(6)}{Card(6)}{Card(2)}{Card(2)}\n" + f"Pair: {Card(6)}{Card(6)}\n" + ) + await ctx.send(box(msg)) + return await ctx.send( + f"Are you ready to play my game?! What are you waiting for? Start the game using `{ctx.prefix}poker play`!" + ) + + @poker.command() + async def play(self, ctx): + """Starts the Game!""" + if not self._in_game.get(ctx.guild.id, False): + self._in_game[ctx.guild.id] = True + self.player_deck.new_deck() + self.dealer_deck.new_deck() + else: + return await ctx.send("You're already in a game...") + + square = "\N{WHITE MEDIUM SMALL SQUARE}" + msg = ( + f"Dealer's Deck: {square*5}\n" + f"Your Deck: {self.player_deck.suit(0)}{self.player_deck.suit(1)}" + f"{self.player_deck.suit(2)}{self.player_deck.suit(3)}{self.player_deck.suit(4)}" + ) + + await ctx.send(box(msg)) + + if self._hit.get(ctx.guild.id, False): + await ctx.send("`Stay` or `fold`?") + answers = ["stay", "fold"] + else: + await ctx.send("`Stay`, `hit`, or `fold`?") + answers = ["stay", "hit", "fold"] + await self._play_response(ctx, answers) + + async def _play_response(self, ctx, answers): + pred = MessagePredicate.lower_contained_in(answers) + try: + user_resp = await ctx.bot.wait_for("message", timeout=120, check=pred) + except asyncio.TimeoutError: + await ctx.send("No response.") + return await self.fold(ctx) + if "stay" in user_resp.content: + return await self.stay(ctx) + elif "hit" in user_resp.content: + return await self.hit(ctx) + elif "fold" in user_resp.content: + return await self.fold(ctx) + else: + log.error( + "LuigiPoker: Something broke unexpectedly in _play_response. Please report it.", + exc_info=True, + ) + + async def hit(self, ctx): + card_question = await ctx.send( + "What cards do you want to swap out?\n" + "Use numbers 1 through 5 to specify, with commas in between.\n" + "Examples: `1,3,5` or `4, 5`" + ) + try: + user_resp = await ctx.bot.wait_for( + "message", timeout=60, check=MessagePredicate.same_context(ctx) + ) + except asyncio.TimeoutError: + await ctx.send("No response.") + return await self.fold(ctx) + + user_answers = user_resp.content.strip().split(",") + user_answers_valid = list(set(user_answers) & set(["1", "2", "3", "4", "5"])) + if len(user_answers_valid) == 0: + return await self.hit(ctx) + + await ctx.send("Swapping Cards...") + self.player_deck.swap(user_answers_valid) + square = "\N{WHITE MEDIUM SMALL SQUARE}" + msg = ( + f"Dealer's Deck: {square*5}\n" + f"Your Deck: {self.player_deck.suit(0)}{self.player_deck.suit(1)}" + f"{self.player_deck.suit(2)}{self.player_deck.suit(3)}{self.player_deck.suit(4)}" + ) + await ctx.send(box(msg)) + await ctx.send("`Stay` or `fold`?") + self._hit[ctx.guild.id] = True + answers = ["stay", "fold"] + await self._play_response(ctx, answers) + + async def fold(self, ctx): + msg = "You have folded.\n" + msg += box( + f"Dealer's Deck: {self.dealer_deck.suit(0)}{self.dealer_deck.suit(1)}" + f"{self.dealer_deck.suit(2)}{self.dealer_deck.suit(3)}{self.dealer_deck.suit(4)}\n" + f"Your Deck: {self.player_deck.suit(0)}{self.player_deck.suit(1)}" + f"{self.player_deck.suit(2)}{self.player_deck.suit(3)}{self.player_deck.suit(4)}" + ) + + self._in_game[ctx.guild.id] = False + self._hit[ctx.guild.id] = False + await ctx.send(msg) + + async def stay(self, ctx): + say = "" + win = False + same_move = False + tied = False + + # Flush + if self.flush(self.player_deck) != self.flush(self.dealer_deck): + say = "a Flush" + if self.flush(self.player_deck): + win = True + elif self.flush(self.player_deck) and self.flush(self.dealer_deck): + say = "Flush" + same_move = True + if self.player_deck.first_pair > self.dealer_deck.first_pair: + win = True + elif self.player_deck.first_pair == self.dealer_deck.first_pair: + tied = True + + # Four of a Kind + elif self.four_of_a_kind(self.player_deck) != self.four_of_a_kind(self.dealer_deck): + say = "a Four of a Kind" + if self.four_of_a_kind(self.player_deck): + win = True + elif self.four_of_a_kind(self.player_deck) and self.four_of_a_kind(self.dealer_deck): + say = "Four of a Kind" + same_move = True + if self.player_deck.first_pair > self.dealer_deck.first_pair: + win = True + elif self.player_deck.first_pair == self.dealer_deck.first_pair: + tied = True + + # Full House + elif self.full_house(self.player_deck) != self.full_house(self.dealer_deck): + say = "a Full House" + if self.full_house(self.player_deck): + win = True + elif self.full_house(self.player_deck) and self.full_house(self.dealer_deck): + say = "Full House" + same_move = True + if self.player_deck.first_pair > self.dealer_deck.first_pair: + win = True + elif self.player_deck.second_pair > self.dealer_deck.second_pair: + win = True + elif ( + self.player_deck.first_pair == self.dealer_deck.first_pair + and self.player_deck.second_pair == self.dealer_deck.second_pair + ): + tied = True + + # Full House + elif self.three_of_a_kind(self.player_deck) != self.three_of_a_kind(self.dealer_deck): + say = "a Three of a Kind" + if self.three_of_a_kind(self.player_deck): + win = True + elif self.three_of_a_kind(self.player_deck) and self.three_of_a_kind(self.dealer_deck): + say = "Three of a Kind" + same_move = True + if self.player_deck.first_pair > self.dealer_deck.first_pair: + win = True + elif self.player_deck.first_pair == self.dealer_deck.first_pair: + tied = True + + # Two Pairs + elif self.two_pair(self.player_deck) != self.two_pair(self.dealer_deck): + say = "Two Pairs" + if self.two_pair(self.player_deck): + win = True + elif self.two_pair(self.player_deck) and self.two_pair(self.dealer_deck): + say = "Two Pairs" + same_move = True + if self.player_deck.first_pair > self.dealer_deck.first_pair: + win = True + elif self.player_deck.second_pair > self.dealer_deck.second_pair: + win = True + elif ( + self.player_deck.first_pair == self.dealer_deck.first_pair + and self.player_deck.second_pair == self.dealer_deck.second_pair + ): + tied = True + + # One Pair + elif self.one_pair(self.player_deck) != self.one_pair(self.dealer_deck): + say = "a Pair" + if self.one_pair(self.player_deck): + win = True + elif self.one_pair(self.player_deck) and self.one_pair(self.dealer_deck): + say = "Pair" + same_move = True + if self.player_deck.first_pair > self.dealer_deck.first_pair: + win = True + elif self.player_deck.first_pair == self.dealer_deck.first_pair: + tied = True + else: + tied = True + + msg = "You've stayed.\n" + + if same_move: + if win: + msg += f"You won! Your {say} is greater than Dealer's {say}!" + else: + msg += f"You lost! The Dealer's {say} is greater than your {say}!" + elif win: + msg += f"You won! You got {say}!" + elif tied: + msg += "Both the Dealer and the Player have tied." + else: + msg += f"You lost! The Dealer got {say}." + + msg += box( + f"Dealer's Deck: {self.dealer_deck.suit(0)}{self.dealer_deck.suit(1)}" + f"{self.dealer_deck.suit(2)}{self.dealer_deck.suit(3)}{self.dealer_deck.suit(4)}\n" + f"Your Deck: {self.player_deck.suit(0)}{self.player_deck.suit(1)}" + f"{self.player_deck.suit(2)}{self.player_deck.suit(3)}{self.player_deck.suit(4)}" + ) + self._in_game[ctx.guild.id] = False + self._hit[ctx.guild.id] = False + await ctx.send(msg) + + @staticmethod + def one_pair(deck): + answer = False + for x in range(0, deck.len() - 1): + if deck.num(x) == deck.num(x + 1): + deck.first_pair = deck.num(x) + answer = True + + return answer + + @staticmethod + def two_pair(deck): + answer = False + first_pair = 0 + second_pair = 0 + + for x in range(0, deck.len() - 1): + if deck.num(x) == deck.num(x + 1): + if first_pair == 0: + first_pair = deck.num(x) + elif first_pair != deck.num(x) and second_pair == 0: + second_pair = deck.num(x) + + if first_pair != 0 and second_pair != 0: + deck.first_pair = first_pair + deck.second_pair = second_pair + answer = True + + return answer + + @staticmethod + def three_of_a_kind(deck): + answer = False + for x in range(0, deck.len() - 2): + if deck.num(x) == deck.num(x + 1) and deck.num(x + 1) == deck.num(x + 2): + deck.first_pair = deck.num(x) + answer = True + + return answer + + @staticmethod + def full_house(deck): + answer = False + first_pair = 0 + second_pair = 0 + for x in range(0, deck.len() - 2): + if deck.num(x) == deck.num(x + 1) and deck.num(x + 1) == deck.num(x + 2): + if first_pair == 0: + first_pair = deck.num(x) + for x in range(0, deck.len() - 1): + if deck.num(x) == deck.num(x + 1): + if first_pair != deck.num(x) and second_pair == 0: + second_pair = deck.num(x) + + if first_pair != 0 and second_pair != 0: + deck.first_pair = first_pair + deck.second_pair = second_pair + answer = True + + return answer + + @staticmethod + def four_of_a_kind(deck): + answer = False + for x in range(0, deck.len() - 3): + if ( + deck.num(x) == deck.num(x + 1) + and deck.num(x + 1) == deck.num(x + 2) + and deck.num(x + 2) == deck.num(x + 3) + ): + deck.first_pair = deck.num(x) + answer = True + + return answer + + @staticmethod + def flush(deck): + answer = False + x = 0 + if ( + deck.num(x) == deck.num(x + 1) + and deck.num(x + 1) == deck.num(x + 2) + and deck.num(x + 2) == deck.num(x + 3) + and deck.num(x + 3) == deck.num(x + 4) + ): + deck.first_pair = deck.num(x) + answer = True + + return answer