### # Copyright (c) SpiderDave # Copyright (c) 2020, oddluck # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import re import random import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks import time import os, errno import pickle try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization("UNO") except ImportError: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = lambda x: x class UNO(callbacks.Plugin): """ UNO! """ threaded = True game = [{}, {}, {}, {}, {}] channeloptions = {} channeloptions["allow_game"] = False channeloptions["debug"] = False channeloptions["use_queue"] = True channeloptions["nplayers"] = 10 channeloptions["maxbots"] = 9 channeloptions["use_colors"] = True channeloptions["use_notice"] = True lastgame = time.time() def make_sure_path_exists(path): try: os.makedirs(path) except OSError as exception: if exception.errno != errno.EEXIST: raise make_sure_path_exists(r"%s%suno" % (conf.supybot.directories.data(), os.sep)) dataPath = r"%s%suno%s" % (conf.supybot.directories.data(), os.sep, os.sep) prefixChar = conf.supybot.reply.whenAddressedBy.chars()[0] def start(self, irc, msg, args, text): """ Start a new game of UNO. For the rules of the game, use the "uno rules" command. """ try: self._read_options(irc) except: pass if self.channeloptions["allow_game"] == False: irc.reply("Error: allow_game=False") return def get(group): v = group.getSpecific(channel=msg.args[0]) return v() try: prefixChar = get(conf.supybot.reply.whenAddressedBy.chars)[0] except: prefixChar = "" # may want to add a game variant later if text: gametype = text.lower().strip() if gametype.replace(" ", "") == "globalthermalnuclearwar": irc.reply("Curious game. The only winning move is not to play.") return if gametype not in ["uno"]: irc.reply("Error: Invald game type %s." % gametype) return else: gametype = "uno" nick = msg.nick table = self._gettablefromnick(nick) if table != None: gametype = self.game[table].get("type").capitalize() irc.reply("Error: You are already in a game of %s." % gametype) return table = self._getopentable() if table == None: irc.reply("Sorry, all the game tables are in use at the moment.") return self._cleanup(table) self.game[table]["channel"] = msg.args[0] self.game[table]["type"] = gametype if gametype == "uno": self.game[table]["players"][nick] = {} # self.game[table]['nplayers']=int(self.channeloptions[gametype+'_nplayers']) self.game[table]["nplayers"] = int(self.channeloptions["nplayers"]) irc.reply( "%s has started a new game of %s at table %s. For the rules of the" ' game, type "%suno rules". To accept this challenge, join with "%suno' ' join". To add a cpu player, type "%suno join cpu".' % ( nick, gametype.capitalize(), table + 1, prefixChar, prefixChar, prefixChar, ), prefixNick=False, ) self.game[table]["phase"] = "join" start = wrap(start, ["public", optional("something")]) def begin(self, irc, msg, args): """ Begin the UNO game. """ self._uno_begin(irc, msg.nick) begin = wrap(begin) def _uno_begin(self, irc, nick): table = self._gettablefromnick(nick) if table == None: irc.reply("Error: You are not playing a game at any of the tables.") return if self.game[table]["type"] != "uno": irc.reply("Error: Not an UNO game.") return if self.game[table]["phase"] != "join": irc.reply("Error: You can't use this command right now.") return nplayers = list(self.game[table]["players"].keys()) if len(nplayers) < 2: irc.reply("Error: You need at least two players for UNO.") return # start things for real for n in list(self.game[table]["players"].keys()): self.game[table]["players"][n]["hand"] = [] # each player draws 7 initial cards for i in range(0, 7): card = self.game[table]["deck"].pop( random.randint(0, len(self.game[table]["deck"]) - 1) ) self.game[table]["players"][n]["hand"].append(card) self.game[table]["phase"] = "running" irc.reply( "Game started! Check your notices and follow the instructions.", prefixNick=False, to=self.game[table]["channel"], ) for n in list(self.game[table]["players"].keys()): self._uno_tell_status(irc, n) self._uno_do_cpu(irc, table) def _uno_do_cpu(self, irc, table): while 1: if self.game[table].get("phase") != "running": return if self.game[table].get("type") != "uno": return turnplayer = list(self.game[table]["players"].keys())[ self.game[table]["turn"] ] if self.game[table]["players"][turnplayer].get("cpu"): self._uno_cpu_play(irc, table) else: return def _uno_tell_status(self, irc, nick): table = self._gettablefromnick(nick) if table == None: irc.reply("Error: You are not playing UNO at any of the tables.") return channel = self.game[table]["channel"] def get(group): v = group.getSpecific(channel=channel) return v() try: prefixChar = get(conf.supybot.reply.whenAddressedBy.chars)[0] except: prefixChar = "" opponents = [p for p in list(self.game[table]["players"].keys()) if p != nick] opponent = opponents[0] opponent_cards = [ "%s has %s cards" % (n, len(self.game[table]["players"][n]["hand"])) for n in list(self.game[table]["players"].keys()) if n != nick ] opponent_cards = ", ".join(opponent_cards) topcard = self.game[table]["discard"][-1] if "Wild" in topcard: topcard = "%s (%s)" % (topcard, self.game[table].get("wildcolor")) yourhand = self.game[table]["players"][nick]["hand"] ncards = len(self.game[table]["players"][nick]["hand"]) opponent_ncards = len(self.game[table]["players"][opponent]["hand"]) turnplayer = list(self.game[table]["players"].keys())[self.game[table]["turn"]] if turnplayer == nick: if self.game[table]["players"][turnplayer].get("cpu"): # We'll skip notice in the channel since the cpu player will # take their turn quickly, and no need to announce it. pass else: if self.channeloptions["use_colors"] == True: if "Red" in topcard: topcardcolor = ircutils.mircColor(topcard, "red", "black") elif "Blue" in topcard: topcardcolor = ircutils.mircColor( topcard, "light blue", "black" ) elif "Green" in topcard: topcardcolor = ircutils.mircColor( topcard, "light green", "black" ) else: topcardcolor = ircutils.mircColor(topcard, "yellow", "black") txt = "It is %s's turn. The top card is %s." % (nick, topcardcolor) txt = txt.replace("s's", "s'") irc.reply(txt, to=channel) turnplayer = "your" yourhandcolor = [] for i in range(len(yourhand)): if "Red" in yourhand[i]: yourhandcolor.append( ircutils.mircColor(yourhand[i], "red", "black") ) elif "Green" in yourhand[i]: yourhandcolor.append( ircutils.mircColor(yourhand[i], "light green", "black") ) elif "Blue" in yourhand[i]: yourhandcolor.append( ircutils.mircColor(yourhand[i], "light blue", "black") ) elif "Yellow" in yourhand[i]: yourhandcolor.append( ircutils.mircColor(yourhand[i], "yellow", "black") ) else: yourhandcolor.append( ircutils.mircColor(yourhand[i], None, "black") ) yourhandcolor = utils.str.commaAndify(yourhandcolor) txt = ( "It is your turn. The top card is %s. You have %s cards (%s)." ' %s. To play a card, use the "%suno play" command.' % ( topcardcolor, ncards, yourhandcolor, opponent_cards, prefixChar, ) ) if self.channeloptions["use_notice"] == True: self.reply(irc, txt, to=nick, notice=True, private=True) else: self.reply(irc, txt, to=nick, notice=False, private=True) else: yourhand = utils.str.commaAndify(yourhand) txt = "It is %s's turn. The top card is %s." % (nick, topcard) txt = txt.replace("s's", "s'") irc.reply(txt, to=channel) turnplayer = "your" txt = ( "It is your turn. The top card is %s. You have %s cards (%s)." ' %s. To play a card, use the "%suno play" command.' % (topcard, ncards, yourhand, opponent_cards, prefixChar) ) if self.channeloptions["use_notice"] == True: self.reply(irc, txt, to=nick, notice=True, private=True) else: self.reply(irc, txt, to=nick, notice=False, private=True) else: pass def _uno_is_valid_play(self, table, card, discard, wildcolor): if card == "Wild": # Wild is always a valid play for now return True if card == "Wild Draw 4": turnplayer = list(self.game[table]["players"].keys())[ self.game[table]["turn"] ] if "Wild" not in discard: discardcolor = discard.split(" ", 1)[0] else: discardcolor = wildcolor for c in self.game[table]["players"][turnplayer]["hand"]: if discardcolor in c: return False return True unocolors = ["Blue", "Green", "Red", "Yellow"] for color in unocolors: if color in card: if "Wild" in discard: # Wild or Wild Draw 4 if color == self.game[table].get("wildcolor"): return True else: return False if color in discard: # Color matches return True if card.split(" ", 1)[1] == discard.split(" ", 1)[1]: # Number or word matches return True return False def _uno_draw_card(self, table, player): if len(self.game[table]["deck"]) > 1: card = self.game[table]["deck"].pop( random.randint(0, len(self.game[table]["deck"]) - 1) ) self.game[table]["players"][player]["hand"].append(card) else: card = self.game[table]["deck"].pop(0) self.game[table]["players"][player]["hand"].append(card) if len(self.game[table]["deck"]) == 0: self.game[table]["deck"] = self.game[table]["discard"] self.game[table]["discard"] = [] random.shuffle(self.game[table]["deck"]) topcard = self.game[table]["deck"].pop( random.randint(0, len(self.game[table]["deck"]) - 1) ) self.game[table]["discard"].append(topcard) return card def tellstatus(self, irc, msg, args): """ Tell current UNO status """ self._uno_tell_status(irc, msg.nick) tellstatus = wrap(tellstatus) def rules(self, irc, msg, args, text): """ Display rules for uno. Start a game of uno with the "uno start" command. """ if text: gametype = text.lower().strip() else: gametype = "uno" if gametype == "uno": irc.reply("Rules for UNO: http://www.wonkavator.com/uno/unorules.html") else: irc.reply("Unknown game type.") rules = wrap(rules, [additional("text")]) def join(self, irc, msg, args, table, fakenick): """[] Join a game of UNO previously started with the "uno start" command. Specify
if there is more than one game to join in that channel. """ try: self._read_options(irc) except: pass if self.channeloptions["allow_game"] == False: irc.reply("Error: allow_game=False") return def get(group): v = group.getSpecific(channel=msg.args[0]) return v() try: prefixChar = get(conf.supybot.reply.whenAddressedBy.chars)[0] except: prefixChar = "" nick = msg.nick if table != None: table -= 1 # make tables zero based tables = self._getcurrenttables() if not tables: # no games running irc.reply("Error: There are no games to join.") return if table != None and table not in tables: # given table doesn't have a game going if table not in list(range(len(self.game))): irc.reply("Error: That table doesn't exist") return irc.reply("Error: There is no game at that table") return tables = [t for t in tables if self.game[t]["channel"] == msg.args[0]] if table != None: if table not in tables: irc.reply("Error: That table is in another channel.") return tables = [table] # success! if len(tables) == 0: irc.reply("Error: There are no games to join in this channel.") return elif len(tables) == 1: table = tables[0] else: messagetxt = ( "Please specify which table you'd like to play at (uno join
)." " Current tables are: " ) for t in tables: messagetxt += "Table %s (%s), " % ( t + 1, " ".join(list(self.game[t]["players"].keys())), ) messagetxt = messagetxt.rsplit(", ", 1)[0] + "." irc.reply(messagetxt) return isfake = False iscpu = False if ((self.channeloptions["debug"]) and fakenick) or ( fakenick and fakenick.lower() == "cpu" ): nick = fakenick isfake = True if fakenick.lower() == "cpu": iscpu = True if self.game[table]["phase"] == "join": if nick in list(self.game[table]["players"].keys()): irc.reply("Error: you have already joined.") return if self.game[table]["type"] == "uno": self._init_uno(table) if iscpu == True: nick = self._uno_make_cpu(table) else: self.game[table]["players"][nick] = {} if isfake == True: self.game[table]["players"][nick]["fake"] = True if ( len(list(self.game[table]["players"].keys())) < self.game[table]["nplayers"] ): irc.reply( "%s has joined the %s game at table %s. Use %suno begin to" " begin the game." % (nick, self.game[table]["type"], table + 1, prefixChar), prefixNick=False, to=self.game[table]["channel"], ) return for n in list(self.game[table]["players"].keys()): self.game[table]["players"][n]["hand"] = [] # each player draws 7 initial cards for i in range(0, 7): card = self.game[table]["deck"].pop( random.randint(0, len(self.game[table]["deck"]) - 1) ) self.game[table]["players"][n]["hand"].append(card) irc.reply( "Game started! Check your notices and follow the instructions.", prefixNick=False, to=self.game[table]["channel"], ) for n in list(self.game[table]["players"].keys()): self._uno_tell_status(irc, n) self.game[table]["phase"] = "running" else: if self.game[table]["phase"] == "running": irc.reply("Error: Game already running.") return elif self.game[table]["phase"] == "": irc.reply( 'Error: You need to create a game with the "uno start" command' " first." ) return else: # don't know when this would happen, but whatever irc.reply("Error: not join phase.") return join = wrap(join, ["public", optional("int"), optional("something")]) def leave(self, irc, msg, args, fakenick): """ Leave a game of UNO. """ try: self._read_options(irc) except: pass if self.channeloptions["allow_game"] == False: irc.reply("Error: allow_game=False") return nick = msg.nick if self.channeloptions["debug"] and fakenick: nick = fakenick table = self._gettablefromnick(nick) if table == None: irc.reply("Error: You are not playing a game at any of the tables.") return if self.game[table].get("type") == "uno": self._leavegame(irc, msg, nick) self._uno_do_cpu(irc, table) # only works if game type is uno return leave = wrap(leave, ["public", optional("something")]) def _leavegame(self, irc, msg, nick): """ Leave a game of UNO. """ try: self._read_options(irc) except: pass if self.channeloptions["allow_game"] == False: irc.reply("Error: allow_game=False") return table = self._gettablefromnick(nick) if table == None: return channel = self.game[table]["channel"] # leaving a game when you're the only player if len(self.game[table]["players"]) == 1: irc.reply("There are no more players; The game is over.", to=channel) self.game[table]["phase"] = "gameover" self._cleanup(table) return # check if it's only bot players left Human = False for n in list(self.game[table]["players"].keys()): if not self.game[table]["players"][n].get("cpu"): if n != nick: Human = True if not Human: irc.reply("There are no more human players; the game is over.", to=channel) self.game[table]["phase"] = "gameover" self._cleanup(table) return # ---- replace with cpu ---- oldnick = nick nick = self._uno_make_cpu(table) del self.game[table]["players"][ nick ] # remove new cpu player (we just want the nick) self.game[table]["players"][nick] = self.game[table]["players"][oldnick] del self.game[table]["players"][oldnick] self.game[table]["players"][nick]["fake"] = True self.game[table]["players"][nick]["cpu"] = True irc.reply( "%s has been replaced by %s." % (oldnick, nick), prefixNick=False, to=self.game[table]["channel"], ) return def _init_uno(self, table): self.game[table]["deck"] = [] self.game[table]["discard"] = [] self.game[table]["wildcolor"] = "Red" self.game[table]["turn"] = 0 self.game[table]["direction"] = 1 unocolors = ["Blue", "Green", "Red", "Yellow"] for unocolor in unocolors: for i in range(10): self.game[table]["deck"].append("%s %s" % (unocolor, i)) for i in range(1, 10): self.game[table]["deck"].append("%s %s" % (unocolor, i)) for i in range(0, 2): self.game[table]["deck"].append("%s Draw Two" % unocolor) self.game[table]["deck"].append("%s Reverse" % unocolor) self.game[table]["deck"].append("%s Skip" % unocolor) for i in range(0, 4): self.game[table]["deck"].append("Wild") self.game[table]["deck"].append("Wild Draw 4") self.unodeck = [card for card in self.game[table]["deck"]] random.shuffle(self.game[table]["deck"]) card = self.game[table]["deck"].pop( random.randint(0, len(self.game[table]["deck"]) - 1) ) self.game[table]["discard"].append(card) def _uno_make_cpu(self, table): nicklist = [ "Snarl", "Slag", "Sludge", "Swoop", "Grimlock", "Data", "Lore", "B-4", "Marvin", "Deep Thought", "Gort", "C-3PO", "R2-D2", "Daryl", "Bishop", "Johnny 5", "Bender", "Flexo", "Dorothy", "Robot B-9", "K-9", "Vicki", "Buffybot", "Lyla", "Robo", "Cyrax", "Sektor", ] nicklist = [ n for n in nicklist if n not in list(self.game[table]["players"].keys()) ] nick = random.choice(nicklist) # assumes nick isn't taken atm self.game[table]["players"][nick] = {} self.game[table]["players"][nick]["fake"] = True self.game[table]["players"][nick]["cpu"] = True return nick def _uno_cpu_play(self, irc, table): channel = self.game[table]["channel"] Human = False for n in list(self.game[table]["players"].keys()): if not self.game[table]["players"][n].get("cpu"): Human = True if not Human: irc.reply("There are no more human players; the game is over.", to=channel) self.game[table]["phase"] = "gameover" self._cleanup(table) return nick = list(self.game[table]["players"].keys())[self.game[table]["turn"]] discard = self.game[table]["discard"][-1] wildcolor = self.game[table].get("wildcolor") novalid = True for c in self.game[table]["players"][nick]["hand"]: if self._uno_is_valid_play(table, c, discard, wildcolor) == True: novalid = False if novalid == True: # draw a card card = self._uno_draw_card(table, nick) self.game[table]["players"][nick]["hasdrawn"] = True if self._uno_is_valid_play(table, card, discard, wildcolor) == True: # always play the card if possible ncards = len(self.game[table]["players"][nick]["hand"]) irc.reply( "%s draws a card, and plays it; It's a %s (%s cards left in hand)." % (nick, card, ncards), to=channel, ) self.game[table]["players"][nick]["hand"].remove(card) self.game[table]["discard"].append(card) else: # Can't play a card, end turn ncards = len(self.game[table]["players"][nick]["hand"]) irc.reply( "%s draws a card (%s cards in hand)." % (nick, ncards), to=self.game[table]["channel"], ) card = "" else: # pick a random card playablecards = [ c for c in self.game[table]["players"][nick]["hand"] if self._uno_is_valid_play(table, c, discard, wildcolor) == True or "Wild" in c ] card = random.choice(playablecards) if card == "Wild" or card == "Wild Draw 4": # Just do dumb blind color choice unocolors = ["Blue", "Green", "Red", "Yellow"] self.game[table]["wildcolor"] = random.choice(unocolors) self.game[table]["players"][nick]["hand"].remove(card) self.game[table]["discard"].append(card) ncards = len(self.game[table]["players"][nick]["hand"]) if "Wild" in card: card = "%s (%s)" % (card, self.game[table]["wildcolor"]) if self.channeloptions["use_colors"] == True: if "Red" in card: cardcolor = ircutils.mircColor(card, "red", "black") elif "Blue" in card: cardcolor = ircutils.mircColor(card, "light blue", "black") elif "Green" in card: cardcolor = ircutils.mircColor(card, "light green", "black") else: cardcolor = ircutils.mircColor(card, "yellow", "black") irc.reply( "%s played %s (%s cards left in hand)." % (nick, cardcolor, ncards), to=channel, ) else: irc.reply( "%s played %s (%s cards left in hand)." % (nick, card, ncards), to=channel, ) ncards = len(self.game[table]["players"][nick]["hand"]) if ncards == 0: irc.reply( "The game is over. " + ircutils.bold("%s wins!" % nick), to=channel ) self.game[table]["phase"] = "gameover" self._cleanup(table) return if "Reverse" in card: self.game[table]["direction"] *= -1 nplayers = len(list(self.game[table]["players"].keys())) turn = self.game[table]["turn"] + 1 * self.game[table]["direction"] if turn > nplayers - 1: turn = 0 if turn < 0: turn = nplayers - 1 self.game[table]["turn"] = turn if "Draw Two" in card or "Draw 4" in card: ndrawcards = 2 if "Draw 4" in card: ndrawcards = 4 drawplayer = list(self.game[table]["players"].keys())[ self.game[table]["turn"] ] for n in range(ndrawcards): c = self.game[table]["deck"].pop( random.randint(0, len(self.game[table]["deck"]) - 1) ) self.game[table]["players"][drawplayer]["hand"].append(c) ncards = len(self.game[table]["players"][drawplayer]["hand"]) irc.reply( "%s draws %s cards (%s cards in hand)." % (drawplayer, ndrawcards, ncards), to=channel, ) # Skip turn if ( "Skip" in card or "Draw Two" in card or "Draw 4" in card or ("Reverse" in card and nplayers == 2) ): turn = self.game[table]["turn"] + 1 * self.game[table]["direction"] if turn > nplayers - 1: turn = 0 if turn < 0: turn = nplayers - 1 self.game[table]["turn"] = turn for n in list(self.game[table]["players"].keys()): self._uno_tell_status(irc, n) return def play(self, irc, msg, args, text): """ Play a for the UNO game. Examples: "uno play red 0", "uno play wild blue", "uno play draw", "uno play done" """ nick = msg.nick def get(group): v = group.getSpecific(channel=msg.args[0]) return v() try: prefixChar = get(conf.supybot.reply.whenAddressedBy.chars)[0] except: prefixChar = "" table = self._gettablefromnick(nick) if table == None: irc.reply("Error: You are not playing a game at any of the tables.") return channel = self.game[table]["channel"] if ( self.channeloptions["debug"] and text.rsplit(" ", 1)[-1] in self.game[table]["players"] ): fakenick = [ p for p in self.game[table]["players"] if p.lower() == text.rsplit(" ", 1)[-1].lower() ] if len(fakenick) == 0: fakenick = None else: fakenick = fakenick[0] nick = fakenick text = text.rsplit(" ", 1)[:-1][0] if self.game[table]["phase"] == "running": nplayers = len(list(self.game[table]["players"].keys())) if nick not in self.game[table]["players"]: irc.reply("Error: You're not playing this game.") return opponent = [p for p in self.game[table]["players"] if p != nick][0] turnplayer = list(self.game[table]["players"].keys())[ self.game[table]["turn"] ] if nick != turnplayer: # Note: it will prefix nick in notice -- need to fix that irc.reply("%s: It is %s's turn." % (nick, turnplayer), prefixNick=False) return text = text.strip() discard = self.game[table]["discard"][-1] wildcolor = self.game[table].get("wildcolor") novalid = True for c in self.game[table]["players"][nick]["hand"]: if self._uno_is_valid_play(table, c, discard, wildcolor) == True: novalid = False if text.lower() == "draw": if self.game[table]["players"][nick].get("hasdrawn") == True: irc.reply("You have already drawn a card.") return if novalid == False: irc.reply("You can't draw because you have a card you can play") return else: # Draw a card c = self.game[table]["deck"].pop( random.randint(0, len(self.game[table]["deck"]) - 1) ) self.game[table]["players"][nick]["hand"].append(c) if self._uno_is_valid_play(table, c, discard, wildcolor) == True: self.reply( irc, "You draw a %s from the draw pile. You can choose not to" ' play this card using "%suno play done"' % (c, prefixChar), to=nick, notice=True, private=True, ) self.game[table]["players"][nick]["hasdrawn"] = True return # card=c else: # Can't play a card, end turn ncards = len(self.game[table]["players"][nick]["hand"]) self.reply( irc, "You draw a %s from the draw pile. You have no cards to" " play, and your turn is over." % c, to=nick, notice=True, private=True, ) irc.reply( "%s draws a card (%s cards in hand)." % (nick, ncards), to=self.game[table]["channel"], ) turn = ( self.game[table]["turn"] + 1 * self.game[table]["direction"] ) if turn > nplayers - 1: turn = 0 if turn < 0: turn = nplayers - 1 self.game[table]["turn"] = turn for n in list(self.game[table]["players"].keys()): self._uno_tell_status(irc, n) self._uno_do_cpu(irc, table) return if text.lower() == "done": if self.game[table]["players"][nick].get("hasdrawn") != True: self.reply( irc, "You can't finish your turn without playing or drawing a card.", to=nick, notice=True, private=True, ) return ncards = len(self.game[table]["players"][nick]["hand"]) irc.reply( "%s draws a card (%s cards in hand)." % (nick, ncards), to=self.game[table]["channel"], ) self.game[table]["players"][nick]["hasdrawn"] = False turn = self.game[table]["turn"] + 1 * self.game[table]["direction"] if turn > nplayers - 1: turn = 0 if turn < 0: turn = nplayers - 1 self.game[table]["turn"] = turn for nn in list(self.game[table]["players"].keys()): self._uno_tell_status(irc, nn) self._uno_do_cpu(irc, table) return card = text card = [card for card in self.unodeck if card.lower() == text.lower()] validwild = [ "Wild Blue", "Wild Green", "Wild Red", "Wild Yellow", "Wild Draw 4 Blue", "Wild Draw 4 Green", "Wild Draw 4 Red", "Wild Draw 4 Yellow", ] if len(card) == 0: card = [c for c in validwild if c.lower() == text.lower()] if len(card) != 0: card = card[0].rsplit(" ", 1) newcolor = card[1] card = [card[0]] else: if card[0] == "Wild" or card[0] == "Wild Draw 4": irc.reply("You must specify a color when playing a Wild card.") return if len(card) == 0: irc.reply("That is not a valid card.") return else: card = card[0] hand = self.game[table]["players"][nick]["hand"] if card not in hand: irc.reply("That card is not in your hand.") return # check for illegal move if self._uno_is_valid_play(table, card, discard, wildcolor) == False: irc.reply("You can't play that card.") return # play the card self.game[table]["players"][nick]["hand"].remove(card) self.game[table]["discard"].append(card) ncards = len(self.game[table]["players"][nick]["hand"]) if "Wild" in card: self.game[table]["wildcolor"] = newcolor card = "%s (%s)" % (card, self.game[table]["wildcolor"]) if self.channeloptions["use_colors"] == True: if "Red" in card: cardcolor = ircutils.mircColor(card, "red", "black") elif "Blue" in card: cardcolor = ircutils.mircColor(card, "light blue", "black") elif "Green" in card: cardcolor = ircutils.mircColor(card, "light green", "black") else: cardcolor = ircutils.mircColor(card, "yellow", "black") if self.game[table]["players"][nick].get("hasdrawn") == True: irc.reply( "%s draws a card, and plays it; It's a %s (%s cards left in" " hand)." % (nick, cardcolor, ncards), to=channel, ) self.game[table]["players"][nick]["hasdrawn"] = False else: irc.reply( "%s played %s (%s cards left in hand)." % (nick, cardcolor, ncards), to=channel, ) else: if self.game[table]["players"][nick].get("hasdrawn") == True: irc.reply( "%s draws a card, and plays it; It's a %s (%s cards left in" " hand)." % (nick, card, ncards), to=channel, ) self.game[table]["players"][nick]["hasdrawn"] = False else: irc.reply( "%s played %s (%s cards left in hand)." % (nick, card, ncards), to=channel, ) ncards = len(self.game[table]["players"][nick]["hand"]) if ncards == 0: irc.reply( "The game is over. " + ircutils.bold("%s wins!" % nick), to=channel ) self.game[table]["phase"] = "gameover" self._cleanup(table) return if "Reverse" in card: self.game[table]["direction"] *= -1 turn = self.game[table]["turn"] + 1 * self.game[table]["direction"] if turn > nplayers - 1: turn = 0 if turn < 0: turn = nplayers - 1 self.game[table]["turn"] = turn if "Draw Two" in card or "Draw 4" in card: ndrawcards = 2 if "Draw 4" in card: ndrawcards = 4 drawplayer = list(self.game[table]["players"].keys())[ self.game[table]["turn"] ] for n in range(ndrawcards): c = self.game[table]["deck"].pop( random.randint(0, len(self.game[table]["deck"]) - 1) ) self.game[table]["players"][drawplayer]["hand"].append(c) ncards = len(self.game[table]["players"][drawplayer]["hand"]) irc.reply( "%s draws %s cards (%s cards in hand)." % (drawplayer, ndrawcards, ncards), to=channel, ) # Skip turn if ( "Skip" in card or "Draw Two" in card or "Draw 4" in card or ("Reverse" in card and nplayers == 2) ): turn = self.game[table]["turn"] + 1 * self.game[table]["direction"] if turn > nplayers - 1: turn = 0 if turn < 0: turn = nplayers - 1 self.game[table]["turn"] = turn for n in list(self.game[table]["players"].keys()): self._uno_tell_status(irc, n) self._uno_do_cpu(irc, table) else: irc.reply("Error: game not running") play = wrap(play, ["text"]) def setoption(self, irc, msg, args, channel, text, value): """