diff --git a/modules/unstable/UNO.pm b/modules/unstable/UNO.pm new file mode 100644 index 0000000..c0bb549 --- /dev/null +++ b/modules/unstable/UNO.pm @@ -0,0 +1,1466 @@ +# Module: UNO. See below for documentation. +# Copyright (C) 2010-2011 Xelhua Development Group, et al. +# This program is free software; rights to this code are stated in doc/LICENSE. +package M::UNO; +use strict; +use warnings; +use feature qw(switch); +use API::Std qw(cmd_add cmd_del hook_add hook_del trans conf_get err has_priv match_user awarn); +use API::IRC qw(notice privmsg); + +# Set various variables we'll need throughout runtime. +my $UNO = 0; +my $UNOW = 0; +my ($UNOCHAN, $UNOTIME, $UNOGCC, $EDITION, $ORDER, $DEALER, $CURRTURN, $TOPCARD, %PLAYERS, %NICKS); +my $DRAWN = 0; +my $ANYEDITION = 0; + +# Initialization subroutine. +sub _init { + # Check for required configuration values. + if (!conf_get('uno:edition')) { + err(3, 'Unable to load UNO: Missing required configuration values.', 0); + return; + } + $EDITION = (conf_get('uno:edition'))[0][0]; + $EDITION = uc(substr $EDITION, 0, 1).substr $EDITION, 1; + + # Check if the edition is valid. + if ($EDITION !~ m/^(Original|Super|Advanced|Any)$/xsm) { + err(3, "Unable to load UNO: Invalid edition: $EDITION", 0); + return; + } + + # If it's Any, set ANYEDITION. + if ($EDITION eq 'Any') { $ANYEDITION = 1 } + + # PostgreSQL is not supported, yet. + if ($Auto::ENFEAT =~ /pgsql/) { err(3, 'Unable to load UNO: PostgreSQL is not supported.', 0); return } + + # Create `unoscores` table. + $Auto::DB->do('CREATE TABLE IF NOT EXISTS unoscores (player TEXT, score INTEGER)') or return; + + # Create UNO command. + cmd_add('UNO', 0, 0, \%M::UNO::HELP_UNO, \&M::UNO::cmd_uno) or return; + + # Create on_nick hook. + hook_add('on_nick', 'uno.updatedata.nick', \&M::UNO::on_nick) or return; + # Create on_quit hook. + hook_add('on_quit', 'uno.updatedata.quit', \&M::UNO::on_quit) or return; + # Create on_part hook. + hook_add('on_part', 'uno.updatedata.part', \&M::UNO::on_part) or return; + # Create on_kick hook. + hook_add('on_kick', 'uno.updatedata.kick', \&M::UNO::on_kick) or return; + # Create on_rehash hook. + hook_add('on_rehash', 'uno.updatedata.rehash', \&M::UNO::on_rehash) or return; + + # Success. + return 1; +} + +# Void subroutine. +sub _void { + # Delete the UNO command. + cmd_del('UNO') or return; + + # Delete on_nick hook. + hook_del('on_nick', 'uno.updatedata.nick') or return; + # Delete on_quit hook. + hook_del('on_quit', 'uno.updatedata.quit') or return; + # Delete on_part hook. + hook_del('on_part', 'uno.updatedata.part') or return; + # Delete on_kick hook. + hook_del('on_kick', 'uno.updatedata.kick') or return; + # Delete on_rehash hook. + hook_del('on_rehash', 'uno.updatedata.rehash') or return; + + # Success. + return 1; +} + +# Help hash for UNO command. Spanish, German and French translations needed. +our %HELP_UNO = ( + 'en' => "This command allows you to take various actions in a game of UNO. \2Syntax:\2 UNO (START|JOIN|DEAL|PLAY|DRAW|PASS|CARDS|TOPCARD|STATS|KICK|QUIT|STOP|TOPTEN|SCORE) [parameters]", +); + +# Callback for UNO command. +sub cmd_uno { + my ($src, @argv) = @_; + + # Check for action parameter. + if (!defined $argv[0]) { + notice($src->{svr}, $src->{nick}, trans('Not enough parameters').q{.}); + return; + } + + # Iterate through available actions. + given (uc $argv[0]) { + when (/^(START|S)$/) { + # UNO START + + # Ensure there is not a game already running. + if ($UNO or $UNOW) { + notice($src->{svr}, $src->{nick}, "There is already a game of UNO running in \2$UNOCHAN\2."); + return; + } + + # Check if the channel is allowed. + if (conf_get('uno:reschan')) { + my ($net, $chan) = split '/', (conf_get('uno:reschan'))[0][0]; + if (lc $src->{svr} ne lc $net or lc $src->{chan} ne lc $chan) { + return; + } + } + + # If it's Any Edition, do some extra stuff. + if ($ANYEDITION) { + # Require the second parameter. + if (!defined $argv[1]) { + notice($src->{svr}, $src->{nick}, "This Auto is configured with Any Edition. You must specify the edition to play with as a second parameter. \2Syntax:\2 UNO START "); + return; + } + if ($argv[1] !~ m/^(original|super|advanced)$/ixsm) { + notice($src->{svr}, $src->{nick}, "Invalid edition \2$argv[1]\2. Must be original, super or advanced."); + return; + } + # Set the edition. + $argv[1] = lc $argv[1]; + $EDITION = $argv[1]; + $EDITION = uc(substr $EDITION, 0, 1).substr $EDITION, 1; + } + + # Set variables. + $UNOW = 1; + $UNOCHAN = $src->{svr}.'/'.lc $src->{chan}; + $PLAYERS{lc $src->{nick}} = []; + $NICKS{lc $src->{nick}} = $src->{nick}; + $DEALER = lc $src->{nick}; + + privmsg($src->{svr}, $src->{chan}, "\2$src->{nick}\2 has started \2\00303U\003\00304N\003\00312O\003 for Auto ($EDITION Edition)\2. UNO JOIN to join the game."); + } + when (/^(JOIN|J)$/) { + # UNO JOIN + + # Ensure a game is running. + if (!$UNO and !$UNOW) { + notice($src->{svr}, $src->{nick}, 'There is currently no game of UNO running. UNO START to start a game.'); + return; + } + else { + if ($src->{svr}.'/'.lc $src->{chan} ne $UNOCHAN) { + notice($src->{svr}, $src->{nick}, "UNO is currently running in \2$UNOCHAN\2."); + return; + } + } + + # Update variables. + $PLAYERS{lc $src->{nick}} = []; + $NICKS{lc $src->{nick}} = $src->{nick}; + if ($UNO) { + $ORDER .= ' '.lc $src->{nick}; + for (my $i = 1; $i <= 7; $i++) { _givecard(lc $src->{nick}) } + } + + privmsg($src->{svr}, $src->{chan}, "\2$src->{nick}\2 has joined the game."); + if ($UNO) { + my $cards; + foreach (@{$PLAYERS{lc $src->{nick}}}) { + $cards .= ' '._fmtcard($_); + } + $cards = substr $cards, 1; + notice($src->{svr}, $src->{nick}, "Your cards are: $cards"); + } + } + when ('DEAL') { + # UNO DEAL + + # Ensure a game is running. + if (!$UNO and !$UNOW) { + notice($src->{svr}, $src->{nick}, 'There is currently no game of UNO running. UNO START to start a game.'); + return; + } + else { + if ($src->{svr}.'/'.lc $src->{chan} ne $UNOCHAN) { + notice($src->{svr}, $src->{nick}, "UNO is currently running in \2$UNOCHAN\2."); + return; + } + } + + # Check if cards have already been dealt. + if ($UNO) { + notice($src->{svr}, $src->{nick}, 'Cards have already been dealt. Game is in progress.'); + return; + } + + # Ensure this is the dealer. + if (lc $src->{nick} ne $DEALER) { + notice($src->{svr}, $src->{nick}, 'Only the dealer may deal the cards.'); + return; + } + + # Check for at least two players. + if (keys %PLAYERS < 2) { + notice($src->{svr}, $src->{nick}, 'Two players are required to play.'); + return; + } + + # Deal the cards. + foreach (keys %PLAYERS) { + for (my $i = 1; $i <= 7; $i++) { _givecard($_) } + my $cards; + foreach my $card (@{$PLAYERS{$_}}) { + $cards .= ' '._fmtcard($card); + } + $cards = substr $cards, 1; + notice($src->{svr}, $_, "Your cards are: $cards"); + $ORDER .= " $_"; + } + $ORDER = substr $ORDER, 1; + + $UNO = 1; + $UNOW = 0; + $UNOTIME = time; + $TOPCARD = _givecard(); + my ($tccol, $tcval) = split m/[:]/, $TOPCARD; + while ($tcval eq 'T' || $tccol =~ m/^W/xsm) { + $TOPCARD = _givecard(); + ($tccol, $tcval) = split m/[:]/, $TOPCARD; + } + $CURRTURN = lc $src->{nick}; + my $left = _nextturn(2); + $CURRTURN = $left; + privmsg($src->{svr}, $src->{chan}, "\2$src->{nick}\2 has dealt the cards. Game begin."); + privmsg($src->{svr}, $src->{chan}, "\2".$NICKS{$left}."'s\2 turn. Top Card: "._fmtcard($TOPCARD)); + _runcard($TOPCARD, 1); + } + when (/^(PLAY|P)$/) { + # UNO PLAY + + # Check for required parameters. + if (!defined $argv[2]) { + notice($src->{svr}, $src->{nick}, trans('Not enough parameters').". \2Syntax:\2 UNO PLAY "); + return; + } + + # Ensure a game is running. + if (!$UNO) { + notice($src->{svr}, $src->{nick}, 'There is currently no game of UNO running. UNO START to start a game.'); + return; + } + else { + if ($src->{svr}.'/'.lc $src->{chan} ne $UNOCHAN) { + notice($src->{svr}, $src->{nick}, "UNO is currently running in \2$UNOCHAN\2."); + return; + } + } + + # Check if they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Check if it's his/her turn. + if (lc $src->{nick} ne $CURRTURN) { + notice($src->{svr}, $src->{nick}, 'It is not your turn.'); + return; + } + + # Fix color. + $argv[1] =~ s/blue/B/ixsm; + $argv[1] =~ s/red/R/ixsm; + $argv[1] =~ s/green/G/ixsm; + $argv[1] =~ s/yellow/Y/ixsm; + $argv[2] =~ s/blue/B/ixsm; + $argv[2] =~ s/red/R/ixsm; + $argv[2] =~ s/green/G/ixsm; + $argv[2] =~ s/yellow/Y/ixsm; + + # Check if they have this card. + if (!_hascard(lc $src->{nick}, uc $argv[1].':'.uc $argv[2])) { + notice($src->{svr}, $src->{nick}, 'You don\'t have that card.'); + return; + } + + # Check if this card is valid. + my ($tcc, $tcv) = split m/[:]/, $TOPCARD; + if (uc $argv[1] eq 'R' || uc $argv[1] eq 'B' || uc $argv[1] eq 'G' || uc $argv[1] eq 'Y') { + if (uc $argv[1] ne $tcc and uc $argv[2] ne $tcv) { + notice($src->{svr}, $src->{nick}, 'That card cannot be played.'); + return; + } + } + + # If this is a Trade Hands card... + if (uc $argv[2] eq 'T') { + # Ensure it has the extra argument. + if (!defined $argv[3]) { + notice($src->{svr}, $src->{nick}, "The Trade Hands card requires the argument. \2Syntax:\2 UNO PLAY T "); + return; + } + # Ensure they're not trading hands with themselves. + if (lc $argv[3] eq $CURRTURN) { + notice($src->{svr}, $src->{nick}, 'You may not trade with yourself.'); + return; + } + # Ensure the player they're trading with is playing. + if (!defined $PLAYERS{lc $argv[3]}) { + notice($src->{svr}, $src->{nick}, "No such user \2$argv[3]\2 is playing."); + return; + } + } + + # If it's a wildcard... + if ($argv[1] =~ m/^W/ixsm) { + # Ensure the third argument is a valid color. + if ($argv[2] !~ m/^(R|B|G|Y)$/ixsm) { + notice($src->{svr}, $src->{nick}, "Invalid color \2$argv[2]\2."); + return; + } + } + + privmsg($src->{svr}, $src->{chan}, "\2$src->{nick}\2 plays "._fmtcard(uc $argv[1].':'.uc $argv[2])); + + # Delete the card from the player's hand. + my $delres = _delcard(lc $src->{nick}, uc $argv[1].':'.uc $argv[2]); + if ($delres == -1) { return 1 } + + # Play the card. + if (defined $argv[3]) { + _runcard(uc $argv[1].':'.uc $argv[2], 0, @argv[3..$#argv]); + } + else { + _runcard(uc $argv[1].':'.uc $argv[2], 0, undef); + } + $DRAWN = 0; + $UNOGCC++; + } + when (/^(DRAW|D)$/) { + # UNO DRAW + + # Ensure a game is running. + if (!$UNO) { + notice($src->{svr}, $src->{nick}, 'There is currently no game of UNO running. UNO START to start a game.'); + return; + } + else { + if ($src->{svr}.'/'.lc $src->{chan} ne $UNOCHAN) { + notice($src->{svr}, $src->{nick}, "UNO is currently running in \2$UNOCHAN\2."); + return; + } + } + + # Check if they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Check if it's his/her turn. + if (lc $src->{nick} ne $CURRTURN) { + notice($src->{svr}, $src->{nick}, 'It is not your turn.'); + return; + } + + # Don't allow them to draw more than one card. + if ($DRAWN eq lc $src->{nick}) { + notice($src->{svr}, $src->{nick}, 'You may only draw once per turn. Use UNO PASS to pass.'); + return; + } + + # Now draw card(s) depending on the edition. + if ($EDITION eq 'Original') { + notice($src->{svr}, $src->{nick}, 'You drew: '._fmtcard(_givecard(lc $src->{nick}))); + my ($net, $chan) = split '/', $UNOCHAN; + privmsg($net, $chan, "\2$src->{nick}\2 drew a card."); + } + else { + my $amnt = int rand 11; + if ($amnt > 0) { + my @dcards; + for (my $i = $amnt; $i > 0; $i--) { push @dcards, _fmtcard(_givecard(lc $src->{nick})) } + notice($src->{svr}, $src->{nick}, 'You drew: '.join(' ', @dcards)); + } + my ($net, $chan) = split '/', $UNOCHAN; + privmsg($net, $chan, "\2$src->{nick}\2 drew \2$amnt\2 cards."); + } + $DRAWN = lc $src->{nick}; + } + when ('PASS') { + # UNO PASS + + # Ensure a game is running. + if (!$UNO) { + notice($src->{svr}, $src->{nick}, 'There is currently no game of UNO running. UNO START to start a game.'); + return; + } + else { + if ($src->{svr}.'/'.lc $src->{chan} ne $UNOCHAN) { + notice($src->{svr}, $src->{nick}, "UNO is currently running in \2$UNOCHAN\2."); + return; + } + } + + # Check if they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Check if it's his/her turn. + if (lc $src->{nick} ne $CURRTURN) { + notice($src->{svr}, $src->{nick}, 'It is not your turn.'); + return; + } + + # Make sure they've drawn at least once. + if ($DRAWN ne lc $src->{nick}) { + notice($src->{svr}, $src->{nick}, 'You must draw once before passing.'); + return; + } + + # Pass this user. + $DRAWN = 0; + my ($net, $chan) = split '/', $UNOCHAN; + privmsg($net, $chan, "\2$src->{nick}\2 passes."); + _nextturn(0); + } + when (/^(CARDS|C)$/) { + # UNO CARDS + + # Ensure a game is running. + if (!$UNO) { + notice($src->{svr}, $src->{nick}, 'There is currently no game of UNO running. UNO START to start a game.'); + return; + } + else { + if ($src->{svr}.'/'.lc $src->{chan} ne $UNOCHAN) { + notice($src->{svr}, $src->{nick}, "UNO is currently running in \2$UNOCHAN\2."); + return; + } + } + + # Check if they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Tell them their cards. + my $cards; + foreach (@{$PLAYERS{lc $src->{nick}}}) { $cards .= ' '._fmtcard($_) } + $cards = substr $cards, 1; + notice($src->{svr}, $src->{nick}, "Your cards are: $cards"); + } + when (/^(TOPCARD|TC)$/) { + # UNO TOPCARD + + # Ensure a game is running. + if (!$UNO) { + notice($src->{svr}, $src->{nick}, 'There is currently no game of UNO running. UNO START to start a game.'); + return; + } + else { + if ($src->{svr}.'/'.lc $src->{chan} ne $UNOCHAN) { + notice($src->{svr}, $src->{nick}, "UNO is currently running in \2$UNOCHAN\2."); + return; + } + } + + # Check if they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Return the top card. + notice($src->{svr}, $src->{nick}, 'Top card: '._fmtcard($TOPCARD)); + } + when (/^(KICK|K)$/) { + # UNO KICK + + # Second parameter required. + if (!defined $argv[1]) { + notice($src->{svr}, $src->{nick}, trans('Not enough parameters').". \2Syntax:\2 UNO KICK "); + return; + } + + # Ensure a game is running. + if (!$UNO and !$UNOW) { + notice($src->{svr}, $src->{nick}, 'There is currently no game of UNO running. UNO START to start a game.'); + return; + } + else { + if ($src->{svr}.'/'.lc $src->{chan} ne $UNOCHAN) { + notice($src->{svr}, $src->{nick}, "UNO is currently running in \2$UNOCHAN\2."); + return; + } + } + + # Ensure they have permission to perform this action. + if (lc $src->{nick} ne $DEALER && !has_priv(match_user(%$src), 'uno.override')) { + notice($src->{svr}, $src->{nick}, trans('Permission denied').q{.}); + return; + } + + # Check if the player is in the game. + if (!defined $PLAYERS{lc $argv[1]}) { + notice($src->{svr}, $src->{nick}, "No such user \2$argv[1]\ is playing."); + return; + } + + # Delete the player. + my ($net, $chan) = split '/', $UNOCHAN; + privmsg($net, $chan, "\2$src->{nick}\2 has kicked \2$argv[1]\2 from the game."); + _delplyr(lc $argv[1]); + } + when (/^(QUIT|Q)$/) { + # UNO QUIT + + # Ensure a game is running. + if (!$UNO and !$UNOW) { + notice($src->{svr}, $src->{nick}, 'There is currently no game of UNO running. UNO START to start a game.'); + return; + } + else { + if ($src->{svr}.'/'.lc $src->{chan} ne $UNOCHAN) { + notice($src->{svr}, $src->{nick}, "UNO is currently running in \2$UNOCHAN\2."); + return; + } + } + + # Check if they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Check if it's his/her turn. + if (lc $src->{nick} ne $CURRTURN) { + notice($src->{svr}, $src->{nick}, 'It is not your turn.'); + return; + } + + # Delete them. + my ($net, $chan) = split '/', $UNOCHAN; + privmsg($net, $chan, "\2$src->{nick}\2 has left the game."); + _delplyr(lc $src->{nick}); + } + when ('STOP') { + # UNO STOP + + # Ensure a game is running. + if (!$UNO and !$UNOW) { + notice($src->{svr}, $src->{nick}, 'There is currently no game of UNO running. UNO START to start a game.'); + return; + } + else { + if ($src->{svr}.'/'.lc $src->{chan} ne $UNOCHAN) { + notice($src->{svr}, $src->{nick}, "UNO is currently running in \2$UNOCHAN\2."); + return; + } + } + + # Ensure they have permission to perform this action. + if (lc $src->{nick} ne $DEALER && !has_priv(match_user(%$src), 'uno.override')) { + notice($src->{svr}, $src->{nick}, trans('Permission denied').q{.}); + return; + } + + # Stop the game. + my ($net, $chan) = split '/', $UNOCHAN; + $UNO = $UNOW = $UNOCHAN = $ORDER = $DEALER = $CURRTURN = $TOPCARD = $DRAWN = $UNOTIME = $UNOGCC = 0; + %PLAYERS = (); + %NICKS = (); + privmsg($net, $chan, "\2$src->{nick}\2 has stopped the game."); + } + when (/^(CARDCOUNT|STATS|CC)$/) { + # UNO CARDCOUNT + + # Ensure a game is running. + if (!$UNO) { + notice($src->{svr}, $src->{nick}, 'There is currently no game of UNO running. UNO START to start a game.'); + return; + } + else { + if ($src->{svr}.'/'.lc $src->{chan} ne $UNOCHAN) { + notice($src->{svr}, $src->{nick}, "UNO is currently running in \2$UNOCHAN\2."); + return; + } + } + + # Iterate through all players, getting their card count. + my $str; + foreach my $plyr (keys %PLAYERS) { + $str .= " \2".$NICKS{$plyr}.":".scalar @{$PLAYERS{$plyr}}."\2"; + } + $str = substr $str, 1; + + # Return count. + notice($src->{svr}, $src->{nick}, "Card count: $str"); + } + when (/^(TOPTEN|T10|TOP10)$/) { + # UNO TOPTEN + + # Get data. + my $dbq = $Auto::DB->prepare('SELECT * FROM unoscores') or notice($src->{svr}, $src->{nick}, trans('An error occurred').q{.}) and return; + $dbq->execute or notice($src->{svr}, $src->{nick}, trans('An error occurred').q{.}) and return; + my $data = $dbq->fetchall_hashref('player') or notice($src->{svr}, $src->{nick}, trans('An error occurred').q{.}) and return; + # Check if there's any scores. + if (keys %$data) { + my $str; + my $i = 0; + foreach (sort {$data->{$b}->{score} <=> $data->{$a}->{score}} keys %$data) { + if ($i > 10) { last } + $str .= ", \2$_:".$data->{$_}->{score}."\2"; + $i++; + } + $str = substr $str, 2; + notice($src->{svr}, $src->{nick}, "Top Ten: $str"); + } + else { + notice($src->{svr}, $src->{nick}, trans('No data available').q{.}); + } + } + when ('SCORE') { + # UNO SCORE + + # Second parameter needed. + if (!defined $argv[1]) { + notice($src->{svr}, $src->{nick}, trans('Not enough parameters').q{.}); + return; + } + my $target = lc $argv[1]; + + if ($Auto::DB->selectrow_array('SELECT score FROM unoscores WHERE player = "'.$target.'"')) { + # Get score. + my $score = $Auto::DB->selectrow_array('SELECT score FROM unoscores WHERE player = "'.$target.'"') or + notice($src->{svr}, $src->{nick}, trans('An error occurred').q{.}) and return; + # Return it. + notice($src->{svr}, $src->{nick}, "Score for \2$argv[1]\2: $score"); + } + else { + notice($src->{svr}, $src->{nick}, trans('No data available').q{.}); + } + } + default { notice($src->{svr}, $src->{nick}, trans('Unknown action', uc $argv[0]).q{.}) } + } + + return 1; +} + +# Subroutine for giving a player a card. +sub _givecard { + my ($player) = @_; + + # Make sure the player exists. + if (defined $player) { + if (!defined $PLAYERS{$player}) { return } + } + + # Get a random number for the appropriate edition. + my $rci; + given ($EDITION) { + when ('Original') { $rci = int rand 53 } + when ('Super') { $rci = int rand 64 } + when ('Advanced') { $rci = int rand 72 } + } + + # Now figure out what card we have here. + my $card; + given ($rci) { + when (0) { $card = 'R:1' } + when (1) { $card = 'R:2' } + when (2) { $card = 'R:3' } + when (3) { $card = 'R:4' } + when (4) { $card = 'R:5' } + when (5) { $card = 'R:6' } + when (6) { $card = 'R:7' } + when (7) { $card = 'R:8' } + when (8) { $card = 'R:9' } + when (9) { $card = 'B:1' } + when (10) { $card = 'B:2' } + when (11) { $card = 'B:3' } + when (12) { $card = 'B:4' } + when (13) { $card = 'B:5' } + when (14) { $card = 'B:6' } + when (15) { $card = 'B:7' } + when (16) { $card = 'B:8' } + when (17) { $card = 'B:9' } + when (18) { $card = 'Y:1' } + when (19) { $card = 'Y:2' } + when (20) { $card = 'Y:3' } + when (21) { $card = 'Y:4' } + when (22) { $card = 'Y:5' } + when (23) { $card = 'Y:6' } + when (24) { $card = 'Y:7' } + when (25) { $card = 'Y:8' } + when (26) { $card = 'Y:9' } + when (27) { $card = 'G:1' } + when (28) { $card = 'G:2' } + when (29) { $card = 'G:3' } + when (30) { $card = 'G:4' } + when (31) { $card = 'G:5' } + when (32) { $card = 'G:6' } + when (33) { $card = 'G:7' } + when (34) { $card = 'G:8' } + when (35) { $card = 'G:9' } + when (36) { $card = 'W:0' } + when (37) { $card = 'W:0' } + when (38) { $card = 'W:0' } + when (39) { + if ($EDITION eq 'Original') { $card = 'WD4:0' } + else { $card = 'WHF:0' } + } + when (40) { + if ($EDITION eq 'Original') { $card = 'WD4:0' } + else { $card = 'WHF:0' } + } + when (41) { $card = 'R:R' } + when (42) { $card = 'B:R' } + when (43) { $card = 'Y:R' } + when (44) { $card = 'G:R' } + when (45) { $card = 'R:S' } + when (46) { $card = 'B:S' } + when (47) { $card = 'Y:S' } + when (48) { $card = 'G:S' } + when (49) { $card = 'R:D2' } + when (50) { $card = 'B:D2' } + when (51) { $card = 'Y:D2' } + when (52) { $card = 'G:D2' } + when (53) { $card = 'WAH:0' } + when (54) { $card = 'WAH:0' } + when (55) { $card = 'WAH:0' } + when (56) { $card = 'R:T' } + when (57) { $card = 'B:T' } + when (58) { $card = 'G:T' } + when (59) { $card = 'Y:T' } + when (60) { $card = 'R:X' } + when (61) { $card = 'B:X' } + when (62) { $card = 'G:X' } + when (63) { $card = 'Y:X' } + when (64) { $card = 'R:W' } + when (65) { $card = 'B:W' } + when (66) { $card = 'G:W' } + when (67) { $card = 'Y:W' } + when (68) { $card = 'R:B' } + when (69) { $card = 'B:B' } + when (70) { $card = 'G:B' } + when (71) { $card = 'Y:B' } + default { $card = 'W:0' } + } + + # Add the card to the player's arrayref. + if (defined $player) { push @{$PLAYERS{$player}}, $card } + + # Return the card. + return $card; +} + +# Return an IRC formatted version of a card name. +sub _fmtcard { + my ($card) = @_; + + my $fmt; + my ($color, $val) = split m/[:]/, $card; + if ($color eq 'W' or $color eq 'WD4' or $color eq 'WAH' or $color eq 'WHF') { $val = $color } + + given ($color) { + when ('R') { $fmt = "\00301,04[$val]\003" } + when ('B') { $fmt = "\00300,12[$val]\003" } + when ('G') { $fmt = "\00300,03[$val]\003" } + when ('Y') { $fmt = "\00301,08[$val]\003" } + default { $fmt = "\002\00300,01[$val]\003\002" } + } + + return $fmt; +} + +# Next turn. +sub _nextturn { + my ($skip) = @_; + my @order = split ' ', $ORDER.' '.$ORDER; + my $br = 0; + my $nplayer; + # Iterate through the players. + foreach (@order) { + if ($br) { + if ($skip eq 1) { + $skip = $_; + next; + } + $nplayer = $_; + last; + } + if ($_ eq $CURRTURN) { + $br = 1; + next; + } + } + # Check if there was a result. + if (!defined $nplayer) { + # Mind, this should never happen, but must be the next person in order. + $nplayer = $order[0]; + } + if ($skip eq 2) { return $nplayer } + + my ($net, $chan) = split '/', $UNOCHAN; + $CURRTURN = $nplayer; + privmsg($net, $chan, "\2".$NICKS{$nplayer}."'s\2 turn. Top Card: "._fmtcard($TOPCARD)); + my $cards; + foreach (@{$PLAYERS{$nplayer}}) { $cards .= ' '._fmtcard($_) } + $cards = substr $cards, 1; + notice($net, $NICKS{$nplayer}, "Your cards are: $cards"); + + if ($skip) { return $skip } + return 1; +} + +# Subroutine for performing actions depending on the card. +sub _runcard { + my ($card, $spec, @vals) = @_; + my ($ccol, $cval) = split m/[:]/, $card; + if (!defined $spec) { $spec = 0 } + my ($net, $chan) = split '/', $UNOCHAN; + if ($ccol ne 'R' && $ccol ne 'B' && $ccol ne 'G' && $ccol ne 'Y') { + $TOPCARD = $cval.':0'; + } + else { + $TOPCARD = uc $card; + } + + given ($ccol) { + when (/(R|B|G|Y)/) { + given ($cval) { + when (/^[1-9]$/) { + if ($spec) { return } + _nextturn(0); + } + when ('R') { + if (keys %PLAYERS > 2) { + # Extract current full order. + my $ns = 0; + my @nop; + my @order = split ' ', $ORDER.' '.$ORDER; + foreach (@order) { + if ($ns) { + if ($_ eq $CURRTURN) { + last; + } + else { + push @nop, $_; + } + } + else { + if ($_ eq $CURRTURN) { + push @nop, $_; + $ns = 1; + } + } + } + # Set new order. + $ORDER = 0; + for (my $i = $#nop; $i >= 0; $i--) { $ORDER .= ' '.$nop[$i] } + $ORDER = substr $ORDER, 1; + } + privmsg($net, $chan, 'Game play has been reversed!'); + if (keys %PLAYERS > 2) { _nextturn(0) } + else { _nextturn(1) } + } + when ('S') { + if ($spec) { + privmsg($net, $chan, "\2".$NICKS{$CURRTURN}."\2 is skipped!"); + _nextturn(0); + } + else { + privmsg($net, $chan, "\2".$NICKS{_nextturn(2)}."\2 is skipped!"); + _nextturn(1); + } + } + when ('D2') { + if ($spec) { + if ($EDITION eq 'Original') { + _givecard($CURRTURN); + _givecard($CURRTURN); + privmsg($net, $chan, "\2".$NICKS{$CURRTURN}."\2 draws 2 cards and is skipped!"); + } + else { + my $amnt = int rand 11; + if ($amnt > 0) { + for (my $i = $amnt; $i > 0; $i--) { _fmtcard(_givecard($CURRTURN)) } + } + privmsg($net, $chan, "\2".$NICKS{$CURRTURN}."\2 draws \2$amnt\2 cards and is skipped!"); + } + _nextturn(0); + } + else { + my $victim = _nextturn(2); + if ($EDITION eq 'Original') { + _givecard($victim); + _givecard($victim); + privmsg($net, $chan, "\2".$NICKS{$victim}."\2 draws 2 cards and is skipped!"); + } + else { + my $amnt = int rand 11; + if ($amnt > 0) { + for (my $i = $amnt; $i > 0; $i--) { _fmtcard(_givecard($victim)) } + } + privmsg($net, $chan, "\2".$NICKS{$victim}."\2 draws \2$amnt\2 cards and is skipped!"); + } + _nextturn(1); + } + } + when ('X') { + # Get all cards of this color. + my @xcards; + foreach my $ucard (@{$PLAYERS{$CURRTURN}}) { + my ($xhcol, undef) = split m/[:]/, $ucard; + if ($xhcol eq $ccol) { push @xcards, $ucard } + } + # Get a more human-readable version of the color. + my $tcol; + given ($ccol) { + when ('R') { $tcol = "\00304red\003" } + when ('B') { $tcol = "\00312blue\003" } + when ('G') { $tcol = "\00303green\003" } + when ('Y') { $tcol = "\00308yellow\003" } + } + privmsg($net, $chan, "\2".$NICKS{$CURRTURN}."\2 is discarding all his/her cards of color \2$tcol\2."); + # Delete all the cards. + my $delres; + foreach (@xcards) { $delres = _delcard($CURRTURN, $_) } + if (defined $delres) { + my $str; + for (my $i = $#xcards; $i >= 0; $i--) { $str .= ' '._fmtcard($xcards[$i]) } + $str = substr $str, 1; + if ($delres != -1) { + notice($net, $NICKS{$CURRTURN}, "You discarded: $str"); + _nextturn(0); + } + } + else { _nextturn(0) } + } + when ('T') { + # Get cards. + my @ucards = @{$PLAYERS{$CURRTURN}}; + my @rcards = @{$PLAYERS{lc $vals[0]}}; + # Reset cards. + $PLAYERS{$CURRTURN} = []; + $PLAYERS{lc $vals[0]} = []; + # Set new cards. + foreach (@ucards) { push @{$PLAYERS{lc $vals[0]}}, $_ } + foreach (@rcards) { push @{$PLAYERS{$CURRTURN}}, $_ } + # The deed, is done. + privmsg($net, $chan, "\2".$NICKS{$CURRTURN}."\2 has traded hands with \2".$NICKS{lc $vals[0]}."\2!"); + _nextturn(0); + } + when ('B') { + # Iterate through all players. + foreach my $vplyr (keys %PLAYERS) { + # Make sure it isn't the player. + if ($vplyr ne $CURRTURN) { + for (my $i = 1; $i <= 7; $i++) { _givecard($vplyr) } + } + } + # Finished. + privmsg($net, $chan, "\2".$NICKS{$CURRTURN}."\2 drops a card bomb on the game! All other players gain 7 cards!"); + _nextturn(0); + } + when ('W') { + # Get a list of players. + my @plyrs = keys %PLAYERS; + # Select a random player. + my $rand = int rand scalar @plyrs; + # Make sure the player isn't the victim. + while ($plyrs[$rand] eq $CURRTURN) { $rand = int rand scalar @plyrs } + # Set victim. + my $victim = $plyrs[$rand]; + # Get the cards of the victim. + my $cards; + foreach (@{$PLAYERS{$victim}}) { $cards .= ' '._fmtcard($_) } + $cards = substr $cards, 1; + # Give the victim two cards. + _givecard($victim); _givecard($victim); + # Reveal the cards to the player. + notice($net, $NICKS{$CURRTURN}, "\2".$NICKS{$victim}."'s\2 cards are: $cards"); + # Finished. + privmsg($net, $chan, "The magical UNO wizard has revealed \2".$NICKS{$victim}."'s\2 hand to \2".$NICKS{$CURRTURN}."\2! \2".$NICKS{$victim}."\2 gains two cards!"); + _nextturn(0); + } + } + } + default { + given ($ccol) { + when ('W') { + my $tcol; + given ($cval) { + when ('R') { $tcol = "\00304red\003" } + when ('B') { $tcol = "\00312blue\003" } + when ('G') { $tcol = "\00303green\003" } + when ('Y') { $tcol = "\00308yellow\003" } + } + privmsg($net, $chan, "\2".$NICKS{$CURRTURN}."\2 changes color to \2$tcol\2."); + _nextturn(0); + } + when ('WD4') { + my $tcol; + given ($cval) { + when ('R') { $tcol = "\00304red\003" } + when ('B') { $tcol = "\00312blue\003" } + when ('G') { $tcol = "\00303green\003" } + when ('Y') { $tcol = "\00308yellow\003" } + } + my $victim = _nextturn(2); + for (my $i = 1; $i <= 4; $i++) { _givecard($victim) } + privmsg($net, $chan, "\2".$NICKS{$CURRTURN}."\2 changes color to \2$tcol\2. \2".$NICKS{$victim}."\2 draws 4 cards and is skipped!"); + _nextturn(1); + } + when ('WHF') { + # Get more human-readable version of the color. + my $tcol; + given ($cval) { + when ('R') { $tcol = "\00304red\003" } + when ('B') { $tcol = "\00312blue\003" } + when ('G') { $tcol = "\00303green\003" } + when ('Y') { $tcol = "\00308yellow\003" } + } + # Give the next player a random amount of cards. + my $victim = _nextturn(2); + my $amnt = int rand 11; + while ($amnt == 0) { $amnt = int rand 11 } + for (my $i = 1; $i <= $amnt; $i++) { _givecard($victim) } + # All done. + privmsg($net, $chan, "\2".$NICKS{$CURRTURN}."\2 changes color to \2$tcol\2. \2".$NICKS{$victim}."\2 draws \2$amnt\2 cards and is skipped!"); + _nextturn(1); + } + when ('WAH') { + # Get more human-readable version of the color. + my $tcol; + given ($cval) { + when ('R') { $tcol = "\00304red\003" } + when ('B') { $tcol = "\00312blue\003" } + when ('G') { $tcol = "\00303green\003" } + when ('Y') { $tcol = "\00308yellow\003" } + } + # Iterate through all players. + foreach my $vplyr (keys %PLAYERS) { + # Make sure it isn't the player. + if ($vplyr ne $CURRTURN) { + my $amnt = int rand 11; + for (my $i = 1; $i <= $amnt; $i++) { _givecard($vplyr) } + } + } + # Finished. + privmsg($net, $chan, "\2".$NICKS{$CURRTURN}."\2 changes color to \2$tcol\2. All other players draw 0-10 cards!"); + _nextturn(0); + } + } + } + } + + return 1; +} + +# Subroutine for checking if a player has a card. +sub _hascard { + my ($player, $card) = @_; + + # Check for the player arrayref. + if (!defined $PLAYERS{$player}) { return } + + # Iterate through his/her cards. + foreach my $pc (@{$PLAYERS{$player}}) { + if ($pc eq $card) { return 1 } + my ($pcol, undef) = split m/[:]/, $card; + if ($pcol ne 'R' && $pcol ne 'B' && $pcol ne 'G' && $pcol ne 'Y') { + my ($hcol, undef) = split m/[:]/, $pc; + if ($pcol eq $hcol) { return 1 } + } + } + + return; +} + +# Subroutine for deleting a card from a player's hand. +sub _delcard { + my ($player, $card) = @_; + + # Check for the player arrayref. + if (!defined $PLAYERS{$player}) { return } + + # Iterate through his/her cards and delete the correct card. + for (my $i = 0; $i < scalar @{$PLAYERS{$player}}; $i++) { + if ($PLAYERS{$player}[$i] eq $card) { + undef $PLAYERS{$player}[$i]; + last; + } + else { + my ($pcol, undef) = split m/[:]/, $card; + if ($pcol !~ m/^(R|B|G|Y)$/xsm) { + my ($hcol, undef) = split m/[:]/, $PLAYERS{$player}[$i]; + if ($pcol eq $hcol) { undef $PLAYERS{$player}[$i]; last } + } + } + } + + # Rebuild his/her hand. + my @cards = []; + foreach my $hc (@{$PLAYERS{$player}}) { + if (defined $hc) { push @cards, $hc } + } + delete $PLAYERS{$player}; + $PLAYERS{$player} = []; + if (ref $cards[0] eq 'ARRAY') { shift @cards } + if (!scalar @cards) { + _gameover($player); + return -1; + } + elsif (scalar(@cards) == 1) { + my ($net, $chan) = split '/', $UNOCHAN; + privmsg($net, $chan, "\2".$NICKS{$player}."\2 has \2\00303U\003\00304N\003\00312O\003\2!"); + } + foreach (@cards) { push @{$PLAYERS{$player}}, $_ } + + return 1; +} + +# Subroutine for deleting a player. +sub _delplyr { + my ($player) = @_; + + # Check if the player exists. + if (!defined $PLAYERS{$player}) { return } + my ($net, $chan) = split '/', $UNOCHAN; + + # Delete their player data. + delete $PLAYERS{$player}; + delete $NICKS{$player}; + + # If there is only one player left, end the game. + if (keys %PLAYERS < 2) { + $UNO = $UNOW = $UNOCHAN = $ORDER = $DEALER = $CURRTURN = $TOPCARD = $DRAWN = $UNOTIME = $UNOGCC = 0; + %PLAYERS = (); + %NICKS = (); + privmsg($net, $chan, 'There is only one player left. Game over.'); + return 1; + } + + # Update state data. + if ($UNO) { + if ($DEALER eq $player) { + if ($CURRTURN eq $player) { $DEALER = _nextturn(2) } + else { $DEALER = $CURRTURN } + } + if ($CURRTURN eq $player) { _nextturn(0) } + } + + # Update order. + if ($UNO) { + my @order; + foreach (split ' ', $ORDER) { + if ($_ ne $player) { push @order, $_ } + } + $ORDER = join ' ', @order; + } + + return 1; +} + +# For when a player has won. +sub _gameover { + my ($player) = @_; + + # Update database. + my $score; + if (!$Auto::DB->selectrow_array('SELECT * FROM unoscores WHERE player = "'.$player.'"')) { + $Auto::DB->do('INSERT INTO unoscores (player, score) VALUES ("'.$player.'", "0")') or err(3, "Unable to update UNO score for $player!", 0); + $score = 0; + } + else { + $score = $Auto::DB->selectrow_array('SELECT score FROM unoscores WHERE player = "'.$player.'"') or err(3, "Unable to update UNO score for $player!", 0); + } + $score++; + $Auto::DB->do('UPDATE unoscores SET score = "'.$score.'" WHERE player = "'.$player.'"') or err(3, "Unable to update UNO score for $player!", 0); + + # Declare their victory. + my ($net, $chan) = split '/', $UNOCHAN; + privmsg($net, $chan, "Game over. \2".$NICKS{$player}."\2 is victorious! Bringing his/her score to \2$score\2! Congratulations!"); + my ($hours, $mins, $secs); + $hours = $mins = $secs = 0; + my $durtime = time - $UNOTIME; + while ($durtime >= 3600) { $hours++; $durtime -= 3600 } + while ($durtime >= 60) { $mins++; $durtime -= 60 } + while ($durtime >= 1) { $secs++; $durtime -= 1 } + privmsg($net, $chan, "Game lasted $hours:$mins:$secs; $UNOGCC cards were played."); + + # Reset variables. + $UNO = $UNOW = $UNOCHAN = $ORDER = $DEALER = $CURRTURN = $TOPCARD = $DRAWN = $UNOTIME = $UNOGCC = 0; + %PLAYERS = (); + %NICKS = (); + + return 1; +} + +# Subroutine for when someone changes their nick. +sub on_nick { + my (($src, $newnick)) = @_; + + # Check if a game is currently running. + if ($UNO or $UNOW) { + # There is. + + # Check if the user is playing. + if (defined $PLAYERS{lc $src->{nick}}) { + # Update data. + $PLAYERS{lc $newnick} = $PLAYERS{lc $src->{nick}}; + $NICKS{lc $newnick} = $newnick; + if ($UNO) { + my @order = split ' ', $ORDER; + for (my $i = 0; $i < scalar @order; $i++) { + if ($order[$i] eq lc $src->{nick}) { + $order[$i] = lc $newnick; + } + } + $ORDER = join ' ', @order; + } + if ($UNO) { if ($CURRTURN eq lc $src->{nick}) { $CURRTURN = lc $newnick } } + if ($DEALER eq lc $src->{nick}) { $DEALER = lc $newnick } + # Delete garbage. + delete $PLAYERS{lc $src->{nick}}; + delete $NICKS{lc $src->{nick}}; + } + } + + return 1; +} + +# Subroutine for when someone disconnects. +sub on_quit { + my (($src, undef)) = @_; + + # Check if a game is currently running. + if ($UNO or $UNOW) { + # There is. + + # Check if the user is playing. + if (defined $PLAYERS{lc $src->{nick}}) { + my ($net, $chan) = split '/', $UNOCHAN; + privmsg($net, $chan, "\2$src->{nick}\2 left the game."); + _delplyr(lc $src->{nick}); + } + } + + return 1; +} + +# Subroutine for when someone parts. +sub on_part { + my (($src, $chan, undef)) = @_; + + # Check if a game is currently running. + if ($UNO or $UNOW) { + # There is. + + # Check if this is the channel UNO is in. + my ($net, $uchan) = split '/', $UNOCHAN; + if ($src->{svr} eq $net and lc $chan eq $uchan) { + # Check if the user is playing. + if (defined $PLAYERS{lc $src->{nick}}) { + privmsg($net, $uchan, "\2$src->{nick}\2 left the game."); + _delplyr(lc $src->{nick}); + } + } + } + + return 1; +} + +# Subroutine for when someone is kicked. +sub on_kick { + my (($src, $chan, $user, undef)) = @_; + + # Check if a game is currently running. + if ($UNO or $UNOW) { + # There is. + + # Check if this is the channel UNO is in. + my ($net, $uchan) = split '/', $UNOCHAN; + if ($src->{svr} eq $net and lc $chan eq $uchan) { + # Check if the user is playing. + if (defined $PLAYERS{lc $user}) { + privmsg($net, $uchan, "\2$user\2 left the game."); + _delplyr(lc $user); + } + } + } +} + +# Subroutine for when a rehash occurs. +sub on_rehash { + # Ensure a game isn't running right now. + if ($UNO or $UNOW) { awarn(3, 'on_rehash: Unable to update UNO edition: A game is currently running.'); return } + + # Check if the edition is specified. + if (conf_get('uno:edition')) { + # Check if the edition is valid. + my $ce = (conf_get('uno:edition'))[0][0]; + $ce = lc $ce; + if ($ce !~ m/^(original|super|advanced|any)$/xsm) { + awarn(3, 'on_rehash: Unable to update UNO edition: Invalid edition \''.$ce.'\''); + return; + } + + # Set new edition. + $ce = uc(substr $ce, 0, 1).substr $ce, 1; + if ($ce eq 'Any') { $ANYEDITION = 1 } + else { $ANYEDITION = 0 } + $EDITION = $ce; + } + + return 1; +} + + +# Start initialization. +API::Std::mod_init('UNO', 'Xelhua', '1.05', '3.0.0a8', __PACKAGE__); +# build: perl=5.010000 + +__END__ + +=head1 NAME + +UNO - Three editions of the UNO card game + +=head1 VERSION + + 1.05 + +=head1 SYNOPSIS + + # config block + uno { + edition "original"; + } + + !uno start + starcoder has started UNO for Auto (Original Edition). UNO JOIN to join the game. + !uno join + Crystal has joined the game. + !uno deal + -blue- Your cards are: [2] [1] [9] [8] [S] [2] [D2] + starcoder has dealt the cards. Game begin. + Crystal's turn. Top Card: [4] + !uno play g d2 + Crystal plays [D2] + starcoder draws 2 cards and is skipped! + Crystal's turn. Top Card: [D2] + +=head1 DESCRIPTION + +This module adds the complete functionality of the classic card game, UNO, to +Auto, with three editions (Original, Super, Advanced) for endless hours of fun. + +See DIFFERENCES BETWEEN EDITIONS for the differences between the editions. + +The commands this adds are: + + UNO START|S [edition] + UNO JOIN|J + UNO DEAL + UNO PLAY|P [player if Trade Hands card] + UNO DRAW|D + UNO PASS + UNO CARDS|C + UNO TOPCARD|TC + UNO STATS|CARDCOUNT|CC + UNO KICK|K + UNO QUIT|Q + UNO STOP + UNO TOPTEN|TOP10|T10 + UNO SCORE + +All of which describe themselves quite well with just the name. + +This module is compatible with Auto v3.0.0a8+. + +=head1 INSTALL + +You must add the following to your configuration file: + + uno { + edition "edition here"; + } + +Edition can be "original", "super", "advanced" or "any". + +If any, edition must be specified per-game in START. + +You may also add the reschan option to the block, like so: + + reschan "/"; + +This will restrict use of UNO to the specified channel. Often useful since only +one channel at a time may use UNO. + +=head1 DIFFERENCES BETWEEN EDITIONS + +This is a list of differences between the three editions. + +=over + +=item Original + +This is the original UNO card game, unmodified except that the 0 card is +disabled as it is reserved for after a wildcard is used. + +=item Super + +This edition is based on the UNO Attack game, if you're already familiar with +UNO Attack, then no need to read this as it is unmodified other than instead of +0-12 cards, you get 0-10 cards when drawing. + +Differences from Original: + +* When drawing cards, instead of a set number, a random amount between 0 (0 not +always used) and 10. + +* The Trade Hands (T) card, which allows trading your hand with another player. + +* The Discard All (X) card, which discards all the cards of the same color from +the player's hand. + +* The Wild Hit Fire (WHF) card (replaces WD4), which changes the color and +gives the next player 1-10 cards (0 is disabled here), as well as skips them. + +* The Wild All Hit card, which changes the color and gives all other players 0- +10 cards. Play continues as normal. + + +=item Advanced + +This is Xelhua's own edition, based on Super with two new cards. + +Differences from Super: + +* The Bomb (B) card, which gives all other players a static amount of 7 cards. + +* The Wizard (W) card, which selects a random player and reveals their hand to +the user, as well as gives them two new cards that were not shown to the user. + +=back + +=head1 AUTHOR + +This module was written by Elijah Perrault. + +This module is maintained by Xelhua Development Group. + +=head1 LICENSE AND COPYRIGHT + +This module is Copyright 2010-2011 Xelhua Development Group. + +Released under the same licensing terms as Auto itself. + +=cut + +# vim: set ai et sw=4 ts=4: