Git Product home page Git Product logo

sopel-unobot's Introduction

sopel-UnoBot

Play UNO on IRC with the help of your Sopel bot!

Origins

Original code from http://tmarki.com/wp/2010/02/18/irc-uno-bot-for-phenny/ licensed under the FreeBSD / BSD 2-clause license and copyright Tamás Márki.

Code has been archived as a gist by the original author: https://gist.github.com/tmarki/b4b185db7883a2f14a8580be2f5bff4b

The original code was itself a port of a mIRC script, probably the one found at http://hawkee.com/snippet/5301/

There is also an unobot.py version maintained within the @myano/jenni repo, but it is as different from this version as this version is from the code it was built on. A few ideas from that repo may make their way into this one at some point, but most of the development here up to now was done before discovering the other fork.

Changes

This fork includes (but is not limited to including) the following updates:

  • Players can now leave a game in progress.
    • Players can also be kicked from a game in progress by the owner or a bot admin if necessary.
    • The bot saves the hands of players that leave a game in progress until that game is over, to prevent players with large hands from simply doing a quit/join cycle to get back down to 7 cards.
  • Added unohelp command for new players to learn the basics of gameplay and use of many UNO commands not included in Sopel's commands list to keep from polluting it too much.
  • Added cards command for players to have the bot send them their hand again in case they were in another channel when their turn came and the notice was therefore sent to the wrong window.
  • The module now supports a game in each channel, rather than being limited to playing UNO in only one channel.
    • Games can be moved from channel to channel, as well, by the game owner or a bot admin, so as to allow a flourishing discussion in a channel where UNO is being played to continue uninterrupted while the game moves elsewhere.
    • The bot owner can query for a list of channels in which UNO games are running (useful as preparation for updates, bot server maintenance, etc.).
  • Score saving uses JSON objects instead of hardcoded format strings. While less compact, it is much more easily understood by a human reader, and is easier for the bot owner to edit if corrections are needed.
  • The "top 10" list has been renamed from unotop10 to unotop and only displays five (5) entries to reduce spam to the channel.
  • Use new decorators from Phenny's successor, Sopel (formerly known as Willie). — see #sopel @ freenode. This project was formerly known as willie-UnoBot and was renamed to match the bot upon being updated to support the new version.
  • Updated syntax to take advantage of new bot framework features.
    • For example, the bot no longer fails to recognize play commands that contain extra whitespace between the arguments.

Licensing

Parts of this project are covered by the Simplified BSD / FreeBSD / BSD 2-clause license. However, much of it has not yet been declared licensed. See the LICENSE.md file for details.

sopel-unobot's People

Contributors

dgw avatar tmarki avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

sopel-unobot's Issues

Error loading Unobot

Error loading unobot: invalid syntax (unobot.py, line 697)
Grabbed the very latest commit as of today.
I'm on python3.3. Not sure if i'm missing any potential requirements.

UnoBot needs a license

While based on code released under the BSD two-clause license, this project doesn't actually define a license for the copious modifications. I realized this while reading the README and thinking about what features have been added since it was last updated…

My immediate instinct is to go GPL (with which the original license is compatible), but I'd like to think on it. Actually, uncertainty as to which license to use is probably why old me just slapped a disclaimer on it and did nothing further, but that's not responsible open-source behavior. (It's slightly excusable for code I wrote entirely from scratch, but certainly not for derivative works!)

Anyway, this issue is to show that, yes, licensing is on my radar. Not on the front burner, so to speak, but I do want to make a decision soon-ish.

A player kicked before dealing who rejoins after dealing gets 0 cards

The bot "saves" an empty hand, even though the game has not started yet. It shouldn't be saving hands (or even tracking kicked players) if the game hasn't been dealt yet.

Yes, this just happened in gameplay a few minutes ago. Yes, I immediately stopped the game. Yes, it was hilarious.

Verify py2.7 & py3 compatibility

The module code has some hold-overs from the pre-py3 era, like except Exception, e and the trailing print __doc__.strip() that make it incompatible with python 3. An important step for this codebase is to audit the whole thing and make sure every bit of it is cross-compatible with both python2.7 and python3.

I started looking at this thanks to #44, but wanted to open more of a tracking issue for it (rather than that one, which is more of a user-support ticket).

This OP to be updated with a checklist of issues once I've gotten a chance to complete a first pass.

Issues found and status:

  • Trailing print
  • Deprecated except syntax

Fix top10 and/or migrate to key-value store

Tasks:

  • Fix top10 (appears done as of 757be22)
  • Use nick groups to get preferred nick if available (?)
  • Migrate to key-value store

Original issue text:

Currently the scores are being stored incorrectly, such that they print out like this:

[11:00:53] <%Kaede> #1 dgw (469 points 12 games, 6 won, 1:33:56 wasted)
[11:00:53] <%Kaede> #2 Sammie (36 points 1 games, 1 won, 0:15:55 wasted)
[11:00:53] <%Kaede> #3 Slyphoria (20 points 1 games, 1 won, 0:05:56 wasted)
[11:00:54] <%Kaede> #4 Owningmatt93 (0 points 1 games, 0 won, 0:01:31 wasted)
[11:00:55] <%Kaede> #5 Tenzinn (0 points 1 games, 0 won, 0:38:52 wasted)
[11:00:56] <%Kaede> #6 Sammie (0 points 1 games, 0 won, 0:38:52 wasted)
[11:00:56] <%Kaede> #7 Tenzinn (0 points 1 games, 0 won, 0:15:55 wasted)

Fix and/or migrate to willie's (or whatever he's called by the time this is done) key-value store.

Game-ending "gains 1 points" needs fixing

Funnily enough, I seem to recall considering whether to do a grammar check for this message, and deciding that it wasn't worth the effort because a game ending with only 1 point gained would be so unlikely.

Well, it happened. And I never want to see that ugly bad grammar again.

Throws JSON error for empty score file

It's not a major issue. As is evident from the logs, the scores are saved—but it shouldn't spit out an error that doesn't matter.

<@WZ1> .unotop
<@Masa-chan> Error loading UNO scores: No JSON object could be decoded
<@Masa-chan> No scores yet
<@WZ1> kek
<@WZ1> .play b 8
<@Masa-chan> We have a winner: WZ1!!! This game took 00:11:46
<@Masa-chan> WZ1 gains 21 points!
<@Masa-chan> Error loading UNO scores: No JSON object could be decoded
<@WZ1> ffs
<@dgw> .unotop
<@Masa-chan> #1 WZ1 (21 points in 1 game (1 won), 0:11:46 wasted)

Add exceptions to "game owner only" command restrictions

Some commands are (sensibly) limited to being performed by the game owner/starter. However, there are strong cases for letting the bot owner—and possibly admins also—perform those commands in case the game owner is unresponsive or gets disconnected/kicked.

Consider also allowing channel ops+ to perform these actions.

  • Bot owner (maybe admins too) should always be able to stop a game.
  • Bot owner (maybe admins also) should be able to deal in case the game starter isn't paying attention.

.unocolors with no argument causes AttributeError

<dgw> .unocolors
<Sopel> AttributeError: 'NoneType' object has no attribute 'lower' (file
        "/var/lib/sopel/modules/unobot.py", line 767, in set_card_colors)

In addition to fixing the error, this command should actually notice the current color setting to the user, like .unotheme does when given no argument.

With colors off, WD4 appears as just [W]

Reconstructed IRC log to illustrate (since it happened to another player, not me):

<Yayaka> ;cards
-Kaede-: Your cards (4): G[6] R[4] R[S] [W]
<Yayaka> ;play w y
<Yayaka> ;play wd4 y
<Kaede> vil draws four and is skipped!

This is a pretty obvious bug in UnoGame.render_cards_nocolor(). The logic to handle WD4 cards differently from regular W cards is missing, and needs to be added. (I also now think that this method should be part of UnoBot, instead, but I need to think about what else would need refactoring with that and make another issue later.)

Generate regulation decks

Deck Geekery

Current situation

Currently the create_deck() function generates decks as follows:

  • append one of each color RGBY for every card 1-9, D2, S, and R (48 cards)
  • append two each of W and WD4 (4 cards)
  • multiply deck by four

The final (double) deck size is thus 4(48 + 4) = 4(52) = 208

In f9bdf33 the generated deck size was doubled to let the bot generate decks less often, so the true effective deck size is 104 cards (we just shuffle two of them together)—too small.

A regulation UNO deck is as follows:

There are 108 cards in a Uno deck. There are four suits, Red, Green, Yellow and Blue, each consisting of one 0 card, two 1 cards, two 2s, 3s, 4s, 5s, 6s, 7s, 8s and 9s; two Draw Two cards; two Skip cards; and two Reverse cards. In addition there are four Wild cards and four Wild Draw Four cards.
http://play-k.kaserver5.org/Uno.html

This works out to 4( 1 + 2(12) ) + 4 + 4 = 4(25) + 8 = 108

4f18270 was technically incorrect, though before that the decks generated by this module were:

  • append one of each color RGBY for every card 0-9, D2, S, and R (52 cards)
  • append two each of W and WD4 (4 cards)
  • multiply deck by two

This yielded a deck size of 2(52 + 4) = 2(56) = 112, which was too big instead of too small due to extra 0 cards.

Solutions

To generate regulation UNO decks, the following changes are needed:

  • Add back 0 cards
  • Generate half as many 0 cards as other COLORED_CARD_NUMS values

There's no elegant way to generate an actual regulation deck with the current code structure, really. 0s come in different numbers than any other category of card, so the simplest way to do it is to add in one 0 card of each color after adding the specials, before multiplying the deck.

But in case COLORED_CARD_NUMS becomes important in future code, it would be desirable to have 0 in that list to avoid possible hard-to-debug issues. In that case, the process would be:

for {1-9,D2,R,S}:
    for {colors RGBY}:
        add color+card
for {0-9,D2,R,S}:
    for {colors RGBY}:
        add color+card
for {W,WD4}:
    for {4 times}:
        add card

That would yield a deck size of 4(12) + 4(13) + 8 = 48 + 52 + 8 = 108

It is correct, but it would be fragile because the step adding cards without 0s would require using a subset of the list. To mitigate the fragility, sorting the list (likely on startup, rather than every time a deck is generated) would nearly guarantee that 0 is always in the first list position. ("Nearly guarantee" because some future crazy variant of UNO might decide to use punctuation characters that come before digits in the sorting order, but it's unlikely.)

The simple solution could be good for now, with a comment on the global list of card values that it doesn't include 0…or the slightly uglier subset method could be more future-proof.

Eh, this is why modeling real-world objects in code is a PITA…

Omit held cards from newly generated decks

While working on #37 I had this thought: Why not omit cards that are out (in players' hands) from the generated deck?

Have to make a decision about how to handle cards that are held by players who left the game, because their cards are saved in case they rejoin (to prevent quit/join exploit for getting back down to 7). But it is trivial to go through the deck and remove each card held by every player, plus the top card on the discard pile.

Be fully compliant with UNO rules regarding game-starting card

Currently this game will keep drawing top cards at the start of a round if a W or WD4 comes up. The UNO rules specify that the start of a round should be handled as follows:

STARTING A DISCARD PILE
Well, if a word card is the first one turned up from the DRAW pile, this is what happens.
Wild Draw Four - It's put back in the deck and another one is chosen.
Wild Card - The player to the left of the dealer calls out a color - then plays.
Draw Two Card - The player to the left of dealer must pick two cards and then the next player goes (sad, but true).
Reverse Card - The dealer plays first, but then goes to the right instead of the left.
Skip Card - The player to the left of dealer doesn't start (get it, he's skipped). The player to the left of him starts.

http://www.wonkavator.com/uno/unorules.html

Make sure to verify that all of these rules are being followed, in addition to adding the W and WD4 logic. Will probably reduce the elegance of the code somewhat because cardPlayed() or deal() or something in UnoGame will have to handle prompting for a color if the first card is W.

Implement WD4 restriction/penalty

This is the best, meanest, most sneaky card to have. Not only does the player get to call the next color played, but the next player has to pick 4 cards and forfeit his turn. There is a hitch, however (of course): you can only play this card when you don't have a card in your hand that matches the color of the card previously played.
Note: A player may have a matching number or word card of a different color in his hand and plays his "Wild Draw Four" card.
[…]
If a player plays a Wild Draw 4 card illegally and gets caught, he must first show his hand to the player who challenged. If guilty, he must draw 4 cards. If not guilty, the challenger must draw 2 cards in addition to the 4. The challenge can only be made by the person required to draw the four cards.
http://www.wonkavator.com/uno/unorules.html

Be forgiving of swapped .play command arguments

It would be kinder to players if the bot wasn't as strict about the order of .play arguments. Putting the card value first should work just as well as putting the color first. Should be simple enough to handle, since there's very little overlap between the sets of card values and card colors. (R = Red/Reverse is the only conflicting pairing I can think of at the moment.)

Theme support: working. Theme design: not so much.

Theme progress

  • default
    • Blue
    • Green
    • Red
    • Yellow
    • Wild
      • Ideally, make this no-color to let the client decide (requires changing code)
  • dark
    • Blue
    • Green
    • Red
    • Yellow
    • Wild
  • light
    • Blue
    • Green
    • Red
    • Yellow
    • Wild

Labeling this a bug because the game is basically unplayable with anything but the default theme due to how bad the themes look.

In the light theme, the green is too bright, and the cards essentially all need to be bolded for readability.

In the dark theme, the dark cyan stand-in for the default blue is way too close to green.

Add user preference to render cards with a background color

Tasks

  • Add theme support to card renderer [da8f365]
  • Add preference mechanism (setting) [bdf03b5]
  • Add preference mechanism (getting/using) [bdf03b5 / b4e4d12]

Would be great if users could specify whether they want no background color (the default), black, or white behind the cards to help with reading yellow cards in a white IRC window, or blue on a dark gray.

Might behoove the module to allow specifying "Light Blue" or "Cyan" for the blue cards, also, or that could be built into the theme (cyan to be used with the black background option).

Add .cards command to re-send card listing

Even with a client configured to send non-server notices to the current channel, sometimes one needs to switch out of the UNO channel to answer something else. If one's turn comes up during that time, the cards are displayed in the other channel, requiring either a very good memory or some awkward back-and-forth to figure out what to play. By just saying .cards, one should be able to have their deck sent immediately to alleviate this issue.

Convert to cookiecutter module and publish on PyPI

Now that the module is updated for Sopel, and Sopel supports third-party modules installable via pip, this one should be published in the repository.

Decision: cookiecutter modules actually reside in the sopel_modules/modname/modname.py file within the git repository. It's probably possible to rewrite the module history to look like it's always been there—and therefore preserve git-blame-ability—but is that worth doing? Probably not. It could mess up Github's Contributions calendar, and would certainly mess up any commit hashes that are referenced in issue/PR histories.

Ranking ties should all be given the same rank

Given multiple users with the same number of accumulated points, the UNO rankings should assign all of them the same rank, then continue as if they all occupied consecutive slots.

Example:

User Points Rank
noob 121 1
nerd 120 2
geek 120 2
pleb 54 4
(and so on)

The current calculation system assigns different ranks to users with the same number of points based on an arbitrary secondary sort key (name, I think), which isn't really fair.

Allow users to quit/join mid-game

It's not really traditional UNO, but other UNO bots (like this mIRC script, with which I have the most experience) let players quit mid-game. (It also lets them join mid-game, which I think is odd but should be considered.)

Fix AttributeError(s) in play()

[16:30:10] <~dgw> ;play b
[16:30:10] <&Kaede> AttributeError: 'NoneType' object has no attribute 'upper' (file "/var/lib/willie/modules/unobot.py", line 189, in play)
[16:30:11] <~dgw> ;play b 4
[16:30:11] <&Kaede> Owningmatt93's turn. Top Card: [4]
[16:30:12] <~dgw> pls
[16:30:13] <~dgw> wow
[16:30:15] <~dgw> dat bug doe
[16:30:25] <+Owningmatt93> ;play
[16:30:25] <&Kaede> AttributeError: 'NoneType' object has no attribute 'upper' (file "/var/lib/willie/modules/unobot.py", line 187, in play)

Make players say "UNO!" instead of saying it for them

When you have one card left, you must yell "UNO" (meaning one). Failure to do this results in you having to pick two cards from the DRAW pile. That is, of course if you get caught by the other players.

GOING OUT
A player who forgets to say UNO before his card touches the discard pile, but "catches" himself before any other player catches him, is safe and is not subject to the penalty. You may not catch a player for failure to say UNO until his second to last card touches the DISCARD pile. Also, you may not catch a player for failure to say UNO after the next player begins his turn. "Beginning a turn is defines as either drawing a card from the DRAW pile or drawing a card from your hand to play.
If the last card played in a hand is a Draw Two or Wild Draw Four card, the next player must draw the two or four cards. These cards are counted when points are totaled.

— both from http://www.wonkavator.com/uno/unorules.html

Letting players catch each other might be more complicated than it's worth; maybe the bot should always catch people who fail, or have a random chance (~75% maybe) to catch players who fail to say UNO.

Enforce reneging rules

Don't let players who have drawn on the current turn play any other card once they have taken a new one. This is specified in the UNO rules (but I don't have time at this minute to pull a quote).

There should be a new special message for this to inform the player why they may not play a different card.

Make sure all users are in the new channel when doing .unomove

Tentative plan:

  • Stage 1: Refuse to move games if not all users are in the new channel.
  • Stage 2: Notify users not in the new channel to join it, and monitor JOINs to finalize the move when all players are present.
  • Stage 3: Allow players to vote against moving the game if anyone is missing from the new channel.

Original OP follows.


Introduced in bb7c3e8, .unomove is a great way to take the game elsewhere if other users in the channel in which you started playing don't appreciate the noise. But there's no verification that all the current players are actually in the new channel.

This can be handled a few ways. Some ideas:

  • Auto-invite all users who aren't in the new channel
    • Needs to consider channels that are private or have keys, probably not the best option; also requires that Sopel has high enough privileges in the new channel to send invites.
  • Refuse to move the game if not all players are in the new channel
    • Puts more burden on users, but the logic is simpler.
  • Move game when all users are in new channel
    • Requires checking, and then monitoring JOINs in the new channel. New data structures and all sorts of kludgey logic would probably be needed for this one, but it's a good compromise between the other two options.

Won't work with 6.x due to rename

Module needs to import from "sopel" instead of "willie" for 6.x compatibility (the rename that didn't need to happen, happened anyway).

  • Change imports from willie to import from sopel
  • Try to import sopel packages first and fall back to willie if needed
  • Rename repository

Screwing with the imports might be unnecessary if @embolalia pushes a shim to make both willie and sopel imports work with future versions, as is possibly planned. But it's good to have on the radar.

R should act like S in 2-player games

The UNO rules stipulate that in a two-player game, playing an R card is the same as playing an S. That should be reflected in how this module behaves.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.