rbot 0.9.10 → 0.9.12
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +22 -4
- data/COPYING +5 -18
- data/ChangeLog +396 -0
- data/README +23 -25
- data/REQUIREMENTS +58 -12
- data/Rakefile +215 -0
- data/bin/rbot +47 -25
- data/bin/rbot-remote +76 -0
- data/data/rbot/languages/french.lang +5 -5
- data/data/rbot/languages/german.lang +4 -4
- data/data/rbot/languages/italian.lang +53 -0
- data/data/rbot/languages/japanese.lang +43 -0
- data/data/rbot/languages/russian.lang +60 -60
- data/data/rbot/languages/traditional_chinese.lang +75 -0
- data/data/rbot/plugins/alias.rb +192 -0
- data/data/rbot/plugins/autoop.rb +74 -37
- data/data/rbot/plugins/autorejoin.rb +15 -5
- data/data/rbot/plugins/bans.rb +454 -150
- data/data/rbot/plugins/bash.rb +173 -31
- data/data/rbot/plugins/botsnack.rb +37 -0
- data/data/rbot/plugins/cal.rb +11 -2
- data/data/rbot/plugins/chanserv.rb +132 -0
- data/data/rbot/plugins/chucknorris.rb +2 -2
- data/data/rbot/plugins/chucknorris.yml.gz +0 -0
- data/data/rbot/plugins/debugger.rb +136 -0
- data/data/rbot/plugins/deepthoughts.rb +89 -95
- data/data/rbot/plugins/delicious.rb +109 -0
- data/data/rbot/plugins/dice.rb +77 -25
- data/data/rbot/plugins/dict.rb +243 -0
- data/data/rbot/plugins/dictclient.rb +211 -0
- data/data/rbot/plugins/digg.rb +2 -2
- data/data/rbot/plugins/excuse.rb +4 -4
- data/data/rbot/plugins/factoids.rb +506 -0
- data/data/rbot/plugins/figlet.rb +127 -18
- data/data/rbot/plugins/fish.rb +93 -33
- data/data/rbot/plugins/forecast.rb +40 -27
- data/data/rbot/plugins/fortune.rb +86 -11
- data/data/rbot/plugins/freshmeat.rb +46 -12
- data/data/rbot/plugins/games/azgame.rb +565 -0
- data/data/rbot/plugins/games/quiz.rb +961 -0
- data/data/rbot/plugins/games/roshambo.rb +62 -0
- data/data/rbot/plugins/{roulette.rb → games/roulette.rb} +58 -8
- data/data/rbot/plugins/games/shiritori.rb +485 -0
- data/data/rbot/plugins/games/uno.rb +1205 -0
- data/data/rbot/plugins/games/wheelfortune.rb +615 -0
- data/data/rbot/plugins/grouphug.rb +76 -22
- data/data/rbot/plugins/hl2.rb +97 -0
- data/data/rbot/plugins/host.rb +10 -1
- data/data/rbot/plugins/httpd.rb.disabled +1 -0
- data/data/rbot/plugins/imdb.rb +511 -47
- data/data/rbot/plugins/insult.rb +32 -37
- data/data/rbot/plugins/iplookup.rb +224 -224
- data/data/rbot/plugins/karma.rb +45 -21
- data/data/rbot/plugins/keywords.rb +258 -160
- data/data/rbot/plugins/lart.rb +126 -95
- data/data/rbot/plugins/lastfm.rb +421 -12
- data/data/rbot/plugins/linkbot.rb +75 -0
- data/data/rbot/plugins/markov.rb +103 -46
- data/data/rbot/plugins/math.rb +6 -5
- data/data/rbot/plugins/modes.rb +114 -0
- data/data/rbot/plugins/nickrecover.rb +97 -0
- data/data/rbot/plugins/nickserv.rb +111 -48
- data/data/rbot/plugins/nslookup.rb +15 -19
- data/data/rbot/plugins/plugin.header +11 -0
- data/data/rbot/plugins/quakeauth.rb +74 -11
- data/data/rbot/plugins/quotes.rb +189 -233
- data/data/rbot/plugins/reaction.rb +415 -0
- data/data/rbot/plugins/remind.rb +11 -15
- data/data/rbot/plugins/remotectl.rb +29 -0
- data/data/rbot/plugins/ri.rb +74 -0
- data/data/rbot/plugins/rot13.rb +16 -1
- data/data/rbot/plugins/rss.rb +1219 -585
- data/data/rbot/plugins/salut.rb +226 -0
- data/data/rbot/plugins/script.rb +184 -0
- data/data/rbot/plugins/search.rb +233 -0
- data/data/rbot/plugins/seen.rb +44 -40
- data/data/rbot/plugins/shortenurls.rb +93 -0
- data/data/rbot/plugins/slashdot.rb +49 -11
- data/data/rbot/plugins/spell.rb +37 -27
- data/data/rbot/plugins/theyfightcrime.rb +36 -39
- data/data/rbot/plugins/threat.rb +25 -22
- data/data/rbot/plugins/time.rb +135 -0
- data/data/rbot/plugins/topic.rb +107 -50
- data/data/rbot/plugins/translator.rb +360 -0
- data/data/rbot/plugins/tube.rb +28 -40
- data/data/rbot/plugins/twitter.rb +187 -0
- data/data/rbot/plugins/urban.rb +62 -53
- data/data/rbot/plugins/url.rb +193 -409
- data/data/rbot/plugins/usermodes.rb +26 -0
- data/data/rbot/plugins/wall.rb +60 -0
- data/data/rbot/plugins/weather.rb +254 -598
- data/data/rbot/plugins/wow.rb +41 -37
- data/data/rbot/plugins/wserver.rb +17 -25
- data/data/rbot/plugins/youtube.rb +237 -0
- data/data/rbot/templates/lart/{larts → larts-english} +0 -0
- data/data/rbot/templates/lart/larts-italian +25 -0
- data/data/rbot/templates/lart/larts-japanese +10 -0
- data/data/rbot/templates/lart/{praises → praises-english} +0 -0
- data/data/rbot/templates/lart/praises-italian +4 -0
- data/data/rbot/templates/lart/praises-japanese +3 -0
- data/data/rbot/templates/quiz/win_messages +11 -0
- data/data/rbot/templates/salut/salut-english +40 -0
- data/data/rbot/templates/salut/salut-french +29 -0
- data/data/rbot/templates/salut/salut-italian +51 -0
- data/data/rbot/templates/salut/salut-japanese +31 -0
- data/lib/rbot/botuser.rb +940 -0
- data/lib/rbot/config-compat.rb +23 -0
- data/lib/rbot/config.rb +211 -276
- data/lib/rbot/core/auth.rb +1063 -0
- data/lib/rbot/core/basics.rb +214 -0
- data/lib/rbot/core/config.rb +330 -0
- data/lib/rbot/core/filters_ui.rb +64 -0
- data/lib/rbot/core/irclog.rb +292 -0
- data/lib/rbot/core/remote.rb +401 -0
- data/lib/rbot/core/unicode.rb +91 -0
- data/lib/rbot/core/userdata.rb +200 -0
- data/lib/rbot/core/utils/extends.rb +444 -0
- data/lib/rbot/core/utils/filters.rb +154 -0
- data/lib/rbot/core/utils/httputil.rb +689 -0
- data/lib/rbot/core/utils/utils.rb +717 -0
- data/lib/rbot/dbhash.rb +30 -9
- data/lib/rbot/irc.rb +1966 -0
- data/lib/rbot/ircbot.rb +816 -675
- data/lib/rbot/ircsocket.rb +246 -232
- data/lib/rbot/language.rb +86 -8
- data/lib/rbot/load-gettext.rb +154 -0
- data/lib/rbot/maskdb.rb +162 -0
- data/lib/rbot/message.rb +392 -65
- data/lib/rbot/messagemapper.rb +438 -94
- data/lib/rbot/pkgconfig.rb +6 -0
- data/lib/rbot/plugins.rb +713 -172
- data/lib/rbot/post-config.rb +1 -0
- data/lib/rbot/rbotconfig.rb +31 -12
- data/lib/rbot/registry.rb +79 -48
- data/lib/rbot/rfc2812.rb +927 -519
- data/lib/rbot/timer.rb +232 -167
- data/po/en_US/rbot-alias.po +82 -0
- data/po/en_US/rbot-autoop.po +0 -0
- data/po/en_US/rbot-autorejoin.po +0 -0
- data/po/en_US/rbot-azgame.po +200 -0
- data/po/en_US/rbot-bans.po +0 -0
- data/po/en_US/rbot-bash.po +0 -0
- data/po/en_US/rbot-botsnack.po +0 -0
- data/po/en_US/rbot-cal.po +3 -0
- data/po/en_US/rbot-chanserv.po +0 -0
- data/po/en_US/rbot-chucknorris.po +0 -0
- data/po/en_US/rbot-debugger.po +0 -0
- data/po/en_US/rbot-deepthoughts.po +0 -0
- data/po/en_US/rbot-delicious.po +0 -0
- data/po/en_US/rbot-dice.po +0 -0
- data/po/en_US/rbot-dict.po +0 -0
- data/po/en_US/rbot-dictclient.po +124 -0
- data/po/en_US/rbot-digg.po +0 -0
- data/po/en_US/rbot-eightball.po +0 -0
- data/po/en_US/rbot-excuse.po +0 -0
- data/po/en_US/rbot-factoids.po +107 -0
- data/po/en_US/rbot-figlet.po +36 -0
- data/po/en_US/rbot-fish.po +0 -0
- data/po/en_US/rbot-forecast.po +0 -0
- data/po/en_US/rbot-fortune.po +0 -0
- data/po/en_US/rbot-freshmeat.po +0 -0
- data/po/en_US/rbot-grouphug.po +18 -0
- data/po/en_US/rbot-hl2.po +0 -0
- data/po/en_US/rbot-host.po +3 -0
- data/po/en_US/rbot-imdb.po +0 -0
- data/po/en_US/rbot-insult.po +0 -0
- data/po/en_US/rbot-iplookup.po +0 -0
- data/po/en_US/rbot-karma.po +0 -0
- data/po/en_US/rbot-keywords.po +24 -0
- data/po/en_US/rbot-lart.po +0 -0
- data/po/en_US/rbot-lastfm.po +172 -0
- data/po/en_US/rbot-linkbot.po +0 -0
- data/po/en_US/rbot-markov.po +20 -0
- data/po/en_US/rbot-math.po +0 -0
- data/po/en_US/rbot-modes.po +0 -0
- data/po/en_US/rbot-nickrecover.po +40 -0
- data/po/en_US/rbot-nickserv.po +104 -0
- data/po/en_US/rbot-nslookup.po +0 -0
- data/po/en_US/rbot-quakeauth.po +0 -0
- data/po/en_US/rbot-quiz.po +0 -0
- data/po/en_US/rbot-quotes.po +108 -0
- data/po/en_US/rbot-reaction.po +0 -0
- data/po/en_US/rbot-remind.po +0 -0
- data/po/en_US/rbot-remotectl.po +0 -0
- data/po/en_US/rbot-ri.po +0 -0
- data/po/en_US/rbot-roshambo.po +0 -0
- data/po/en_US/rbot-rot13.po +0 -0
- data/po/en_US/rbot-roulette.po +0 -0
- data/po/en_US/rbot-rss.po +20 -0
- data/po/en_US/rbot-salut.po +0 -0
- data/po/en_US/rbot-script.po +0 -0
- data/po/en_US/rbot-search.po +0 -0
- data/po/en_US/rbot-seen.po +0 -0
- data/po/en_US/rbot-shiritori.po +113 -0
- data/po/en_US/rbot-shortenurls.po +0 -0
- data/po/en_US/rbot-slashdot.po +0 -0
- data/po/en_US/rbot-spell.po +54 -0
- data/po/en_US/rbot-theyfightcrime.po +0 -0
- data/po/en_US/rbot-threat.po +0 -0
- data/po/en_US/rbot-time.po +0 -0
- data/po/en_US/rbot-topic.po +0 -0
- data/po/en_US/rbot-translator.po +77 -0
- data/po/en_US/rbot-tube.po +0 -0
- data/po/en_US/rbot-twitter.po +24 -0
- data/po/en_US/rbot-uno.po +512 -0
- data/po/en_US/rbot-urban.po +0 -0
- data/po/en_US/rbot-url.po +0 -0
- data/po/en_US/rbot-usermodes.po +0 -0
- data/po/en_US/rbot-wall.po +33 -0
- data/po/en_US/rbot-weather.po +0 -0
- data/po/en_US/rbot-wheelfortune.po +205 -0
- data/po/en_US/rbot-wow.po +0 -0
- data/po/en_US/rbot-wserver.po +0 -0
- data/po/en_US/rbot-youtube.po +58 -0
- data/po/en_US/rbot.po +1097 -0
- data/po/fr/rbot-alias.po +97 -0
- data/po/fr/rbot-autoop.po +0 -0
- data/po/fr/rbot-autorejoin.po +0 -0
- data/po/fr/rbot-azgame.po +204 -0
- data/po/fr/rbot-bans.po +0 -0
- data/po/fr/rbot-bash.po +0 -0
- data/po/fr/rbot-botsnack.po +0 -0
- data/po/fr/rbot-cal.po +3 -0
- data/po/fr/rbot-chanserv.po +0 -0
- data/po/fr/rbot-chucknorris.po +0 -0
- data/po/fr/rbot-debugger.po +0 -0
- data/po/fr/rbot-deepthoughts.po +0 -0
- data/po/fr/rbot-delicious.po +0 -0
- data/po/fr/rbot-dice.po +0 -0
- data/po/fr/rbot-dict.po +0 -0
- data/po/fr/rbot-dictclient.po +134 -0
- data/po/fr/rbot-digg.po +0 -0
- data/po/fr/rbot-eightball.po +0 -0
- data/po/fr/rbot-excuse.po +0 -0
- data/po/fr/rbot-factoids.po +111 -0
- data/po/fr/rbot-figlet.po +37 -0
- data/po/fr/rbot-fish.po +0 -0
- data/po/fr/rbot-forecast.po +0 -0
- data/po/fr/rbot-fortune.po +0 -0
- data/po/fr/rbot-freshmeat.po +0 -0
- data/po/fr/rbot-grouphug.po +18 -0
- data/po/fr/rbot-hl2.po +0 -0
- data/po/fr/rbot-host.po +3 -0
- data/po/fr/rbot-imdb.po +0 -0
- data/po/fr/rbot-insult.po +0 -0
- data/po/fr/rbot-iplookup.po +0 -0
- data/po/fr/rbot-karma.po +0 -0
- data/po/fr/rbot-keywords.po +24 -0
- data/po/fr/rbot-lart.po +0 -0
- data/po/fr/rbot-lastfm.po +172 -0
- data/po/fr/rbot-linkbot.po +0 -0
- data/po/fr/rbot-markov.po +20 -0
- data/po/fr/rbot-math.po +0 -0
- data/po/fr/rbot-modes.po +0 -0
- data/po/fr/rbot-nickrecover.po +36 -0
- data/po/fr/rbot-nickserv.po +116 -0
- data/po/fr/rbot-nslookup.po +0 -0
- data/po/fr/rbot-quakeauth.po +0 -0
- data/po/fr/rbot-quiz.po +0 -0
- data/po/fr/rbot-quotes.po +138 -0
- data/po/fr/rbot-reaction.po +0 -0
- data/po/fr/rbot-remind.po +0 -0
- data/po/fr/rbot-remotectl.po +0 -0
- data/po/fr/rbot-ri.po +0 -0
- data/po/fr/rbot-roshambo.po +0 -0
- data/po/fr/rbot-rot13.po +0 -0
- data/po/fr/rbot-roulette.po +0 -0
- data/po/fr/rbot-rss.po +20 -0
- data/po/fr/rbot-salut.po +0 -0
- data/po/fr/rbot-script.po +0 -0
- data/po/fr/rbot-search.po +0 -0
- data/po/fr/rbot-seen.po +0 -0
- data/po/fr/rbot-shiritori.po +119 -0
- data/po/fr/rbot-shortenurls.po +0 -0
- data/po/fr/rbot-slashdot.po +0 -0
- data/po/fr/rbot-spell.po +56 -0
- data/po/fr/rbot-theyfightcrime.po +0 -0
- data/po/fr/rbot-threat.po +0 -0
- data/po/fr/rbot-time.po +0 -0
- data/po/fr/rbot-topic.po +0 -0
- data/po/fr/rbot-translator.po +90 -0
- data/po/fr/rbot-tube.po +0 -0
- data/po/fr/rbot-twitter.po +24 -0
- data/po/fr/rbot-uno.po +521 -0
- data/po/fr/rbot-urban.po +0 -0
- data/po/fr/rbot-url.po +0 -0
- data/po/fr/rbot-usermodes.po +0 -0
- data/po/fr/rbot-wall.po +33 -0
- data/po/fr/rbot-weather.po +0 -0
- data/po/fr/rbot-wheelfortune.po +255 -0
- data/po/fr/rbot-wow.po +0 -0
- data/po/fr/rbot-wserver.po +0 -0
- data/po/fr/rbot-youtube.po +64 -0
- data/po/fr/rbot.po +1133 -0
- data/po/it/rbot-alias.po +82 -0
- data/po/it/rbot-autoop.po +0 -0
- data/po/it/rbot-autorejoin.po +0 -0
- data/po/it/rbot-azgame.po +186 -0
- data/po/it/rbot-bans.po +0 -0
- data/po/it/rbot-bash.po +0 -0
- data/po/it/rbot-botsnack.po +0 -0
- data/po/it/rbot-cal.po +3 -0
- data/po/it/rbot-chanserv.po +0 -0
- data/po/it/rbot-chucknorris.po +0 -0
- data/po/it/rbot-debugger.po +0 -0
- data/po/it/rbot-deepthoughts.po +0 -0
- data/po/it/rbot-delicious.po +0 -0
- data/po/it/rbot-dice.po +0 -0
- data/po/it/rbot-dict.po +0 -0
- data/po/it/rbot-dictclient.po +111 -0
- data/po/it/rbot-digg.po +0 -0
- data/po/it/rbot-eightball.po +0 -0
- data/po/it/rbot-excuse.po +0 -0
- data/po/it/rbot-factoids.po +110 -0
- data/po/it/rbot-figlet.po +36 -0
- data/po/it/rbot-fish.po +0 -0
- data/po/it/rbot-forecast.po +0 -0
- data/po/it/rbot-fortune.po +0 -0
- data/po/it/rbot-freshmeat.po +0 -0
- data/po/it/rbot-grouphug.po +18 -0
- data/po/it/rbot-hl2.po +0 -0
- data/po/it/rbot-host.po +3 -0
- data/po/it/rbot-imdb.po +0 -0
- data/po/it/rbot-insult.po +0 -0
- data/po/it/rbot-iplookup.po +0 -0
- data/po/it/rbot-karma.po +0 -0
- data/po/it/rbot-keywords.po +24 -0
- data/po/it/rbot-lart.po +0 -0
- data/po/it/rbot-lastfm.po +172 -0
- data/po/it/rbot-linkbot.po +0 -0
- data/po/it/rbot-markov.po +20 -0
- data/po/it/rbot-math.po +0 -0
- data/po/it/rbot-modes.po +0 -0
- data/po/it/rbot-nickrecover.po +36 -0
- data/po/it/rbot-nickserv.po +104 -0
- data/po/it/rbot-nslookup.po +0 -0
- data/po/it/rbot-quakeauth.po +0 -0
- data/po/it/rbot-quiz.po +0 -0
- data/po/it/rbot-quotes.po +108 -0
- data/po/it/rbot-reaction.po +0 -0
- data/po/it/rbot-remind.po +0 -0
- data/po/it/rbot-remotectl.po +0 -0
- data/po/it/rbot-ri.po +0 -0
- data/po/it/rbot-roshambo.po +0 -0
- data/po/it/rbot-rot13.po +0 -0
- data/po/it/rbot-roulette.po +0 -0
- data/po/it/rbot-rss.po +20 -0
- data/po/it/rbot-salut.po +0 -0
- data/po/it/rbot-script.po +0 -0
- data/po/it/rbot-search.po +0 -0
- data/po/it/rbot-seen.po +0 -0
- data/po/it/rbot-shiritori.po +102 -0
- data/po/it/rbot-shortenurls.po +0 -0
- data/po/it/rbot-slashdot.po +0 -0
- data/po/it/rbot-spell.po +54 -0
- data/po/it/rbot-theyfightcrime.po +0 -0
- data/po/it/rbot-threat.po +0 -0
- data/po/it/rbot-time.po +0 -0
- data/po/it/rbot-topic.po +0 -0
- data/po/it/rbot-translator.po +77 -0
- data/po/it/rbot-tube.po +0 -0
- data/po/it/rbot-twitter.po +24 -0
- data/po/it/rbot-uno.po +564 -0
- data/po/it/rbot-urban.po +0 -0
- data/po/it/rbot-url.po +0 -0
- data/po/it/rbot-usermodes.po +0 -0
- data/po/it/rbot-wall.po +33 -0
- data/po/it/rbot-weather.po +0 -0
- data/po/it/rbot-wheelfortune.po +251 -0
- data/po/it/rbot-wow.po +0 -0
- data/po/it/rbot-wserver.po +0 -0
- data/po/it/rbot-youtube.po +64 -0
- data/po/it/rbot.po +1127 -0
- data/po/ja/rbot-alias.po +82 -0
- data/po/ja/rbot-autoop.po +0 -0
- data/po/ja/rbot-autorejoin.po +0 -0
- data/po/ja/rbot-azgame.po +194 -0
- data/po/ja/rbot-bans.po +0 -0
- data/po/ja/rbot-bash.po +0 -0
- data/po/ja/rbot-botsnack.po +0 -0
- data/po/ja/rbot-cal.po +3 -0
- data/po/ja/rbot-chanserv.po +0 -0
- data/po/ja/rbot-chucknorris.po +0 -0
- data/po/ja/rbot-debugger.po +0 -0
- data/po/ja/rbot-deepthoughts.po +0 -0
- data/po/ja/rbot-delicious.po +0 -0
- data/po/ja/rbot-dice.po +0 -0
- data/po/ja/rbot-dict.po +0 -0
- data/po/ja/rbot-dictclient.po +111 -0
- data/po/ja/rbot-digg.po +0 -0
- data/po/ja/rbot-eightball.po +0 -0
- data/po/ja/rbot-excuse.po +0 -0
- data/po/ja/rbot-factoids.po +107 -0
- data/po/ja/rbot-figlet.po +36 -0
- data/po/ja/rbot-fish.po +0 -0
- data/po/ja/rbot-forecast.po +0 -0
- data/po/ja/rbot-fortune.po +0 -0
- data/po/ja/rbot-freshmeat.po +0 -0
- data/po/ja/rbot-grouphug.po +18 -0
- data/po/ja/rbot-hl2.po +0 -0
- data/po/ja/rbot-host.po +3 -0
- data/po/ja/rbot-imdb.po +0 -0
- data/po/ja/rbot-insult.po +0 -0
- data/po/ja/rbot-iplookup.po +0 -0
- data/po/ja/rbot-karma.po +0 -0
- data/po/ja/rbot-keywords.po +24 -0
- data/po/ja/rbot-lart.po +0 -0
- data/po/ja/rbot-lastfm.po +172 -0
- data/po/ja/rbot-linkbot.po +0 -0
- data/po/ja/rbot-markov.po +20 -0
- data/po/ja/rbot-math.po +0 -0
- data/po/ja/rbot-modes.po +0 -0
- data/po/ja/rbot-nickrecover.po +36 -0
- data/po/ja/rbot-nickserv.po +104 -0
- data/po/ja/rbot-nslookup.po +0 -0
- data/po/ja/rbot-quakeauth.po +0 -0
- data/po/ja/rbot-quiz.po +0 -0
- data/po/ja/rbot-quotes.po +108 -0
- data/po/ja/rbot-reaction.po +0 -0
- data/po/ja/rbot-remind.po +0 -0
- data/po/ja/rbot-remotectl.po +0 -0
- data/po/ja/rbot-ri.po +0 -0
- data/po/ja/rbot-roshambo.po +0 -0
- data/po/ja/rbot-rot13.po +0 -0
- data/po/ja/rbot-roulette.po +0 -0
- data/po/ja/rbot-rss.po +20 -0
- data/po/ja/rbot-salut.po +0 -0
- data/po/ja/rbot-script.po +0 -0
- data/po/ja/rbot-search.po +0 -0
- data/po/ja/rbot-seen.po +0 -0
- data/po/ja/rbot-shiritori.po +115 -0
- data/po/ja/rbot-shortenurls.po +0 -0
- data/po/ja/rbot-slashdot.po +0 -0
- data/po/ja/rbot-spell.po +54 -0
- data/po/ja/rbot-theyfightcrime.po +0 -0
- data/po/ja/rbot-threat.po +0 -0
- data/po/ja/rbot-time.po +0 -0
- data/po/ja/rbot-topic.po +0 -0
- data/po/ja/rbot-translator.po +77 -0
- data/po/ja/rbot-tube.po +0 -0
- data/po/ja/rbot-twitter.po +24 -0
- data/po/ja/rbot-uno.po +512 -0
- data/po/ja/rbot-urban.po +0 -0
- data/po/ja/rbot-url.po +0 -0
- data/po/ja/rbot-usermodes.po +0 -0
- data/po/ja/rbot-wall.po +33 -0
- data/po/ja/rbot-weather.po +0 -0
- data/po/ja/rbot-wheelfortune.po +205 -0
- data/po/ja/rbot-wow.po +0 -0
- data/po/ja/rbot-wserver.po +0 -0
- data/po/ja/rbot-youtube.po +58 -0
- data/po/ja/rbot.po +999 -0
- data/po/rbot-alias.pot +83 -0
- data/po/rbot-autoop.pot +0 -0
- data/po/rbot-autorejoin.pot +0 -0
- data/po/rbot-azgame.pot +187 -0
- data/po/rbot-bans.pot +0 -0
- data/po/rbot-bash.pot +0 -0
- data/po/rbot-botsnack.pot +0 -0
- data/po/rbot-cal.pot +21 -0
- data/po/rbot-chanserv.pot +0 -0
- data/po/rbot-chucknorris.pot +0 -0
- data/po/rbot-debugger.pot +0 -0
- data/po/rbot-deepthoughts.pot +0 -0
- data/po/rbot-delicious.pot +0 -0
- data/po/rbot-dice.pot +0 -0
- data/po/rbot-dict.pot +0 -0
- data/po/rbot-dictclient.pot +112 -0
- data/po/rbot-digg.pot +0 -0
- data/po/rbot-eightball.pot +0 -0
- data/po/rbot-excuse.pot +0 -0
- data/po/rbot-factoids.pot +108 -0
- data/po/rbot-figlet.pot +37 -0
- data/po/rbot-fish.pot +0 -0
- data/po/rbot-forecast.pot +0 -0
- data/po/rbot-fortune.pot +0 -0
- data/po/rbot-freshmeat.pot +0 -0
- data/po/rbot-grouphug.pot +36 -0
- data/po/rbot-hl2.pot +0 -0
- data/po/rbot-host.pot +21 -0
- data/po/rbot-imdb.pot +0 -0
- data/po/rbot-insult.pot +0 -0
- data/po/rbot-iplookup.pot +0 -0
- data/po/rbot-karma.pot +0 -0
- data/po/rbot-keywords.pot +25 -0
- data/po/rbot-lart.pot +0 -0
- data/po/rbot-lastfm.pot +190 -0
- data/po/rbot-linkbot.pot +0 -0
- data/po/rbot-markov.pot +21 -0
- data/po/rbot-math.pot +0 -0
- data/po/rbot-modes.pot +0 -0
- data/po/rbot-nickrecover.pot +37 -0
- data/po/rbot-nickserv.pot +105 -0
- data/po/rbot-nslookup.pot +0 -0
- data/po/rbot-quakeauth.pot +0 -0
- data/po/rbot-quiz.pot +0 -0
- data/po/rbot-quotes.pot +109 -0
- data/po/rbot-reaction.pot +0 -0
- data/po/rbot-remind.pot +0 -0
- data/po/rbot-remotectl.pot +0 -0
- data/po/rbot-ri.pot +0 -0
- data/po/rbot-roshambo.pot +0 -0
- data/po/rbot-rot13.pot +0 -0
- data/po/rbot-roulette.pot +0 -0
- data/po/rbot-rss.pot +21 -0
- data/po/rbot-salut.pot +0 -0
- data/po/rbot-script.pot +0 -0
- data/po/rbot-search.pot +0 -0
- data/po/rbot-seen.pot +0 -0
- data/po/rbot-shiritori.pot +103 -0
- data/po/rbot-shortenurls.pot +0 -0
- data/po/rbot-slashdot.pot +0 -0
- data/po/rbot-spell.pot +55 -0
- data/po/rbot-theyfightcrime.pot +0 -0
- data/po/rbot-threat.pot +0 -0
- data/po/rbot-time.pot +0 -0
- data/po/rbot-topic.pot +0 -0
- data/po/rbot-translator.pot +78 -0
- data/po/rbot-tube.pot +0 -0
- data/po/rbot-twitter.pot +25 -0
- data/po/rbot-uno.pot +513 -0
- data/po/rbot-urban.pot +0 -0
- data/po/rbot-url.pot +0 -0
- data/po/rbot-usermodes.pot +0 -0
- data/po/rbot-wall.pot +33 -0
- data/po/rbot-weather.pot +0 -0
- data/po/rbot-wheelfortune.pot +206 -0
- data/po/rbot-wow.pot +0 -0
- data/po/rbot-wserver.pot +0 -0
- data/po/rbot-youtube.pot +59 -0
- data/po/rbot.pot +1004 -0
- data/po/zh_CN/rbot-alias.po +82 -0
- data/po/zh_CN/rbot-autoop.po +0 -0
- data/po/zh_CN/rbot-autorejoin.po +0 -0
- data/po/zh_CN/rbot-azgame.po +186 -0
- data/po/zh_CN/rbot-bans.po +0 -0
- data/po/zh_CN/rbot-bash.po +0 -0
- data/po/zh_CN/rbot-botsnack.po +0 -0
- data/po/zh_CN/rbot-cal.po +3 -0
- data/po/zh_CN/rbot-chanserv.po +0 -0
- data/po/zh_CN/rbot-chucknorris.po +0 -0
- data/po/zh_CN/rbot-debugger.po +0 -0
- data/po/zh_CN/rbot-deepthoughts.po +0 -0
- data/po/zh_CN/rbot-delicious.po +0 -0
- data/po/zh_CN/rbot-dice.po +0 -0
- data/po/zh_CN/rbot-dict.po +0 -0
- data/po/zh_CN/rbot-dictclient.po +111 -0
- data/po/zh_CN/rbot-digg.po +0 -0
- data/po/zh_CN/rbot-eightball.po +0 -0
- data/po/zh_CN/rbot-excuse.po +0 -0
- data/po/zh_CN/rbot-factoids.po +107 -0
- data/po/zh_CN/rbot-figlet.po +36 -0
- data/po/zh_CN/rbot-fish.po +0 -0
- data/po/zh_CN/rbot-forecast.po +0 -0
- data/po/zh_CN/rbot-fortune.po +0 -0
- data/po/zh_CN/rbot-freshmeat.po +0 -0
- data/po/zh_CN/rbot-grouphug.po +18 -0
- data/po/zh_CN/rbot-hl2.po +0 -0
- data/po/zh_CN/rbot-host.po +3 -0
- data/po/zh_CN/rbot-imdb.po +0 -0
- data/po/zh_CN/rbot-insult.po +0 -0
- data/po/zh_CN/rbot-iplookup.po +0 -0
- data/po/zh_CN/rbot-karma.po +0 -0
- data/po/zh_CN/rbot-keywords.po +24 -0
- data/po/zh_CN/rbot-lart.po +0 -0
- data/po/zh_CN/rbot-lastfm.po +172 -0
- data/po/zh_CN/rbot-linkbot.po +0 -0
- data/po/zh_CN/rbot-markov.po +20 -0
- data/po/zh_CN/rbot-math.po +0 -0
- data/po/zh_CN/rbot-modes.po +0 -0
- data/po/zh_CN/rbot-nickrecover.po +36 -0
- data/po/zh_CN/rbot-nickserv.po +104 -0
- data/po/zh_CN/rbot-nslookup.po +0 -0
- data/po/zh_CN/rbot-quakeauth.po +0 -0
- data/po/zh_CN/rbot-quiz.po +0 -0
- data/po/zh_CN/rbot-quotes.po +108 -0
- data/po/zh_CN/rbot-reaction.po +0 -0
- data/po/zh_CN/rbot-remind.po +0 -0
- data/po/zh_CN/rbot-remotectl.po +0 -0
- data/po/zh_CN/rbot-ri.po +0 -0
- data/po/zh_CN/rbot-roshambo.po +0 -0
- data/po/zh_CN/rbot-rot13.po +0 -0
- data/po/zh_CN/rbot-roulette.po +0 -0
- data/po/zh_CN/rbot-rss.po +20 -0
- data/po/zh_CN/rbot-salut.po +0 -0
- data/po/zh_CN/rbot-script.po +0 -0
- data/po/zh_CN/rbot-search.po +0 -0
- data/po/zh_CN/rbot-seen.po +0 -0
- data/po/zh_CN/rbot-shiritori.po +102 -0
- data/po/zh_CN/rbot-shortenurls.po +0 -0
- data/po/zh_CN/rbot-slashdot.po +0 -0
- data/po/zh_CN/rbot-spell.po +54 -0
- data/po/zh_CN/rbot-theyfightcrime.po +0 -0
- data/po/zh_CN/rbot-threat.po +0 -0
- data/po/zh_CN/rbot-time.po +0 -0
- data/po/zh_CN/rbot-topic.po +0 -0
- data/po/zh_CN/rbot-translator.po +77 -0
- data/po/zh_CN/rbot-tube.po +0 -0
- data/po/zh_CN/rbot-twitter.po +24 -0
- data/po/zh_CN/rbot-uno.po +512 -0
- data/po/zh_CN/rbot-urban.po +0 -0
- data/po/zh_CN/rbot-url.po +0 -0
- data/po/zh_CN/rbot-usermodes.po +0 -0
- data/po/zh_CN/rbot-wall.po +33 -0
- data/po/zh_CN/rbot-weather.po +0 -0
- data/po/zh_CN/rbot-wheelfortune.po +205 -0
- data/po/zh_CN/rbot-wow.po +0 -0
- data/po/zh_CN/rbot-wserver.po +0 -0
- data/po/zh_CN/rbot-youtube.po +58 -0
- data/po/zh_CN/rbot.po +1006 -0
- data/po/zh_TW/rbot-alias.po +82 -0
- data/po/zh_TW/rbot-autoop.po +0 -0
- data/po/zh_TW/rbot-autorejoin.po +0 -0
- data/po/zh_TW/rbot-azgame.po +186 -0
- data/po/zh_TW/rbot-bans.po +0 -0
- data/po/zh_TW/rbot-bash.po +0 -0
- data/po/zh_TW/rbot-botsnack.po +0 -0
- data/po/zh_TW/rbot-cal.po +3 -0
- data/po/zh_TW/rbot-chanserv.po +0 -0
- data/po/zh_TW/rbot-chucknorris.po +0 -0
- data/po/zh_TW/rbot-debugger.po +0 -0
- data/po/zh_TW/rbot-deepthoughts.po +0 -0
- data/po/zh_TW/rbot-delicious.po +0 -0
- data/po/zh_TW/rbot-dice.po +0 -0
- data/po/zh_TW/rbot-dict.po +0 -0
- data/po/zh_TW/rbot-dictclient.po +111 -0
- data/po/zh_TW/rbot-digg.po +0 -0
- data/po/zh_TW/rbot-eightball.po +0 -0
- data/po/zh_TW/rbot-excuse.po +0 -0
- data/po/zh_TW/rbot-factoids.po +107 -0
- data/po/zh_TW/rbot-figlet.po +36 -0
- data/po/zh_TW/rbot-fish.po +0 -0
- data/po/zh_TW/rbot-forecast.po +0 -0
- data/po/zh_TW/rbot-fortune.po +0 -0
- data/po/zh_TW/rbot-freshmeat.po +0 -0
- data/po/zh_TW/rbot-grouphug.po +18 -0
- data/po/zh_TW/rbot-hl2.po +0 -0
- data/po/zh_TW/rbot-host.po +3 -0
- data/po/zh_TW/rbot-imdb.po +0 -0
- data/po/zh_TW/rbot-insult.po +0 -0
- data/po/zh_TW/rbot-iplookup.po +0 -0
- data/po/zh_TW/rbot-karma.po +0 -0
- data/po/zh_TW/rbot-keywords.po +24 -0
- data/po/zh_TW/rbot-lart.po +0 -0
- data/po/zh_TW/rbot-lastfm.po +172 -0
- data/po/zh_TW/rbot-linkbot.po +0 -0
- data/po/zh_TW/rbot-markov.po +20 -0
- data/po/zh_TW/rbot-math.po +0 -0
- data/po/zh_TW/rbot-modes.po +0 -0
- data/po/zh_TW/rbot-nickrecover.po +36 -0
- data/po/zh_TW/rbot-nickserv.po +104 -0
- data/po/zh_TW/rbot-nslookup.po +0 -0
- data/po/zh_TW/rbot-quakeauth.po +0 -0
- data/po/zh_TW/rbot-quiz.po +0 -0
- data/po/zh_TW/rbot-quotes.po +108 -0
- data/po/zh_TW/rbot-reaction.po +0 -0
- data/po/zh_TW/rbot-remind.po +0 -0
- data/po/zh_TW/rbot-remotectl.po +0 -0
- data/po/zh_TW/rbot-ri.po +0 -0
- data/po/zh_TW/rbot-roshambo.po +0 -0
- data/po/zh_TW/rbot-rot13.po +0 -0
- data/po/zh_TW/rbot-roulette.po +0 -0
- data/po/zh_TW/rbot-rss.po +20 -0
- data/po/zh_TW/rbot-salut.po +0 -0
- data/po/zh_TW/rbot-script.po +0 -0
- data/po/zh_TW/rbot-search.po +0 -0
- data/po/zh_TW/rbot-seen.po +0 -0
- data/po/zh_TW/rbot-shiritori.po +102 -0
- data/po/zh_TW/rbot-shortenurls.po +0 -0
- data/po/zh_TW/rbot-slashdot.po +0 -0
- data/po/zh_TW/rbot-spell.po +54 -0
- data/po/zh_TW/rbot-theyfightcrime.po +0 -0
- data/po/zh_TW/rbot-threat.po +0 -0
- data/po/zh_TW/rbot-time.po +0 -0
- data/po/zh_TW/rbot-topic.po +0 -0
- data/po/zh_TW/rbot-translator.po +77 -0
- data/po/zh_TW/rbot-tube.po +0 -0
- data/po/zh_TW/rbot-twitter.po +24 -0
- data/po/zh_TW/rbot-uno.po +512 -0
- data/po/zh_TW/rbot-urban.po +0 -0
- data/po/zh_TW/rbot-url.po +0 -0
- data/po/zh_TW/rbot-usermodes.po +0 -0
- data/po/zh_TW/rbot-wall.po +33 -0
- data/po/zh_TW/rbot-weather.po +0 -0
- data/po/zh_TW/rbot-wheelfortune.po +205 -0
- data/po/zh_TW/rbot-wow.po +0 -0
- data/po/zh_TW/rbot-wserver.po +0 -0
- data/po/zh_TW/rbot-youtube.po +58 -0
- data/po/zh_TW/rbot.po +1034 -0
- data/setup.rb +800 -574
- metadata +723 -108
- data/data/rbot/contrib/plugins/figlet.rb +0 -20
- data/data/rbot/contrib/plugins/ri.rb +0 -83
- data/data/rbot/plugins/demauro.rb +0 -95
- data/data/rbot/plugins/google.rb +0 -53
- data/data/rbot/plugins/opme.rb +0 -19
- data/data/rbot/plugins/roshambo.rb +0 -54
- data/data/rbot/plugins/rubyurl.rb +0 -39
- data/data/rbot/plugins/tinyurl.rb +0 -39
- data/data/rbot/plugins/xmlrpc.rb.disabled +0 -52
- data/data/rbot/templates/levels.rbot +0 -32
- data/data/rbot/templates/users.rbot +0 -1
- data/lib/rbot/auth.rb +0 -314
- data/lib/rbot/channel.rb +0 -54
- data/lib/rbot/httputil.rb +0 -309
- data/lib/rbot/utils.rb +0 -83
@@ -0,0 +1,1205 @@
|
|
1
|
+
#-- vim:sw=2:et
|
2
|
+
#++
|
3
|
+
#
|
4
|
+
# :title: Uno Game Plugin for rbot
|
5
|
+
#
|
6
|
+
# Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
|
7
|
+
#
|
8
|
+
# Copyright:: (C) 2008 Giuseppe Bilotta
|
9
|
+
#
|
10
|
+
# License:: GPL v2
|
11
|
+
#
|
12
|
+
# Uno Game: get rid of the cards you have
|
13
|
+
#
|
14
|
+
# TODO documentation
|
15
|
+
# TODO allow full form card names for play
|
16
|
+
# TODO allow choice of rules re stacking + and playing Reverse with them
|
17
|
+
|
18
|
+
class UnoGame
|
19
|
+
COLORS = %w{Red Green Blue Yellow}
|
20
|
+
SPECIALS = %w{+2 Reverse Skip}
|
21
|
+
NUMERICS = (0..9).to_a
|
22
|
+
VALUES = NUMERICS + SPECIALS
|
23
|
+
|
24
|
+
def UnoGame.color_map(clr)
|
25
|
+
case clr
|
26
|
+
when 'Red'
|
27
|
+
:red
|
28
|
+
when 'Blue'
|
29
|
+
:royal_blue
|
30
|
+
when 'Green'
|
31
|
+
:limegreen
|
32
|
+
when 'Yellow'
|
33
|
+
:yellow
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def UnoGame.irc_color_bg(clr)
|
38
|
+
Irc.color([:white,:black][COLORS.index(clr)%2],UnoGame.color_map(clr))
|
39
|
+
end
|
40
|
+
|
41
|
+
def UnoGame.irc_color_fg(clr)
|
42
|
+
Irc.color(UnoGame.color_map(clr))
|
43
|
+
end
|
44
|
+
|
45
|
+
def UnoGame.colorify(str, fg=false)
|
46
|
+
ret = Bold.dup
|
47
|
+
str.length.times do |i|
|
48
|
+
ret << (fg ?
|
49
|
+
UnoGame.irc_color_fg(COLORS[i%4]) :
|
50
|
+
UnoGame.irc_color_bg(COLORS[i%4]) ) +str[i,1]
|
51
|
+
end
|
52
|
+
ret << NormalText
|
53
|
+
end
|
54
|
+
|
55
|
+
UNO = UnoGame.colorify('UNO!', true)
|
56
|
+
|
57
|
+
# Colored play cards
|
58
|
+
class Card
|
59
|
+
attr_reader :color
|
60
|
+
attr_reader :value
|
61
|
+
attr_reader :shortform
|
62
|
+
attr_reader :to_s
|
63
|
+
attr_reader :score
|
64
|
+
|
65
|
+
def initialize(color, value)
|
66
|
+
raise unless COLORS.include? color
|
67
|
+
@color = color.dup
|
68
|
+
raise unless VALUES.include? value
|
69
|
+
if NUMERICS.include? value
|
70
|
+
@value = value
|
71
|
+
@score = value
|
72
|
+
else
|
73
|
+
@value = value.dup
|
74
|
+
@score = 20
|
75
|
+
end
|
76
|
+
if @value == '+2'
|
77
|
+
@shortform = (@color[0,1]+@value).downcase
|
78
|
+
else
|
79
|
+
@shortform = (@color[0,1]+@value.to_s[0,1]).downcase
|
80
|
+
end
|
81
|
+
@to_s = UnoGame.irc_color_bg(@color) +
|
82
|
+
Bold + ['', @color, @value, ''].join(' ') + NormalText
|
83
|
+
end
|
84
|
+
|
85
|
+
def picker
|
86
|
+
return 0 unless @value.to_s[0,1] == '+'
|
87
|
+
return @value[1,1].to_i
|
88
|
+
end
|
89
|
+
|
90
|
+
def special?
|
91
|
+
SPECIALS.include?(@value)
|
92
|
+
end
|
93
|
+
|
94
|
+
def <=>(other)
|
95
|
+
cc = self.color <=> other.color
|
96
|
+
if cc == 0
|
97
|
+
return self.value.to_s <=> other.value.to_s
|
98
|
+
else
|
99
|
+
return cc
|
100
|
+
end
|
101
|
+
end
|
102
|
+
include Comparable
|
103
|
+
end
|
104
|
+
|
105
|
+
# Wild, Wild +4 cards
|
106
|
+
class Wild < Card
|
107
|
+
def initialize(value=nil)
|
108
|
+
@color = 'Wild'
|
109
|
+
raise if value and not value == '+4'
|
110
|
+
if value
|
111
|
+
@value = value.dup
|
112
|
+
@shortform = 'w'+value
|
113
|
+
else
|
114
|
+
@value = nil
|
115
|
+
@shortform = 'w'
|
116
|
+
end
|
117
|
+
@score = 50
|
118
|
+
@to_s = UnoGame.colorify(['', @color, @value, ''].compact.join(' '))
|
119
|
+
end
|
120
|
+
def special?
|
121
|
+
@value
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class Player
|
126
|
+
attr_accessor :cards
|
127
|
+
attr_accessor :user
|
128
|
+
def initialize(user)
|
129
|
+
@user = user
|
130
|
+
@cards = []
|
131
|
+
end
|
132
|
+
def has_card?(short)
|
133
|
+
has = []
|
134
|
+
@cards.each { |c|
|
135
|
+
has << c if c.shortform == short
|
136
|
+
}
|
137
|
+
if has.empty?
|
138
|
+
return false
|
139
|
+
else
|
140
|
+
return has
|
141
|
+
end
|
142
|
+
end
|
143
|
+
def to_s
|
144
|
+
Bold + @user.to_s + Bold
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# cards in stock
|
149
|
+
attr_reader :stock
|
150
|
+
# current discard
|
151
|
+
attr_reader :discard
|
152
|
+
# previous discard, in case of challenge
|
153
|
+
attr_reader :last_discard
|
154
|
+
# channel the game is played in
|
155
|
+
attr_reader :channel
|
156
|
+
# list of players
|
157
|
+
attr :players
|
158
|
+
# true if the player picked a card (and can thus pass turn)
|
159
|
+
attr_reader :player_has_picked
|
160
|
+
# number of cards to be picked if the player can't play an appropriate card
|
161
|
+
attr_reader :picker
|
162
|
+
|
163
|
+
# game start time
|
164
|
+
attr :start_time
|
165
|
+
|
166
|
+
# the IRC user that created the game
|
167
|
+
attr_accessor :manager
|
168
|
+
|
169
|
+
def initialize(plugin, channel, manager)
|
170
|
+
@channel = channel
|
171
|
+
@plugin = plugin
|
172
|
+
@bot = plugin.bot
|
173
|
+
@players = []
|
174
|
+
@dropouts = []
|
175
|
+
@discard = nil
|
176
|
+
@last_discard = nil
|
177
|
+
@value = nil
|
178
|
+
@color = nil
|
179
|
+
make_base_stock
|
180
|
+
@stock = []
|
181
|
+
make_stock
|
182
|
+
@start_time = nil
|
183
|
+
@join_timer = nil
|
184
|
+
@picker = 0
|
185
|
+
@last_picker = 0
|
186
|
+
@must_play = nil
|
187
|
+
@manager = manager
|
188
|
+
end
|
189
|
+
|
190
|
+
def get_player(user)
|
191
|
+
case user
|
192
|
+
when User
|
193
|
+
@players.each do |p|
|
194
|
+
return p if p.user == user
|
195
|
+
end
|
196
|
+
when String
|
197
|
+
@players.each do |p|
|
198
|
+
return p if p.user.irc_downcase == user.irc_downcase(channel.casemap)
|
199
|
+
end
|
200
|
+
else
|
201
|
+
get_player(user.to_s)
|
202
|
+
end
|
203
|
+
return nil
|
204
|
+
end
|
205
|
+
|
206
|
+
def announce(msg, opts={})
|
207
|
+
@bot.say channel, msg, opts
|
208
|
+
end
|
209
|
+
|
210
|
+
def notify(player, msg, opts={})
|
211
|
+
@bot.notice player.user, msg, opts
|
212
|
+
end
|
213
|
+
|
214
|
+
def make_base_stock
|
215
|
+
@base_stock = COLORS.inject([]) do |list, clr|
|
216
|
+
VALUES.each do |n|
|
217
|
+
list << Card.new(clr, n)
|
218
|
+
list << Card.new(clr, n) unless n == 0
|
219
|
+
end
|
220
|
+
list
|
221
|
+
end
|
222
|
+
4.times do
|
223
|
+
@base_stock << Wild.new
|
224
|
+
@base_stock << Wild.new('+4')
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def make_stock
|
229
|
+
@stock.replace @base_stock
|
230
|
+
# remove the cards in the players hand
|
231
|
+
@players.each { |p| p.cards.each { |c| @stock.delete_one c } }
|
232
|
+
# remove current top discarded card if present
|
233
|
+
if @discard
|
234
|
+
@stock.delete_one(discard)
|
235
|
+
end
|
236
|
+
@stock.shuffle!
|
237
|
+
end
|
238
|
+
|
239
|
+
def start_game
|
240
|
+
debug "Starting game"
|
241
|
+
@players.shuffle!
|
242
|
+
show_order
|
243
|
+
announce _("%{p} deals the first card from the stock") % {
|
244
|
+
:p => @players.first
|
245
|
+
}
|
246
|
+
card = @stock.shift
|
247
|
+
@picker = 0
|
248
|
+
@special = false
|
249
|
+
while Wild === card do
|
250
|
+
@stock.insert(rand(@stock.length), card)
|
251
|
+
card = @stock.shift
|
252
|
+
end
|
253
|
+
set_discard(card)
|
254
|
+
show_discard
|
255
|
+
if @special
|
256
|
+
do_special
|
257
|
+
end
|
258
|
+
next_turn
|
259
|
+
@start_time = Time.now
|
260
|
+
end
|
261
|
+
|
262
|
+
def elapsed_time
|
263
|
+
if @start_time
|
264
|
+
Utils.secs_to_string(Time.now-@start_time)
|
265
|
+
else
|
266
|
+
_("no time")
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def reverse_turn
|
271
|
+
# if there are two players, the Reverse acts like a Skip, unless
|
272
|
+
# there's a @picker running, in which case the Reverse should bounce the
|
273
|
+
# pick on the other player
|
274
|
+
if @players.length > 2
|
275
|
+
@players.reverse!
|
276
|
+
# put the current player back in its place
|
277
|
+
@players.unshift @players.pop
|
278
|
+
announce _("Playing order was reversed!")
|
279
|
+
elsif @picker > 0
|
280
|
+
announce _("%{cp} bounces the pick to %{np}") % {
|
281
|
+
:cp => @players.first,
|
282
|
+
:np => @players.last
|
283
|
+
}
|
284
|
+
else
|
285
|
+
skip_turn
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def skip_turn
|
290
|
+
@players << @players.shift
|
291
|
+
announce _("%{p} skips a turn!") % {
|
292
|
+
# this is first and not last because the actual
|
293
|
+
# turn change will be done by the following next_turn
|
294
|
+
:p => @players.first
|
295
|
+
}
|
296
|
+
end
|
297
|
+
|
298
|
+
def do_special
|
299
|
+
case @discard.value
|
300
|
+
when 'Reverse'
|
301
|
+
reverse_turn
|
302
|
+
@special = false
|
303
|
+
when 'Skip'
|
304
|
+
skip_turn
|
305
|
+
@special = false
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def set_discard(card)
|
310
|
+
@discard = card
|
311
|
+
@value = card.value.dup rescue card.value
|
312
|
+
if Wild === card
|
313
|
+
@color = nil
|
314
|
+
else
|
315
|
+
@color = card.color.dup
|
316
|
+
end
|
317
|
+
if card.picker > 0
|
318
|
+
@picker += card.picker
|
319
|
+
@last_picker = @discard.picker
|
320
|
+
end
|
321
|
+
if card.special?
|
322
|
+
@special = true
|
323
|
+
else
|
324
|
+
@special = false
|
325
|
+
end
|
326
|
+
@must_play = nil
|
327
|
+
end
|
328
|
+
|
329
|
+
def next_turn(opts={})
|
330
|
+
@players << @players.shift
|
331
|
+
@player_has_picked = false
|
332
|
+
show_turn
|
333
|
+
end
|
334
|
+
|
335
|
+
def can_play(card)
|
336
|
+
# if play is forced, check against the only allowed cards
|
337
|
+
return false if @must_play and not @must_play.include?(card)
|
338
|
+
|
339
|
+
# When a +something is online, you can only play a +something of same or
|
340
|
+
# higher something, or a Reverse of the correct color, or a Reverse on
|
341
|
+
# a Reverse
|
342
|
+
# TODO make optional
|
343
|
+
if @picker > 0
|
344
|
+
return true if card.picker >= @last_picker
|
345
|
+
return true if card.value == 'Reverse' and (card.color == @color or @discard.value == card.value)
|
346
|
+
return false
|
347
|
+
else
|
348
|
+
# You can always play a Wild
|
349
|
+
return true if Wild === card
|
350
|
+
# On a Wild, you must match the color
|
351
|
+
if Wild === @discard
|
352
|
+
return card.color == @color
|
353
|
+
else
|
354
|
+
# Otherwise, you can match either the value or the color
|
355
|
+
return (card.value == @value) || (card.color == @color)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def play_card(source, cards)
|
361
|
+
debug "Playing card #{cards}"
|
362
|
+
p = get_player(source)
|
363
|
+
shorts = cards.gsub(/\s+/,'').match(/^(?:([rbgy]\+?\d){1,2}|([rbgy][rs])|(w(?:\+4)?)([rbgy])?)$/).to_a
|
364
|
+
debug shorts.inspect
|
365
|
+
if shorts.empty?
|
366
|
+
announce _("what cards were that again?")
|
367
|
+
return
|
368
|
+
end
|
369
|
+
full = shorts[0]
|
370
|
+
short = shorts[1] || shorts[2] || shorts[3]
|
371
|
+
jolly = shorts[3]
|
372
|
+
jcolor = shorts[4]
|
373
|
+
if jolly
|
374
|
+
toplay = 1
|
375
|
+
else
|
376
|
+
toplay = (full == short) ? 1 : 2
|
377
|
+
end
|
378
|
+
debug [full, short, jolly, jcolor, toplay].inspect
|
379
|
+
# r7r7 -> r7r7, r7, nil, nil
|
380
|
+
# r7 -> r7, r7, nil, nil
|
381
|
+
# w -> w, nil, w, nil
|
382
|
+
# wg -> wg, nil, w, g
|
383
|
+
if cards = p.has_card?(short)
|
384
|
+
debug cards
|
385
|
+
unless can_play(cards.first)
|
386
|
+
announce _("you can't play that card")
|
387
|
+
return
|
388
|
+
end
|
389
|
+
if cards.length >= toplay
|
390
|
+
# if the played card is a W+4 not played during a stacking +x
|
391
|
+
# TODO if A plays an illegal W+4, B plays a W+4, should the next
|
392
|
+
# player be able to challenge A? For the time being we say no,
|
393
|
+
# but I think he should, and in case A's move was illegal
|
394
|
+
# game would have to go back, A would get the penalty and replay,
|
395
|
+
# while if it was legal the challenger would get 50% more cards,
|
396
|
+
# i.e. 12 cards (or more if the stacked +4 were more). This would
|
397
|
+
# only be possible if the first W+4 was illegal, so it wouldn't
|
398
|
+
# apply for a W+4 played on a +2 anyway.
|
399
|
+
#
|
400
|
+
if @picker == 0 and Wild === cards.first and cards.first.value
|
401
|
+
# save the previous discard in case of challenge
|
402
|
+
@last_discard = @discard.dup
|
403
|
+
# save the color too, in case it was a Wild
|
404
|
+
@last_color = @color.dup
|
405
|
+
else
|
406
|
+
# mark the move as not challengeable
|
407
|
+
@last_discard = nil
|
408
|
+
@last_color = nil
|
409
|
+
end
|
410
|
+
set_discard(p.cards.delete_one(cards.shift))
|
411
|
+
if toplay > 1
|
412
|
+
set_discard(p.cards.delete_one(cards.shift))
|
413
|
+
announce _("%{p} plays %{card} twice!") % {
|
414
|
+
:p => p,
|
415
|
+
:card => @discard
|
416
|
+
}
|
417
|
+
else
|
418
|
+
announce _("%{p} plays %{card}") % { :p => p, :card => @discard }
|
419
|
+
end
|
420
|
+
if p.cards.length == 1
|
421
|
+
announce _("%{p} has %{uno}!") % {
|
422
|
+
:p => p, :uno => UNO
|
423
|
+
}
|
424
|
+
elsif p.cards.length == 0
|
425
|
+
end_game
|
426
|
+
return
|
427
|
+
end
|
428
|
+
show_picker
|
429
|
+
if @color
|
430
|
+
if @special
|
431
|
+
do_special
|
432
|
+
end
|
433
|
+
next_turn
|
434
|
+
elsif jcolor
|
435
|
+
choose_color(p.user, jcolor)
|
436
|
+
else
|
437
|
+
announce _("%{p}, choose a color with: co r|b|g|y") % { :p => p }
|
438
|
+
end
|
439
|
+
else
|
440
|
+
announce _("you don't have two cards of that kind")
|
441
|
+
end
|
442
|
+
else
|
443
|
+
announce _("you don't have that card")
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
def challenge
|
448
|
+
return unless @last_discard
|
449
|
+
# current player
|
450
|
+
cp = @players.first
|
451
|
+
# previous player
|
452
|
+
lp = @players.last
|
453
|
+
announce _("%{cp} challenges %{lp}'s %{card}!") % {
|
454
|
+
:cp => cp, :lp => lp, :card => @discard
|
455
|
+
}
|
456
|
+
# show the cards of the previous player to the current player
|
457
|
+
notify cp, _("%{p} has %{cards}") % {
|
458
|
+
:p => lp, :cards => lp.cards.join(' ')
|
459
|
+
}
|
460
|
+
# check if the previous player had a non-special card of the correct color
|
461
|
+
legal = true
|
462
|
+
lp.cards.each do |c|
|
463
|
+
if c.color == @last_color and not c.special?
|
464
|
+
legal = false
|
465
|
+
end
|
466
|
+
end
|
467
|
+
if legal
|
468
|
+
@picker += 2
|
469
|
+
announce _("%{lp}'s move was legal, %{cp} must pick %{b}%{n}%{b} cards!") % {
|
470
|
+
:cp => cp, :lp => lp, :b => Bold, :n => @picker
|
471
|
+
}
|
472
|
+
@last_color = nil
|
473
|
+
@last_discard = nil
|
474
|
+
deal(cp, @picker)
|
475
|
+
@picker = 0
|
476
|
+
next_turn
|
477
|
+
else
|
478
|
+
announce _("%{lp}'s move was %{b}not%{b} legal, %{lp} must pick %{b}%{n}%{b} cards and play again!") % {
|
479
|
+
:cp => cp, :lp => lp, :b => Bold, :n => @picker
|
480
|
+
}
|
481
|
+
lp.cards << @discard # put the W+4 back in place
|
482
|
+
|
483
|
+
# reset the discard
|
484
|
+
@color = @last_color.dup
|
485
|
+
@discard = @last_discard.dup
|
486
|
+
@special = false
|
487
|
+
@value = @discard.value.dup rescue @discard.value
|
488
|
+
@last_color = nil
|
489
|
+
@last_discard = nil
|
490
|
+
|
491
|
+
# force the player to play the current cards
|
492
|
+
@must_play = lp.cards.dup
|
493
|
+
|
494
|
+
# give him the penalty cards
|
495
|
+
deal(lp, @picker)
|
496
|
+
@picker = 0
|
497
|
+
|
498
|
+
# and restore the turn
|
499
|
+
@players.unshift @players.pop
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def pass(user)
|
504
|
+
p = get_player(user)
|
505
|
+
if @picker > 0
|
506
|
+
announce _("%{p} passes turn, and has to pick %{b}%{n}%{b} cards!") % {
|
507
|
+
:p => p, :b => Bold, :n => @picker
|
508
|
+
}
|
509
|
+
deal(p, @picker)
|
510
|
+
@picker = 0
|
511
|
+
else
|
512
|
+
if @player_has_picked
|
513
|
+
announce _("%{p} passes turn") % { :p => p }
|
514
|
+
else
|
515
|
+
announce _("you need to pick a card first")
|
516
|
+
return
|
517
|
+
end
|
518
|
+
end
|
519
|
+
next_turn
|
520
|
+
end
|
521
|
+
|
522
|
+
def choose_color(user, color)
|
523
|
+
# you can only pick a color if the current color is unset
|
524
|
+
if @color
|
525
|
+
announce _("you can't pick a color now, %{p}") % {
|
526
|
+
:p => get_player(user)
|
527
|
+
}
|
528
|
+
return
|
529
|
+
end
|
530
|
+
case color
|
531
|
+
when 'r'
|
532
|
+
@color = 'Red'
|
533
|
+
when 'b'
|
534
|
+
@color = 'Blue'
|
535
|
+
when 'g'
|
536
|
+
@color = 'Green'
|
537
|
+
when 'y'
|
538
|
+
@color = 'Yellow'
|
539
|
+
else
|
540
|
+
announce _('what color is that?')
|
541
|
+
return
|
542
|
+
end
|
543
|
+
announce _('color is now %{c}') % {
|
544
|
+
:c => UnoGame.irc_color_bg(@color)+" #{@color} "
|
545
|
+
}
|
546
|
+
next_turn
|
547
|
+
end
|
548
|
+
|
549
|
+
def show_time
|
550
|
+
if @start_time
|
551
|
+
announce _("This %{uno} game has been going on for %{time}") % {
|
552
|
+
:uno => UNO,
|
553
|
+
:time => elapsed_time
|
554
|
+
}
|
555
|
+
else
|
556
|
+
announce _("The game hasn't started yet")
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
def show_order
|
561
|
+
announce _("%{uno} playing turn: %{players}") % {
|
562
|
+
:uno => UNO, :players => players.join(' ')
|
563
|
+
}
|
564
|
+
end
|
565
|
+
|
566
|
+
def show_turn(opts={})
|
567
|
+
cards = true
|
568
|
+
cards = opts[:cards] if opts.key?(:cards)
|
569
|
+
player = @players.first
|
570
|
+
announce _("it's %{player}'s turn") % { :player => player }
|
571
|
+
show_user_cards(player) if cards
|
572
|
+
end
|
573
|
+
|
574
|
+
def has_turn?(source)
|
575
|
+
@start_time && (@players.first.user == source)
|
576
|
+
end
|
577
|
+
|
578
|
+
def show_picker
|
579
|
+
if @picker > 0
|
580
|
+
announce _("next player must respond correctly or pick %{b}%{n}%{b} cards") % {
|
581
|
+
:b => Bold, :n => @picker
|
582
|
+
}
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
def show_discard
|
587
|
+
announce _("Current discard: %{card} %{c}") % { :card => @discard,
|
588
|
+
:c => (Wild === @discard) ? UnoGame.irc_color_bg(@color) + " #{@color} " : nil
|
589
|
+
}
|
590
|
+
show_picker
|
591
|
+
end
|
592
|
+
|
593
|
+
def show_user_cards(player)
|
594
|
+
p = Player === player ? player : get_player(player)
|
595
|
+
return unless p
|
596
|
+
notify p, _('Your cards: %{cards}') % {
|
597
|
+
:cards => p.cards.join(' ')
|
598
|
+
}
|
599
|
+
end
|
600
|
+
|
601
|
+
def show_all_cards(u=nil)
|
602
|
+
announce(@players.inject([]) { |list, p|
|
603
|
+
list << [p, p.cards.length].join(': ')
|
604
|
+
}.join(', '))
|
605
|
+
if u
|
606
|
+
show_user_cards(u)
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
def pick_card(user)
|
611
|
+
p = get_player(user)
|
612
|
+
announce _("%{player} picks a card") % { :player => p }
|
613
|
+
deal(p, 1)
|
614
|
+
@player_has_picked = true
|
615
|
+
end
|
616
|
+
|
617
|
+
def deal(player, num=1)
|
618
|
+
picked = []
|
619
|
+
num.times do
|
620
|
+
picked << @stock.delete_one
|
621
|
+
if @stock.length == 0
|
622
|
+
announce _("Shuffling discarded cards")
|
623
|
+
make_stock
|
624
|
+
if @stock.length == 0
|
625
|
+
announce _("No more cards!")
|
626
|
+
end_game # FIXME nope!
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
picked.sort!
|
631
|
+
notify player, _("You picked %{picked}") % { :picked => picked.join(' ') }
|
632
|
+
player.cards += picked
|
633
|
+
player.cards.sort!
|
634
|
+
end
|
635
|
+
|
636
|
+
def add_player(user)
|
637
|
+
if p = get_player(user)
|
638
|
+
announce _("you're already in the game, %{p}") % {
|
639
|
+
:p => p
|
640
|
+
}
|
641
|
+
return
|
642
|
+
end
|
643
|
+
@dropouts.each do |dp|
|
644
|
+
if dp.user == user
|
645
|
+
announce _("you dropped from the game, %{p}, you can't get back in") % {
|
646
|
+
:p => dp
|
647
|
+
}
|
648
|
+
return
|
649
|
+
end
|
650
|
+
end
|
651
|
+
cards = 7
|
652
|
+
if @start_time
|
653
|
+
cards = (@players.inject(0) do |s, pl|
|
654
|
+
s +=pl.cards.length
|
655
|
+
end*1.0/@players.length).ceil
|
656
|
+
end
|
657
|
+
p = Player.new(user)
|
658
|
+
@players << p
|
659
|
+
announce _("%{p} joins this game of %{uno}") % {
|
660
|
+
:p => p, :uno => UNO
|
661
|
+
}
|
662
|
+
deal(p, cards)
|
663
|
+
return if @start_time
|
664
|
+
if @join_timer
|
665
|
+
@bot.timer.reschedule(@join_timer, 10)
|
666
|
+
elsif @players.length > 1
|
667
|
+
announce _("game will start in 20 seconds")
|
668
|
+
@join_timer = @bot.timer.add_once(20) {
|
669
|
+
start_game
|
670
|
+
}
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
def drop_player(nick)
|
675
|
+
# A nick is passed because the original player might have left
|
676
|
+
# the channel or IRC
|
677
|
+
unless p = get_player(nick)
|
678
|
+
announce _("%{p} isn't playing %{uno}") % {
|
679
|
+
:p => p, :uno => UNO
|
680
|
+
}
|
681
|
+
return
|
682
|
+
end
|
683
|
+
announce _("%{p} gives up this game of %{uno}") % {
|
684
|
+
:p => p, :uno => UNO
|
685
|
+
}
|
686
|
+
case @players.length
|
687
|
+
when 2
|
688
|
+
if p == @players.first
|
689
|
+
next_turn
|
690
|
+
end
|
691
|
+
end_game
|
692
|
+
return
|
693
|
+
when 1
|
694
|
+
end_game(true)
|
695
|
+
return
|
696
|
+
end
|
697
|
+
debug @stock.length
|
698
|
+
while p.cards.length > 0
|
699
|
+
@stock.insert(rand(@stock.length), p.cards.shift)
|
700
|
+
end
|
701
|
+
debug @stock.length
|
702
|
+
@dropouts << @players.delete_one(p)
|
703
|
+
end
|
704
|
+
|
705
|
+
def replace_player(old, new)
|
706
|
+
# The new user
|
707
|
+
user = channel.get_user(new)
|
708
|
+
if p = get_player(user)
|
709
|
+
announce _("%{p} is already playing %{uno} here") % {
|
710
|
+
:p => p, :uno => UNO
|
711
|
+
}
|
712
|
+
return
|
713
|
+
end
|
714
|
+
# We scan the player list of the player with the old nick, instead
|
715
|
+
# of using get_player, in case of IRC drops etc
|
716
|
+
@players.each do |p|
|
717
|
+
if p.user.nick == old
|
718
|
+
p.user = user
|
719
|
+
announce _("%{p} takes %{b}%{old}%{b}'s place at %{uno}") % {
|
720
|
+
:p => p, :b => Bold, :old => old, :uno => UNO
|
721
|
+
}
|
722
|
+
return
|
723
|
+
end
|
724
|
+
end
|
725
|
+
announce _("%{b}%{old}%{b} isn't playing %{uno} here") % {
|
726
|
+
:uno => UNO, :b => Bold, :old => old
|
727
|
+
}
|
728
|
+
end
|
729
|
+
|
730
|
+
def end_game(halted = false)
|
731
|
+
runtime = @start_time ? Time.now - @start_time : 0
|
732
|
+
if halted
|
733
|
+
if @start_time
|
734
|
+
announce _("%{uno} game halted after %{time}") % {
|
735
|
+
:time => elapsed_time,
|
736
|
+
:uno => UNO
|
737
|
+
}
|
738
|
+
else
|
739
|
+
announce _("%{uno} game halted before it could start") % {
|
740
|
+
:uno => UNO
|
741
|
+
}
|
742
|
+
end
|
743
|
+
else
|
744
|
+
announce _("%{uno} game finished after %{time}! The winner is %{p}") % {
|
745
|
+
:time => elapsed_time,
|
746
|
+
:uno => UNO, :p => @players.first
|
747
|
+
}
|
748
|
+
end
|
749
|
+
if @picker > 0 and not halted
|
750
|
+
p = @players[1]
|
751
|
+
announce _("%{p} has to pick %{b}%{n}%{b} cards!") % {
|
752
|
+
:p => p, :n => @picker, :b => Bold
|
753
|
+
}
|
754
|
+
deal(p, @picker)
|
755
|
+
@picker = 0
|
756
|
+
end
|
757
|
+
score = @players.inject(0) do |sum, p|
|
758
|
+
if p.cards.length > 0
|
759
|
+
announce _("%{p} still had %{cards}") % {
|
760
|
+
:p => p, :cards => p.cards.join(' ')
|
761
|
+
}
|
762
|
+
sum += p.cards.inject(0) do |cs, c|
|
763
|
+
cs += c.score
|
764
|
+
end
|
765
|
+
end
|
766
|
+
sum
|
767
|
+
end
|
768
|
+
|
769
|
+
closure = { :dropouts => @dropouts, :players => @players, :runtime => runtime }
|
770
|
+
if not halted
|
771
|
+
announce _("%{p} wins with %{b}%{score}%{b} points!") % {
|
772
|
+
:p => @players.first, :score => score, :b => Bold
|
773
|
+
}
|
774
|
+
closure.merge!(:winner => @players.first, :score => score,
|
775
|
+
:opponents => @players.length - 1)
|
776
|
+
end
|
777
|
+
|
778
|
+
@plugin.do_end_game(@channel, closure)
|
779
|
+
end
|
780
|
+
|
781
|
+
end
|
782
|
+
|
783
|
+
# A won game: store score and number of opponents, so we can calculate
|
784
|
+
# an average score per opponent (requested by Squiddhartha)
|
785
|
+
define_structure :UnoGameWon, :score, :opponents
|
786
|
+
# For each player we store the number of games played, the number of
|
787
|
+
# games forfeited, and an UnoGameWon for each won game
|
788
|
+
define_structure :UnoPlayerStats, :played, :forfeits, :won
|
789
|
+
|
790
|
+
class UnoPlugin < Plugin
|
791
|
+
attr :games
|
792
|
+
def initialize
|
793
|
+
super
|
794
|
+
@games = {}
|
795
|
+
end
|
796
|
+
|
797
|
+
def help(plugin, topic="")
|
798
|
+
case topic
|
799
|
+
when 'commands'
|
800
|
+
[
|
801
|
+
_("'jo' to join in"),
|
802
|
+
_("'pl <card>' to play <card>: e.g. 'pl g7' to play Green 7, or 'pl rr' to play Red Reverse, or 'pl y2y2' to play both Yellow 2 cards"),
|
803
|
+
_("'pe' to pick a card"),
|
804
|
+
_("'pa' to pass your turn"),
|
805
|
+
_("'co <color>' to pick a color after playing a Wild: e.g. 'co g' to select Green (or 'pl w+4 g' to select the color when playing the Wild)"),
|
806
|
+
_("'ca' to show current cards"),
|
807
|
+
_("'cd' to show the current discard"),
|
808
|
+
_("'ch' to challenge a Wild +4"),
|
809
|
+
_("'od' to show the playing order"),
|
810
|
+
_("'ti' to show play time"),
|
811
|
+
_("'tu' to show whose turn it is")
|
812
|
+
].join("; ")
|
813
|
+
when 'challenge'
|
814
|
+
_("A Wild +4 can only be played legally if you don't have normal (not special) cards of the current color. ") +
|
815
|
+
_("The next player can challenge a W+4 by using the 'ch' command. ") +
|
816
|
+
_("If the W+4 play was illegal, the player who played it must pick the W+4, pick 4 cards from the stock, and play a legal card. ") +
|
817
|
+
_("If the W+4 play was legal, the challenger must pick 6 cards instead of 4.")
|
818
|
+
when 'rules'
|
819
|
+
_("play all your cards, one at a time, by matching either the color or the value of the currently discarded card. ") +
|
820
|
+
_("cards with special effects: Skip (next player skips a turn), Reverse (reverses the playing order), +2 (next player has to take 2 cards). ") +
|
821
|
+
_("Wilds can be played on any card, and you must specify the color for the next card. ") +
|
822
|
+
_("Wild +4 also forces the next player to take 4 cards, but it can only be played if you can't play a color card. ") +
|
823
|
+
_("you can play another +2 or +4 card on a +2 card, and a +4 on a +4, forcing the first player who can't play one to pick the cumulative sum of all cards. ") +
|
824
|
+
_("you can also play a Reverse on a +2 or +4, bouncing the effect back to the previous player (that now comes next). ")
|
825
|
+
when /scor(?:e|ing)/, /points?/
|
826
|
+
[
|
827
|
+
_("The points won with a game of %{uno} are totalled from the cards remaining in the hands of the other players."),
|
828
|
+
_("Each normal (not special) card is worth its face value (from 0 to 9 points)."),
|
829
|
+
_("Each colored special card (+2, Reverse, Skip) is worth 20 points."),
|
830
|
+
_("Each Wild and Wild +4 is worth 50 points.")
|
831
|
+
].join(" ") % { :uno => UnoGame::UNO }
|
832
|
+
when /cards?/
|
833
|
+
[
|
834
|
+
_("There are 108 cards in a standard %{uno} deck."),
|
835
|
+
_("For each color (Blue, Green, Red, Yellow) there are 19 numbered cards (from 0 to 9), with two of each number except for 0."),
|
836
|
+
_("There are also 6 special cards for each color, two each of +2, Reverse, Skip."),
|
837
|
+
_("Finally, there are 4 Wild and 4 Wild +4 cards.")
|
838
|
+
].join(" ") % { :uno => UnoGame::UNO }
|
839
|
+
when 'admin'
|
840
|
+
_("The game manager (the user that started the game) can execute the following commands to manage it: ") +
|
841
|
+
[
|
842
|
+
_("'uno drop <user>' to drop a user from the game (any user can drop itself using 'uno drop')"),
|
843
|
+
_("'uno replace <old> [with] <new>' to replace a player with someone else (useful in case of disconnects)"),
|
844
|
+
_("'uno transfer [to] <nick>' to transfer game ownership to someone else"),
|
845
|
+
_("'uno end' to end the game before its natural completion")
|
846
|
+
].join("; ")
|
847
|
+
else
|
848
|
+
_("%{uno} game. !uno to start a game. see 'help uno rules' for the rules, 'help uno admin' for admin commands. In-game commands: %{cmds}.") % {
|
849
|
+
:uno => UnoGame::UNO,
|
850
|
+
:cmds => help(plugin, 'commands')
|
851
|
+
}
|
852
|
+
end
|
853
|
+
end
|
854
|
+
|
855
|
+
def message(m)
|
856
|
+
return unless @games.key?(m.channel)
|
857
|
+
return unless m.plugin # skip messages such as: <someuser> botname,
|
858
|
+
g = @games[m.channel]
|
859
|
+
case m.plugin.intern
|
860
|
+
when :jo # join game
|
861
|
+
return if m.params
|
862
|
+
g.add_player(m.source)
|
863
|
+
when :pe # pick card
|
864
|
+
return if m.params
|
865
|
+
if g.has_turn?(m.source)
|
866
|
+
if g.player_has_picked
|
867
|
+
m.reply _("you already picked a card")
|
868
|
+
elsif g.picker > 0
|
869
|
+
g.pass(m.source)
|
870
|
+
else
|
871
|
+
g.pick_card(m.source)
|
872
|
+
end
|
873
|
+
else
|
874
|
+
m.reply _("It's not your turn")
|
875
|
+
end
|
876
|
+
when :pa # pass turn
|
877
|
+
return if m.params or not g.start_time
|
878
|
+
if g.has_turn?(m.source)
|
879
|
+
g.pass(m.source)
|
880
|
+
else
|
881
|
+
m.reply _("It's not your turn")
|
882
|
+
end
|
883
|
+
when :pl # play card
|
884
|
+
if g.has_turn?(m.source)
|
885
|
+
g.play_card(m.source, m.params.downcase)
|
886
|
+
else
|
887
|
+
m.reply _("It's not your turn")
|
888
|
+
end
|
889
|
+
when :co # pick color
|
890
|
+
if g.has_turn?(m.source)
|
891
|
+
g.choose_color(m.source, m.params.downcase)
|
892
|
+
else
|
893
|
+
m.reply _("It's not your turn")
|
894
|
+
end
|
895
|
+
when :ca # show current cards
|
896
|
+
return if m.params
|
897
|
+
g.show_all_cards(m.source)
|
898
|
+
when :cd # show current discard
|
899
|
+
return if m.params or not g.start_time
|
900
|
+
g.show_discard
|
901
|
+
when :ch
|
902
|
+
if g.has_turn?(m.source)
|
903
|
+
if g.last_discard
|
904
|
+
g.challenge
|
905
|
+
else
|
906
|
+
m.reply _("previous move cannot be challenged")
|
907
|
+
end
|
908
|
+
else
|
909
|
+
m.reply _("It's not your turn")
|
910
|
+
end
|
911
|
+
when :od # show playing order
|
912
|
+
return if m.params
|
913
|
+
g.show_order
|
914
|
+
when :ti # show play time
|
915
|
+
return if m.params
|
916
|
+
g.show_time
|
917
|
+
when :tu # show whose turn is it
|
918
|
+
return if m.params
|
919
|
+
if g.has_turn?(m.source)
|
920
|
+
m.nickreply _("it's your turn, sleepyhead")
|
921
|
+
else
|
922
|
+
g.show_turn(:cards => false)
|
923
|
+
end
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
def create_game(m, p)
|
928
|
+
if @games.key?(m.channel)
|
929
|
+
m.reply _("There is already an %{uno} game running here, managed by %{who}. say 'jo' to join in") % {
|
930
|
+
:who => @games[m.channel].manager,
|
931
|
+
:uno => UnoGame::UNO
|
932
|
+
}
|
933
|
+
return
|
934
|
+
end
|
935
|
+
@games[m.channel] = UnoGame.new(self, m.channel, m.source)
|
936
|
+
@bot.auth.irc_to_botuser(m.source).set_temp_permission('uno::manage', true, m.channel)
|
937
|
+
m.reply _("Ok, created %{uno} game on %{channel}, say 'jo' to join in") % {
|
938
|
+
:uno => UnoGame::UNO,
|
939
|
+
:channel => m.channel
|
940
|
+
}
|
941
|
+
end
|
942
|
+
|
943
|
+
def transfer_ownership(m, p)
|
944
|
+
unless @games.key?(m.channel)
|
945
|
+
m.reply _("There is no %{uno} game running here") % { :uno => UnoGame::UNO }
|
946
|
+
return
|
947
|
+
end
|
948
|
+
g = @games[m.channel]
|
949
|
+
old = g.manager
|
950
|
+
new = m.channel.get_user(p[:nick])
|
951
|
+
if new
|
952
|
+
g.manager = new
|
953
|
+
@bot.auth.irc_to_botuser(old).reset_temp_permission('uno::manage', m.channel)
|
954
|
+
@bot.auth.irc_to_botuser(new).set_temp_permission('uno::manage', true, m.channel)
|
955
|
+
m.reply _("%{uno} game ownership transferred from %{old} to %{nick}") % {
|
956
|
+
:uno => UnoGame::UNO, :old => old, :nick => p[:nick]
|
957
|
+
}
|
958
|
+
else
|
959
|
+
m.reply _("who is this %{nick} you want me to transfer game ownership to?") % p
|
960
|
+
end
|
961
|
+
end
|
962
|
+
|
963
|
+
def end_game(m, p)
|
964
|
+
unless @games.key?(m.channel)
|
965
|
+
m.reply _("There is no %{uno} game running here") % { :uno => UnoGame::UNO }
|
966
|
+
return
|
967
|
+
end
|
968
|
+
@games[m.channel].end_game(true)
|
969
|
+
end
|
970
|
+
|
971
|
+
def cleanup
|
972
|
+
@games.each { |k, g| g.end_game(true) }
|
973
|
+
super
|
974
|
+
end
|
975
|
+
|
976
|
+
def chan_reg(channel)
|
977
|
+
@registry.sub_registry(channel.downcase)
|
978
|
+
end
|
979
|
+
|
980
|
+
def chan_stats(channel)
|
981
|
+
stats = chan_reg(channel).sub_registry('stats')
|
982
|
+
class << stats
|
983
|
+
def store(val)
|
984
|
+
val.to_i
|
985
|
+
end
|
986
|
+
def restore(val)
|
987
|
+
val.to_i
|
988
|
+
end
|
989
|
+
end
|
990
|
+
stats.set_default(0)
|
991
|
+
return stats
|
992
|
+
end
|
993
|
+
|
994
|
+
def chan_pstats(channel)
|
995
|
+
pstats = chan_reg(channel).sub_registry('players')
|
996
|
+
pstats.set_default(UnoPlayerStats.new(0,0,[]))
|
997
|
+
return pstats
|
998
|
+
end
|
999
|
+
|
1000
|
+
def do_end_game(channel, closure)
|
1001
|
+
reg = chan_reg(channel)
|
1002
|
+
stats = chan_stats(channel)
|
1003
|
+
stats['played'] += 1
|
1004
|
+
stats['played_runtime'] += closure[:runtime]
|
1005
|
+
if closure[:winner]
|
1006
|
+
stats['finished'] += 1
|
1007
|
+
stats['finished_runtime'] += closure[:runtime]
|
1008
|
+
|
1009
|
+
pstats = chan_pstats(channel)
|
1010
|
+
|
1011
|
+
closure[:players].each do |pl|
|
1012
|
+
k = pl.user.downcase
|
1013
|
+
pls = pstats[k]
|
1014
|
+
pls.played += 1
|
1015
|
+
pstats[k] = pls
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
closure[:dropouts].each do |pl|
|
1019
|
+
k = pl.user.downcase
|
1020
|
+
pls = pstats[k]
|
1021
|
+
pls.played += 1
|
1022
|
+
pls.forfeits += 1
|
1023
|
+
pstats[k] = pls
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
winner = closure[:winner]
|
1027
|
+
won = UnoGameWon.new(closure[:score], closure[:opponents])
|
1028
|
+
k = winner.user.downcase
|
1029
|
+
pls = pstats[k] # already marked played +1 above
|
1030
|
+
pls.won << won
|
1031
|
+
pstats[k] = pls
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
@bot.auth.irc_to_botuser(@games[channel].manager).reset_temp_permission('uno::manage', channel)
|
1035
|
+
@games.delete(channel)
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
def do_chanstats(m, p)
|
1039
|
+
stats = chan_stats(m.channel)
|
1040
|
+
np = stats['played']
|
1041
|
+
nf = stats['finished']
|
1042
|
+
if np > 0
|
1043
|
+
str = _("%{nf} %{uno} games completed over %{np} games played. ") % {
|
1044
|
+
:np => np, :uno => UnoGame::UNO, :nf => nf
|
1045
|
+
}
|
1046
|
+
cgt = stats['finished_runtime']
|
1047
|
+
tgt = stats['played_runtime']
|
1048
|
+
str << _("%{cgt} game time for completed games") % {
|
1049
|
+
:cgt => Utils.secs_to_string(cgt)
|
1050
|
+
}
|
1051
|
+
if np > nf
|
1052
|
+
str << _(" on %{tgt} total game time. ") % {
|
1053
|
+
:tgt => Utils.secs_to_string(tgt)
|
1054
|
+
}
|
1055
|
+
else
|
1056
|
+
str << ". "
|
1057
|
+
end
|
1058
|
+
str << _("%{avg} average game time for completed games") % {
|
1059
|
+
:avg => Utils.secs_to_string(cgt/nf)
|
1060
|
+
}
|
1061
|
+
str << _(", %{tavg} for all games") % {
|
1062
|
+
:tavg => Utils.secs_to_string(tgt/np)
|
1063
|
+
} if np > nf
|
1064
|
+
m.reply str
|
1065
|
+
else
|
1066
|
+
m.reply _("nobody has played %{uno} on %{chan} yet") % {
|
1067
|
+
:uno => UnoGame::UNO, :chan => m.channel
|
1068
|
+
}
|
1069
|
+
end
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
def do_pstats(m, p)
|
1073
|
+
dnick = p[:nick] || m.source # display-nick, don't later case
|
1074
|
+
nick = dnick.downcase
|
1075
|
+
ps = chan_pstats(m.channel)[nick]
|
1076
|
+
if ps.played == 0
|
1077
|
+
m.reply _("%{nick} never played %{uno} here") % {
|
1078
|
+
:uno => UnoGame::UNO, :nick => dnick
|
1079
|
+
}
|
1080
|
+
return
|
1081
|
+
end
|
1082
|
+
np = ps.played
|
1083
|
+
nf = ps.forfeits
|
1084
|
+
nw = ps.won.length
|
1085
|
+
score = ps.won.inject(0) { |sum, w| sum += w.score }
|
1086
|
+
str = _("%{nick} played %{np} %{uno} games here, ") % {
|
1087
|
+
:nick => dnick, :np => np, :uno => UnoGame::UNO
|
1088
|
+
}
|
1089
|
+
str << _("forfeited %{nf} games, ") % { :nf => nf } if nf > 0
|
1090
|
+
str << _("won %{nw} games") % { :nw => nw}
|
1091
|
+
if nw > 0
|
1092
|
+
str << _(" with %{score} total points") % { :score => score }
|
1093
|
+
avg = ps.won.inject(0) { |sum, w| sum += w.score/w.opponents }/nw
|
1094
|
+
str << _(" and an average of %{avg} points per opponent") % { :avg => avg }
|
1095
|
+
end
|
1096
|
+
m.reply str
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
def replace_player(m, p)
|
1100
|
+
unless @games.key?(m.channel)
|
1101
|
+
m.reply _("There is no %{uno} game running here") % { :uno => UnoGame::UNO }
|
1102
|
+
return
|
1103
|
+
end
|
1104
|
+
@games[m.channel].replace_player(p[:old], p[:new])
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
def drop_player(m, p)
|
1108
|
+
unless @games.key?(m.channel)
|
1109
|
+
m.reply _("There is no %{uno} game running here") % { :uno => UnoGame::UNO }
|
1110
|
+
return
|
1111
|
+
end
|
1112
|
+
@games[m.channel].drop_player(p[:nick] || m.source.nick)
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
def print_stock(m, p)
|
1116
|
+
unless @games.key?(m.channel)
|
1117
|
+
m.reply _("There is no %{uno} game running here") % { :uno => UnoGame::UNO }
|
1118
|
+
return
|
1119
|
+
end
|
1120
|
+
stock = @games[m.channel].stock
|
1121
|
+
m.reply(_("%{num} cards in stock: %{stock}") % {
|
1122
|
+
:num => stock.length,
|
1123
|
+
:stock => stock.join(' ')
|
1124
|
+
}, :split_at => /#{NormalText}\s*/)
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
def do_top(m, p)
|
1128
|
+
pstats = chan_pstats(m.channel)
|
1129
|
+
scores = []
|
1130
|
+
wins = []
|
1131
|
+
pstats.each do |k, v|
|
1132
|
+
wins << [v.won.length, k]
|
1133
|
+
scores << [v.won.inject(0) { |s, w| s+=w.score }, k]
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
if n = p[:scorenum]
|
1137
|
+
msg = _("%{uno} %{num} highest scores: ") % {
|
1138
|
+
:uno => UnoGame::UNO, :num => p[:scorenum]
|
1139
|
+
}
|
1140
|
+
scores.sort! { |a1, a2| -(a1.first <=> a2.first) }
|
1141
|
+
scores = scores[0, n.to_i].compact
|
1142
|
+
i = 0
|
1143
|
+
if scores.length <= 5
|
1144
|
+
list = "\n" + scores.map { |a|
|
1145
|
+
i+=1
|
1146
|
+
_("%{i}. %{b}%{nick}%{b} with %{b}%{score}%{b} points") % {
|
1147
|
+
:i => i, :b => Bold, :nick => a.last, :score => a.first
|
1148
|
+
}
|
1149
|
+
}.join("\n")
|
1150
|
+
else
|
1151
|
+
list = scores.map { |a|
|
1152
|
+
i+=1
|
1153
|
+
_("%{i}. %{nick} ( %{score} )") % {
|
1154
|
+
:i => i, :nick => a.last, :score => a.first
|
1155
|
+
}
|
1156
|
+
}.join(" | ")
|
1157
|
+
end
|
1158
|
+
elsif n = p[:winnum]
|
1159
|
+
msg = _("%{uno} %{num} most wins: ") % {
|
1160
|
+
:uno => UnoGame::UNO, :num => p[:winnum]
|
1161
|
+
}
|
1162
|
+
wins.sort! { |a1, a2| -(a1.first <=> a2.first) }
|
1163
|
+
wins = wins[0, n.to_i].compact
|
1164
|
+
i = 0
|
1165
|
+
if wins.length <= 5
|
1166
|
+
list = "\n" + wins.map { |a|
|
1167
|
+
i+=1
|
1168
|
+
_("%{i}. %{b}%{nick}%{b} with %{b}%{score}%{b} wins") % {
|
1169
|
+
:i => i, :b => Bold, :nick => a.last, :score => a.first
|
1170
|
+
}
|
1171
|
+
}.join("\n")
|
1172
|
+
else
|
1173
|
+
list = wins.map { |a|
|
1174
|
+
i+=1
|
1175
|
+
_("%{i}. %{nick} ( %{score} )") % {
|
1176
|
+
:i => i, :nick => a.last, :score => a.first
|
1177
|
+
}
|
1178
|
+
}.join(" | ")
|
1179
|
+
end
|
1180
|
+
else
|
1181
|
+
msg = _("uh, what kind of score list did you want, again?")
|
1182
|
+
list = _(" I can only show the top scores (with top) and the most wins (with topwin)")
|
1183
|
+
end
|
1184
|
+
m.reply msg + list, :max_lines => (msg+list).count("\n")+1
|
1185
|
+
end
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
pg = UnoPlugin.new
|
1189
|
+
|
1190
|
+
pg.map 'uno', :private => false, :action => :create_game
|
1191
|
+
pg.map 'uno end', :private => false, :action => :end_game, :auth_path => 'manage'
|
1192
|
+
pg.map 'uno drop', :private => false, :action => :drop_player, :auth_path => 'manage::drop::self!'
|
1193
|
+
pg.map 'uno giveup', :private => false, :action => :drop_player, :auth_path => 'manage::drop::self!'
|
1194
|
+
pg.map 'uno drop :nick', :private => false, :action => :drop_player, :auth_path => 'manage::drop::other!'
|
1195
|
+
pg.map 'uno replace :old [with] :new', :private => false, :action => :replace_player, :auth_path => 'manage'
|
1196
|
+
pg.map 'uno transfer [game [ownership]] [to] :nick', :private => false, :action => :transfer_ownership, :auth_path => 'manage'
|
1197
|
+
pg.map 'uno stock', :private => false, :action => :print_stock
|
1198
|
+
pg.map 'uno chanstats', :private => false, :action => :do_chanstats
|
1199
|
+
pg.map 'uno stats [:nick]', :private => false, :action => :do_pstats
|
1200
|
+
pg.map 'uno top :scorenum', :private => false, :action => :do_top, :defaults => { :scorenum => 5 }
|
1201
|
+
pg.map 'uno topwin :winnum', :private => false, :action => :do_top, :defaults => { :winnum => 5 }
|
1202
|
+
|
1203
|
+
pg.default_auth('stock', false)
|
1204
|
+
pg.default_auth('manage', false)
|
1205
|
+
pg.default_auth('manage::drop::self', true)
|