html/quiz.pl


   1 # Quiz script for irssi
   2 # (C) Simon Huggins 2001
   3 # huggie@earth.li
   4 
   5 # This program is free software; you can redistribute it and/or modify it
   6 # under the terms of the GNU General Public License as published by the Free
   7 # Software Foundation; either version 2 of the License, or (at your option)
   8 # any later version.
   9 #
  10 # This program is distributed in the hope that it will be useful, but
  11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  12 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  13 # for more details.
  14 #
  15 # You should have received a copy of the GNU General Public License along
  16 # with this program; if not, write to the Free Software Foundation, Inc., 59
  17 # Temple Place, Suite 330, Boston, MA 02111-1307  USA
  18 #
  19 # TODO:
  20 # 	- Do something when people quit (remove from team, readd when
  21 # 	rejoin?)
  22 # 	- Store questions asked in a file rather than just in memory so it
  23 # 	can be restarted without a problem.
  24 
  25 use strict;
  26 use vars qw($VERSION %IRSSI);
  27     
  28 use Irssi 20020217.1542 (); # Version 0.8.1
  29 $VERSION = "0.7";
  30 %IRSSI = (
  31 authors     => "Simon Huggins",
  32 contact     => "huggie-irssi\@earth.li",
  33 name        => "Quiz",
  34 description => "Turns irssi into a quiz bot",
  35 license     => "GPLv2",
  36 url         => "http://the.earth.li/~huggie/irssi/",
  37 changed     => "Wed Apr 24 01:12:01 BST 2002",
  38 );
  39 
  40 use Irssi::Irc;
  41 use Data::Dumper;
  42 
  43 Irssi::settings_add_str("misc","quiz_admin","huggie");
  44 Irssi::settings_add_str("misc","quiz_passwd","stuff");
  45 Irssi::settings_add_str("misc","quiz_file","/home/huggie/.irssi/questions");
  46 
  47 Irssi::settings_add_int("misc","quiz_qlength",60);
  48 Irssi::settings_add_int("misc","quiz_hints",4);
  49 Irssi::settings_add_int("misc","quiz_target_score",10);
  50 Irssi::settings_add_int("misc","quiz_leave_concealed_chars",1);
  51 
  52 Irssi::command("set cmd_queue_speed 2010");
  53 
  54 {
  55 my $s;
  56 
  57 sub load_questions($$) {
  58 	my ($game,$force) = @_;
  59 	my $tag = $game->{'tag'};
  60 	my $channel = $game->{'channel'};
  61 
  62 	my $server = Irssi::server_find_tag($tag);
  63 
  64 	if (!defined $server) {
  65 		Irssi::print("Hrm, couldn't find server for tag ($tag) in load_questions");
  66 		return;
  67 	}
  68 
  69 	return if $game->{'questions'} and not $force;
  70 
  71 	my $file = Irssi::settings_get_str("quiz_file");
  72 	if (open(QS, "<$file")) {
  73 		@{$game->{'questions'}}=<QS>;
  74 		close(QS);
  75 		Irssi::print("Loaded questions");
  76 		return 1;
  77 	} else {
  78 		$server->command("msg $channel Can't find quiz questions, sorry.");
  79 		return;
  80 	}
  81 }
  82 
  83 sub start_game($) {
  84 	my $game = shift;
  85 	my $tag = $game->{'tag'};
  86 	my $channel = $game->{'channel'};
  87 	my $server = Irssi::server_find_tag($tag);
  88 
  89 	if (!defined $server) {
  90 		Irssi::print("Hrm, couldn't find server for tag ($tag) in start_game");
  91 		return;
  92 	}
  93 
  94 	Irssi::timeout_remove($game->{'timeouttag'});
  95 	undef $game->{'timeouttag'};
  96 
  97 	if (!keys %{$game->{'teams'}}) {
  98 		$server->command("msg $channel Sorry no one joined!");
  99 		$game->{'state'} = "over";
 100 		game_over($game);
 101 		return;
 102 	}
 103 	$game->{'state'} = "game";
 104 
 105 	$server->command("msg $channel Game starts now. Questions last ".
 106 		Irssi::settings_get_int("quiz_qlength").
 107 		" seconds and there are ".
 108 		(Irssi::settings_get_int("quiz_hints")-1).
 109 		" hints.  First to reach ".
 110 		Irssi::settings_get_int("quiz_target_score")." wins.");
 111 	next_question($game);
 112 }
 113 
 114 sub show_scores($) {
 115 	my $game = shift;
 116 	my $tag = $game->{'tag'};
 117 	my $channel = $game->{'channel'};
 118 	my $server = Irssi::server_find_tag($tag);
 119 
 120 	if (!defined $server) {
 121 		Irssi::print("Hrm, couldn't find server for tag ($tag) in show_scores");
 122 		return;
 123 	}
 124 
 125 	my (@redscorers,@bluescorers);
 126 
 127 	foreach my $score (sort keys %{$game->{'scores'}}) {
 128 		if ($score =~ /^blue/) {
 129 			$score =~ s/^blue//;
 130 			push @bluescorers, "$score(".
 131 				$game->{'scores'}->{"blue".$score}.")";
 132 		} else {
 133 			$score =~ s/^red//;
 134 			push @redscorers, "$score(".
 135 				$game->{'scores'}->{"red".$score}.")";
 136 		}
 137 	}
 138 
 139 	$server->command("msg $channel 12Blue: ".$game->{'bluescore'}
 140 		."  ".join(",",@bluescorers));
 141 	$server->command("msg $channel 4Red : ".$game->{'redscore'}
 142 		."  ".join(",",@redscorers));
 143 	
 144 	my $ts = Irssi::settings_get_int("quiz_target_score");
 145 	if ($game->{'bluescore'} == $ts or $game->{'redscore'} == $ts) {
 146 		if ($game->{'bluescore'} == $ts) {
 147 			$server->command("msg $channel 12Blue team wins ".
 148 				$game->{'bluescore'}." to ".
 149 				$game->{'redscore'});
 150 		} else {
 151 			$server->command("msg $channel 4Red team wins ".
 152 				$game->{'redscore'}." to ".
 153 				$game->{'bluescore'});
 154 		}
 155 		$game->{'state'}="over";
 156 	} elsif ($game->{'state'} ne "over") {
 157 		$game->{'state'}="pause";
 158 		$server->command("msg $channel Next question in 20 seconds.");
 159 		if ($game->{'timeouttag'}) {
 160 			Irssi::timeout_remove($game->{'timeouttag'});
 161 		}
 162 		$game->{'timeouttag'} = Irssi::timeout_add(20000,
 163 			"next_question",$game);
 164 		$game->{'timeout'} = time() + 20;
 165 	}
 166 	game_over($game);
 167 }
 168 
 169 sub hint($) {
 170 	my $game = shift;
 171 	my $tag = $game->{'tag'};
 172 	my $channel = $game->{'channel'};
 173 	my $server = Irssi::server_find_tag($tag);
 174 
 175 	if (!defined $server) {
 176 		Irssi::print("Hrm, couldn't find server for tag ($tag) in hint");
 177 		return;
 178 	}
 179 
 180 	return if game_over($game);
 181 	if ($game->{'end'} <= time()) {
 182 		$server->command("msg $channel Time's up.  The answer is: ".$game->{'answer'});
 183 		show_scores($game);
 184 	} else {
 185 		$game->{'hint'}++;
 186 		my $num = $game->{'current_answer'} =~ s/\*/*/g;
 187 		if ($num <= Irssi::settings_get_int("quiz_leave_concealed_chars")) {
 188 			return;
 189 		}
 190 		my $pos = index($game->{'current_answer'},"*");
 191 		if ($pos >= 0) {
 192 			$game->{'current_answer'} =~ s/\*/substr($game->{'answer'},$pos,1)/e;
 193 		}
 194 		my $hinttime = $game->{'hint'}*$game->{'hintlen'};
 195 		if ($hinttime != int($hinttime)) {
 196 			$hinttime = sprintf("%.2f", $hinttime);
 197 		}
 198 		$server->command("msg $channel $hinttime second hint: ".
 199 			$game->{'current_answer'});
 200 	}
 201 }
 202 
 203 sub game_over($) {
 204 	my $game = shift;
 205 	my $tag = $game->{'tag'};
 206 	my $channel = $game->{'channel'};
 207 	my $server = Irssi::server_find_tag($tag);
 208 
 209 	if (!defined $server) {
 210 		Irssi::print("Hrm, couldn't find server for tag ($tag) in game_over");
 211 		return;
 212 	}
 213 
 214 	if ($game->{'state'} eq "over") {
 215 		Irssi::timeout_remove($game->{'timeouttag'});
 216 		undef $game->{'timeouttag'};
 217 		undef $game->{'state'};
 218 		undef $game->{'teams'};
 219 		undef $game->{'scores'};
 220 		$server->command("msg $channel Trivia is disabled.  Use !trivon to restart.");
 221 		return 1;
 222 	}
 223 	return;
 224 }
 225 
 226 sub next_question($) {
 227 	my $game = shift;
 228 	my $tag = $game->{'tag'};
 229 	my $channel = $game->{'channel'};
 230 	my $server = Irssi::server_find_tag($tag);
 231 
 232 	if (!defined $server) {
 233 		Irssi::print("Hrm, couldn't find server for tag ($tag) in next_question");
 234 		return;
 235 	}
 236 
 237 	my $len = Irssi::settings_get_int("quiz_qlength")/
 238 		Irssi::settings_get_int("quiz_hints");
 239 	if ($game->{'timeouttag'}) {
 240 		Irssi::timeout_remove($game->{'timeouttag'});
 241 	}
 242 	$game->{'timeouttag'} = Irssi::timeout_add($len*1000, "hint",$game);
 243 	my $t = time();
 244 	$game->{'timeout'} = $t + $len;
 245 	$game->{'end'} = Irssi::settings_get_int("quiz_qlength")+$t;
 246 	$game->{'hint'}=0;
 247 	$game->{'hintlen'} = $len;
 248 	if (!@{$game->{'questions'}}) {
 249 		load_questions($game,1);
 250 		if (!$game->{'questions'}) {
 251 			$server->command("msg $channel Hmmm, no questions found sorry");
 252 			$game->{'state'}="over";
 253 		}
 254 		Irssi::print("Questions looped");
 255 	}
 256 	return if game_over($game);
 257 	my $q = splice(@{$game->{'questions'}},rand(@{$game->{'questions'}}),1);
 258 	chomp $q;
 259 	$q =~ s/
//;
 260 	($game->{'answer'} = $q) =~ s/^(.*)\|//;
 261 	$server->command("msg $channel Question:  $1");
 262 	($game->{'current_answer'} = $game->{'answer'}) =~ s/[a-zA-Z0-9]/*/g;
 263 	$q = s/^(.*)\|.*?$/$1/;
 264 	$server->command("msg $channel Answer:  ".$game->{'current_answer'});
 265 	$game->{'state'}="question";
 266 }
 267 
 268 sub invite_join($$) {
 269 	my ($server,$channel) = @_;
 270 	my $game = $s->{$server->{'tag'}}->{$channel};
 271 
 272 	$server->command("msg $channel Team Trivia thingummie v($VERSION) starts in 1 minute.  Type 4!join red or 12!join blue");
 273 	$game->{'timeouttag'} = Irssi::timeout_add(60000,"start_game",$game);
 274 	$game->{'timeout'} = time()+60;
 275 }
 276 
 277 sub secstonormal($) {
 278 	my $seconds = shift;
 279 	my ($m,$s);
 280 
 281 	$s = $seconds % 60;
 282 	$m = ($seconds - $s)/60;
 283 	return sprintf("%02d:%02d",$m,$s);
 284 }
 285 
 286 sub do_pubcommand($$$$) {
 287 	my ($command,$channel,$server,$nick) = @_;
 288 	my $game = $s->{$server->{'tag'}}->{$channel};
 289 
 290 	$command = lc $command;
 291 	$command =~ s/\s*$//;
 292 
 293 	if ($command =~ /^!bang$/) {
 294 		$server->command("msg $channel Dumping...");
 295 		foreach (split /\n/,Dumper($s)) {
 296 			Irssi::print("$_");
 297 		}
 298 	} elsif ($command =~ /^!trivon$/) {
 299 		if ($s->{$server->{'tag'}}->{$channel}) {
 300 			if ($s->{$server->{'tag'}}->{$channel}->{'state'}) {
 301 				$server->command("msg $nick Trivia is already on.  Use !trivoff to remove it.");
 302 				return;
 303 			}
 304 			#undef $s->{$server->{'tag'}}->{$channel};
 305 		} else {
 306 			# create structure magically
 307 			$game = $s->{$server->{'tag'}}->{$channel} = {};
 308 			$game->{'tag'} = $server->{'tag'};
 309 			$game->{'channel'} = $channel;
 310 		}
 311 		$game->{'teams'}={};
 312 		$game->{'redscore'} = 0;
 313 		$game->{'bluescore'} = 0;
 314 		load_questions($game,0);
 315 		$game->{'state'} = "join";
 316 		invite_join($server,$channel);
 317 	} elsif ($command =~ /^!trivoff$/) {
 318 		return if !$game->{'state'};
 319 		$game->{'state'}="over";
 320 		game_over($game);
 321 	} elsif ($command =~ /^!join/) {
 322 		if ($command =~ /^!join (red|blue)$/) {
 323 			return if !$game->{'state'};
 324 			$game->{'teams'}->{$nick}=$1;
 325 			if ($1 eq "blue") {
 326 				$server->command("msg $nick You have joined the 12Blue team");
 327 			} else {
 328 				$server->command("msg $nick You have joined the 4Red team");
 329 			}
 330 		}
 331 	} elsif ($command =~ /^!teams/) {
 332 		return if !$game->{'state'};
 333 		my @blue=();
 334 		my @red=();
 335 		foreach (sort keys %{$game->{'teams'}}) {
 336 			push @blue, $_ if $game->{'teams'}->{$_} eq "blue";
 337 			push @red,  $_ if $game->{'teams'}->{$_} eq "red";
 338 		}
 339 		$server->command("msg $channel 12Blue: ".join(",",@blue));
 340 		$server->command("msg $channel 4Red : ".join(",",@red));
 341 	} elsif ($command =~ /^!timeleft$/) {
 342 		if ($game->{'state'} eq "join" and $game->{'timeout'}) {
 343 			my $diff = $game->{'timeout'} - time();
 344 			if ($diff > 0) {
 345 				$server->command("msg $channel Time left: ".secstonormal($diff));
 346 			} else {
 347 				Irssi::print("Timeleft: $diff ??");
 348 			}
 349 		}
 350 	}
 351 }
 352 
 353 sub do_command($$$) {
 354 	my ($command,$nick,$server) = @_;
 355 
 356 	$command = lc $command;
 357 	$command =~ s/\s*$//;
 358 
 359 	if ($command =~ /^!bang$/) {
 360 		$server->command("msg $nick BOOM!");
 361 	} elsif ($command =~ /^admin/) {
 362 		if ($command !~ /^admin (.*)$/) {
 363 			$server->command("msg $nick admin needs a nick to change the admin user to!");
 364 		} else {
 365 			Irssi::settings_remove("quiz_admin");
 366 			Irssi::settings_add_str("misc","quiz_admin",$1);
 367 			$server->command("msg $nick admin user is now $1");
 368 		}
 369 	} else {
 370 		$server->command("msg $nick Unknown command '$command'");
 371 	}
 372 }
 373 
 374 sub check_answer($$$$) {
 375 	my ($server,$channel,$nick,$text) = @_;
 376 	my $game = $s->{$server->{'tag'}}->{$channel};
 377 
 378 	return if not exists $game->{'teams'}->{$nick};
 379 
 380 	$text =~ s/\s*$//;
 381 
 382 	if (lc $text eq lc $game->{'answer'}) {
 383 		$server->command("msg $channel Correct answer by ".
 384 		($game->{'teams'}->{$nick} eq "blue"?"12":"4").
 385 		$nick.": ".$game->{'answer'});
 386 		$game->{'state'}="won";
 387 		$game->{$game->{'teams'}->{$nick}."score"}++;
 388 		$game->{'scores'}->{$game->{'teams'}->{$nick}.$nick}++;
 389 		show_scores($game);
 390 		return;
 391 	}
 392 
 393 	my $show=0;
 394 	my @chars = split //,$text;
 395 
 396 	for (my $i=0; $i<length($game->{'answer'}); $i++) {
 397 		if (lc $chars[$i] eq lc substr($game->{'answer'},$i,1)) {
 398 			$show = 1 if substr($game->{'current_answer'},$i,1)
 399 				eq "*";
 400 			substr($game->{'current_answer'},$i,1) =
 401 				substr($game->{'answer'},$i,1);
 402 		}
 403 	}
 404 	$server->command("msg $channel Answer: ".$game->{'current_answer'})
 405 		if $show;
 406 }
 407 
 408 sub event_privmsg {
 409 	my ($server,$data,$nick,$address) = @_;
 410 	my ($target, $text) = split / :/,$data,2;
 411 	my ($command);
 412 
 413 	if ($target =~ /^#/) {
 414 		my $game = $s->{$server->{'tag'}}->{$target};
 415 		if ($text =~ /^!/) {
 416 			do_pubcommand($text,$target,$server,$nick);
 417 		} elsif ($game->{'state'} eq "question") {
 418 			check_answer($server,$target,$nick,$text);
 419 		}
 420 	} else {
 421 		if ($nick ne Irssi::settings_get_str("quiz_admin")) {
 422 			my ($passwd);
 423 			($passwd, $command) = split /\s/,$text,2;
 424 			if ($passwd ne Irssi::settings_get_str("quiz_passwd")) {
 425 				Irssi::print("$nick tried to do $command but got the password wrong.");
 426 			}
 427 		} else {
 428 			$command = $text;
 429 		}
 430 		do_command($command,$nick,$server);
 431 	}
 432 }
 433 
 434 sub event_changed_nick {
 435 	my ($channel,$nick,$oldnick) = @_;
 436 	my $server = $channel->{'server'};
 437 	my $game = $s->{$server->{'tag'}}->{$channel->{'name'}};
 438 
 439 	return if !$game->{'state'};
 440 	
 441 	my $nicktxt = $nick->{'nick'};
 442 	if ($game->{'teams'}->{$oldnick}) {
 443 		$game->{'teams'}->{$nicktxt} = $game->{'teams'}->{$oldnick};
 444 		delete $game->{'teams'}->{$oldnick};
 445 	}
 446 }
 447 
 448 }
 449 
 450 Irssi::signal_add_last("event privmsg", "event_privmsg");
 451 Irssi::signal_add("nicklist changed", "event_changed_nick");