diff options
Diffstat (limited to 'scripts/iquiz_en.pl')
| -rw-r--r-- | scripts/iquiz_en.pl | 285 | 
1 files changed, 167 insertions, 118 deletions
| diff --git a/scripts/iquiz_en.pl b/scripts/iquiz_en.pl index 14b55f5..883ef65 100644 --- a/scripts/iquiz_en.pl +++ b/scripts/iquiz_en.pl @@ -1,5 +1,5 @@  ################################################################## -##    irssi Quiz (iQuiz) script (2010-2016) by wilk/xorandor    ## +##    irssi Quiz (iQuiz) script (2010-2017) by wilk/xorandor    ##  ##################################################################  ## Script inspired by classic mIRC scripts: "Dizzy" by Dizzy,   ##  ##   "Mieszacz" & "Familiada" by snajperx (both with my later   ## @@ -12,7 +12,7 @@  # Script works with:  # -# - standard Dizzy/Pomieszany files (also without "pyt"/"odp" prefixes): +# - standard "Dizzy"/"Pomieszany" files (also without "pyt"/"odp" prefixes):  #  # pyt Evaluate: 2+2=?  # odp four @@ -20,7 +20,7 @@  # odp Sun  # ...  # -# - standard Mieszacz files (also without line numbers): +# - standard "Mieszacz" files (also without line numbers):  #  # 1 alpha  # 2 beta @@ -28,7 +28,7 @@  # 4 delta  # ...  # -# - standard Familiada files (can have any number of answers per question, used also for Multi): +# - standard "Familiada" files (can have any number of answers per question, not only 10; used also for "Multi"):  #  # Planets of our Solar System:  # Mercury*Venus*Earth*Mars*Jupiter*Saturn*Uranus*Neptune @@ -36,16 +36,15 @@  # methane*ethane*propane*butane*pentane*hexane  # ... -# >>> To view all available commands and settings type: /quiz +# >>> To review all available commands and settings type: /quiz OR /help quiz  # only core modules  use strict;  use warnings;  use Irssi qw(theme_register current_theme command_bind settings_add_int settings_add_bool settings_add_str settings_get_int settings_get_bool settings_get_str settings_set_int settings_set_bool settings_set_str printformat timeout_add_once timeout_remove signal_add_last signal_remove signal_stop signal_emit active_win);  use Time::HiRes qw(time); -use constant { QT_STD => 1, QT_MIX => 2, QT_FAM => 3, QT_MUL => 4, QT_SCR => 5 }; # QT_MIL => 6, QT_FOR => 7 -our $VERSION = '160919_en'; +our $VERSION = '170202_en';  our %IRSSI = (  	authors			=> 'wilk',  	name			=> 'iQuiz', @@ -72,41 +71,54 @@ my $_protect_urls = 1;				# bool; turn off antigoogler if URL is detected in que  my $_round_warn_time = 15;			# sec; seconds before round end to show warning (0 = off)  my $_round_warn_coeff = 1.5;		# float; round duration must be longer than coeff * $_round_warn_time to show warning (protection)  my $_qstats_ranks = 0;				# bool; 0: /qstats param corresponds to number of players, 1: /qstats param corresponds to rank -my $_qstats_records = 5;			# int; number of time/speed record places in /qstats +my $_qstats_records = 5;			# int; number of time/speed record places in /qstats (0 - off) +my $_no_hints_spam = 1;				# bool; do not show fully revealed answer as a hint (except the first)? + +my $_flex_separator = '/';			# char; separator used in quiz_flx_* formats +my $_team_separator = ', ';			# str; team players separator eq. "nick1, nick2, nick3"  my $_next_delay = 10;				# sec; default delay between questions  my $_next_delay_long = 20;			# sec; default delay between questions (fam/mul) (longer delay to prevent flooding and give a breath)  my $_round_duration = 90;			# sec; default round duration +my $_hints_interval = 10;			# sec; default hints interval  my $_hint_alpha = '.';				# char; default substitution symbol for alphabet characters in hints (special characters are left intact)  my $_hint_digit = '.';				# char; default substitution symbol for digit characters in hints (special characters are left intact)  my $_quiz_types = 5;				# (do not change)  ##### Internal stuff ##### +use constant { QT_STD => 1, QT_MIX => 2, QT_FAM => 3, QT_MUL => 4, QT_SCR => 5 }; # QT_MIL => 6, QT_FOR => 7  use constant { T_HMS => 0, T_S => 1, T_MS => 2 }; # 0: h/m/s, 1: s only, 2: s.ms  use constant { INSTANT => 1, PREPDOTS => 1, V_INT => 1, V_BOOL => 2, V_STR => 3 };  my %quiz = (  	chan => undef, file => '',  	type => 0, tcnt => 0, # copies just in case someone modifies settings directly while quiz is running -	ison => 0, inq => 0, standby => 0, +	ison => 0,		# /qon ... /qoff (or error while /qreload) +	inq => 0,		# in question +	standby => 0,	# /qon QT_FAM ... /qon (/qoff or error in /qreload) +	ended => 0,		# after last question till /qoff (or error in /qreload)  	stime => 0, qtime => 0,  	qcnt => 0, qnum => 0, hnum => 0,  	score => 0, answers => 0, -	tnext => undef, tround => undef, thint => undef, tremind => undef, twarn => undef, +	tnext => undef, tround => undef, thint => undef, thinter => undef, tremind => undef, twarn => undef,  	hprot => 0, rprot => 0,  	data => [],		# data[]{question realquestion answer answers{}}  	teams => [],	# teams[]{score answers} -	players => {},	# players{}{nick timestamp score answers team besttime alltime bestspeed allspeed} -	lookup => {}, dcnt => 0, dmax => 0, lmax => 0, dots => [], hwords => [] +	players => {},	# players{}{nick timestamp score answers team joined besttime alltime bestspeed allspeed} +	lookup => {}, +	dcnt => 0,		# total number of currently unrevealed letters +	dmax => 0,		# number of currently unrevealed letters in the "most hidden" word +	dots => [], hwords => []  );  my %settings_int = ( -	'quiz_type' => 1, +	'quiz_type' => QT_STD,  	'quiz_teams' => 2,  	'quiz_delay' => $_next_delay,  	'quiz_delay_long' => $_next_delay_long, -	'quiz_round_duration' => $_round_duration, +	'quiz_timeout' => $_round_duration, +	'quiz_hints_interval' => $_hints_interval,  	'quiz_max_hints' => 0,  	'quiz_words_style' => 0,  	'quiz_anticheat_delay' => 3, @@ -120,6 +132,7 @@ my %settings_int = (  my %settings_bool = (  	'quiz_antigoogler' => 1, +	'quiz_autohinter' => 0,  	'quiz_split_long_lines' => 1,  	'quiz_show_first_hint' => 0,  	'quiz_first_hint_dots' => 0, @@ -134,6 +147,7 @@ my %settings_bool = (  	'quiz_transfer_points' => 0,  	'quiz_limiter' => 0,  	'quiz_keep_scores' => 0, +	'quiz_keep_teams' => 1,  	'quiz_cmd_hint' => 1,  	'quiz_cmd_remind' => 1,  ); @@ -180,7 +194,7 @@ theme_register([  	'quiz_err_na',			'%_iQuiz:%_ %RFeature is not available at this time%n',  	'quiz_msg',						'%s', # custom text -	'quiz_msg_start1',				"\00303>>> \00310iQuiz by wilk - let's start \00303<<<", +	'quiz_msg_start1',				"\00303>>> \00310iQuiz by wilk - let's start! \00303<<<",  	'quiz_msg_start2',				"\00303Commands: !hint, !remind, !score, !score nick",  	'quiz_msg_start2_f',			"\00303Commands: !remind, !score, !score nick, !join 1-%u", # 1: max teams  	'quiz_msg_start2_m',			"\00303Commands: !remind, !score, !score nick", @@ -201,7 +215,7 @@ theme_register([  	'quiz_msg_score_other',			"\00304%s\00303 scored \00304%d\00303 %s so far.", # see quiz_msg_score  	'quiz_msg_noscore_other',		"\00304%s\00303 hasn't scored any point so far!", # 1: nick  	'quiz_msg_noscores',			"\00303Scoreboard is empty.", -	'quiz_msg_scores',				"\00303Quiz scores after %s and %u %s:", # 1: time_str (hms), 2: question, 3: quiz_flx_questions, 4: questions (total), 5: quiz_flx_questions (total) +	'quiz_msg_scores',				"\00303Quiz scores after %s and %u %s:", # 1: time_str (hms), 2: question, 3: quiz_flx_aquestions, 4: questions (total), 5: quiz_flx_fquestions (total)  	'quiz_msg_scores_place',		"\00303%u. place: \00304%s\00303 - \00304%d\00303 %s [%.1f%%] (avg. guessing time: %10\$.3fs)", # 1: place, 2: nick, 3: score, 4: quiz_flx_points, 5: score%, 6: answers, 7: quiz_flx_answers, 8: answers%, 9: best time, 10: avg time, 11: best speed, 12: avg speed, 13: spacer  	'quiz_msg_scores_place_full',	"\00303%u. place: \00304%s\00303 - \00304%d\00303 %s [%.1f%%] (%u %s, avg. guessing time: %10\$.3fs)", # see quiz_msg_scores_place  	'quiz_msg_team_score',			"\00303Team %u (%s): \00304%d\00303 %s", # 1: team, 2: players (comma separated), 3: score, 4: quiz_flx_points, 5: score%, 6: answers, 7: quiz_flx_answers, 8: answers% @@ -214,6 +228,7 @@ theme_register([  	'quiz_msg_congrats',			"\00303Congrats, \00304%s\00303! You get %s for answer \00304%s\00303 given after %.3fs (%.3f chars/s) - total points: \00304%d\00303.", # 1: nick, 2: quiz_inc_got_point*, 3: answer, 4: time (ms), 5: speed (chars/s), 6: total score  	'quiz_inc_got_points',			"\00304%d\00303 %s", # 1: points, 2: quiz_flx_points  	'quiz_inc_got_point',			"\00303a %s", # 1: quiz_flx_point +	'quiz_msg_limit',				"\00307You've just got 50%%+1 points - let others play. ;)",  	'quiz_inc_hours',				'%u hr',			# 1: hours  	'quiz_inc_minutes',				'%u min',			# 1: minutes  	'quiz_inc_seconds',				'%u sec',			# 1: seconds @@ -221,25 +236,17 @@ theme_register([  	'quiz_msg_warn_timeout',		"\00307Warning, only \00304%u\00307s left for answering!", # 1: time (s)  	'quiz_msg_all_answers',			"\00303All answers were guessed!",  	'quiz_msg_timeout',				"\00303Timeout!", -	'quiz_msg_next',				"\00303Next question after %us...", # 1: time (s) -	'quiz_msg_next_x',				"\00303Next word after %us...", # 1: time (s) +	'quiz_msg_next',				"\00303Next question in %us...", # 1: time (s) +	'quiz_msg_next_x',				"\00303Next word in %us...", # 1: time (s)  	'quiz_msg_last',				"\00307No more questions!",  	'quiz_msg_skipped',				"\00303This question has been skipped.", -	# 1 point								/ 1 punkt -	# x points								/ x punktow -	# 2-4, x2-x4 points (x != 1)			/ 2-4, x2-x4 punkty (x != 1) +	# 1 point / x points / 2-4, x2-x4 points (x != 1)	|	1 punkt / x punktow / 2-4, x2-x4 punkty (x != 1)  	'quiz_flx_points',				'point/points/points', -	# 1 answer								/ 1 odpowiedz -	# x answers								/ x odpowiedzi -	# 2-4, x2-x4 answers (x != 1)			/ 2-4, x2-x4 odpowiedzi (x != 1) +	# 1 answer / x answers / 2-4, x2-x4 answers (x != 1)	|	1 odpowiedz / x odpowiedzi / 2-4, x2-x4 odpowiedzi (x != 1)  	'quiz_flx_answers',				'answer/answers/answers', -	# after 1 question						/ po 1 pytaniu -	# after x questions						/ po x pytaniach -	# after 2-4, x2-x4 questions (x != 1)	/ po 2-4, x2-x4 pytaniach (x != 1) +	# (after) 1 question / x questions / 2-4, x2-x4 questions (x != 1)	|	(po) 1 pytaniu / x pytaniach / 2-4, x2-x4 pytaniach (x != 1)  	'quiz_flx_aquestions',			'question/questions/questions', -	# from 1 question						/ z 1 pytania -	# from x questions						/ z x pytan -	# from 2-4, x2-x4 questions (x != 1)	/ z 2-4, x2-x4 pytan (x != 1) +	# (from) 1 question / x questions / 2-4, x2-x4 questions (x != 1)	|	(z) 1 pytania / x pytan / 2-4, x2-x4 pytan (x != 1)  	'quiz_flx_fquestions',			'question/questions/questions',  ]); @@ -308,7 +315,7 @@ sub send_irc {  		}  		timeout_add_once($_display_delay, 'evt_delayed_show_msg', $msg); # le trick (workaround for chantext showing after owntext)  	} else { -		send_ui_raw($msg); # this helps when we got disconnected not to lose messages like stats +		send_ui_raw($msg); # this helps when we got disconnected not to lose any messages (like stats)  		send_ui('quiz_err_server');  	}  } @@ -324,7 +331,7 @@ sub send_irc_whisper {  		}  		timeout_add_once($_display_delay, 'evt_delayed_show_notc', [$msg, $nick]); # le trick (workaround for chantext showing after owntext)  	} else { -		send_ui_raw($msg); # this helps when we got disconnected not to lose messages like stats +		send_ui_raw($msg); # this helps when we got disconnected not to lose any messages (like stats)  		send_ui('quiz_err_server');  	}  } @@ -421,7 +428,7 @@ sub antigoogle {  		if (!$quiz{inq}) {  			my $suffix = ($quiz{type} == QT_MIX) ? '_x' : ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) ? '_fm' : '');  			my $answers = keys %{$quiz{lookup}}; -			my $duration = abs(settings_get_int('quiz_round_duration')) || $_round_duration; # abs in case of <0, || in case of ==0 +			my $duration = abs(settings_get_int('quiz_timeout')) || $_round_duration; # abs in case of <0, || in case of ==0  			$msg_crap += length(get_format('quiz_msg_question' . $suffix, [$quiz{qnum}, $quiz{qcnt}, '', $answers, answers_str($answers), $duration]));  		} else {  			$msg_crap += length(get_format('quiz_msg_remind', '')); @@ -483,10 +490,10 @@ sub make_hint {  	my $dots_only = shift;  	my @words = split(/ /, $quiz{data}[$quiz{qnum}]{answer});  	if (!@{$quiz{dots}}) { # make first dots -		@quiz{qw/dcnt dmax lmax/} = (0) x 3; +		@quiz{qw/dcnt dmax/} = (0) x 2; +		$quiz{hwords} = [];  		my ($w, $dmax) = (0) x 2;  		foreach my $word (@words) { -			$quiz{lmax} = length($word) if (length($word) > $quiz{lmax});  			my ($l, $hword, $dcnt) = (0, '', 0);  			foreach my $letter (split(//, $word)) {  				if ($letter =~ /^[a-z0-9]$/i) { @@ -562,12 +569,12 @@ sub time_str {  sub flex {  	my ($value, $format, $flex) = (abs(shift), shift, 0); -	my @flex = split(/\//, get_format($format)); +	my @flex = split(/$_flex_separator/, get_format($format));  	if ($value != 1) {  		$flex++;  		$flex++ if ($value =~ /^[2-4]$|[^1][2-4]$/);  	} -	return defined($flex[$flex]) ? $flex[$flex] : '???'; # just a precaution +	return defined($flex[$flex]) ? $flex[$flex] : '???'; # just a precaution when user messes up  }  sub score_str		{ return flex(shift, 'quiz_flx_points'); }		# X points @@ -589,8 +596,8 @@ sub stop_timer {  }  sub stop_question { -	@quiz{qw/inq hnum hprot rprot dcnt dmax lmax/} = (0) x 7; -	stop_timer($_) foreach (qw/tround thint tremind twarn/); +	@quiz{qw/inq hnum hprot rprot dcnt dmax/} = (0) x 6; +	stop_timer($_) foreach (qw/tround thint thinter tremind twarn/);  	$quiz{dots} = [];  	$quiz{hwords} = [];  	$quiz{lookup} = {}; @@ -598,7 +605,7 @@ sub stop_question {  sub stop_quiz {  	stop_question(); -	@quiz{qw/ison standby/} = (0) x 2; +	@quiz{qw/ison standby ended/} = (0) x 3;  	stop_timer('tnext');  	signal_remove('message public', 'sig_pubmsg');  } @@ -616,9 +623,14 @@ sub init_next_question {  	my ($msg, $instant) = @_;  	if ($quiz{qnum} >= $quiz{qcnt}) {  		send_irc('quiz_msg', $msg . ' ' . get_format('quiz_msg_last'), $instant); +		$quiz{ended} = 1;  	} else { -		my $delay = abs(settings_get_int('quiz_delay' . ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) ? '_long' : ''))); # abs in case of <0 -		$delay ||= ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) ? $_next_delay_long : $_next_delay); # in case of ==0 +		my $delay; +		if (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) { +			$delay = abs(settings_get_int('quiz_delay_long')) || $_next_delay_long; # abs in case of <0, || in case of ==0 +		} else { +			$delay = abs(settings_get_int('quiz_delay')) || $_next_delay; # abs in case of <0, || in case of ==0 +		}  		send_irc('quiz_msg', $msg . ' ' . get_format('quiz_msg_next' . (($quiz{type} == QT_MIX) ? '_x' : ''), $delay), $instant);  		$quiz{tnext} = timeout_add_once($delay * 1000, 'evt_next_question', undef);  	} @@ -667,7 +679,7 @@ sub correct_answer {  }  sub hcmd { -	return sprintf(' %-37s - ', shift); +	return sprintf(' %-38s - ', shift);  }  sub hvar { @@ -682,53 +694,74 @@ sub hvar {  }  sub show_help { -	send_ui_raw("%_$IRSSI{name}%_ v$VERSION by wilk (quiz is currently: " . ($quiz{ison} ? ($quiz{standby} ? 'on standby' : 'running') : 'off') . ')'); +	my $type = settings_get_int('quiz_type'); +	send_ui_raw("%_$IRSSI{name}%_ v$VERSION by wilk (quiz currently: " . ($quiz{ison} ? ($quiz{standby} ? 'is on standby' : ($quiz{ended} ? 'has finished' : 'is running')) : 'is off') . ')');  	send_ui_raw('%_Available commands:%_');  	send_ui_raw(hcmd("/qtype [1-$_quiz_types/name]") . 'change quiz type (see quiz_type)'); -	send_ui_raw(hcmd("/qteams <2-$_max_teams>") . 'change number of teams (Familiada only)'); -	send_ui_raw(hcmd("/qon [channel] <file> [1-$_quiz_types/name] [0-$_max_teams]") . 'start the quiz; you can give a type'); -	send_ui_raw(hcmd('/qstats [places]') . 'display scoreboard (Familiada: 0 - teams only)'); -	send_ui_raw(hcmd('/qhint') . 'show next hint (not for Familiada/Multi)'); +	if ($type == QT_FAM) { +		send_ui_raw(hcmd("/qteams <2-$_max_teams>") . 'change number of teams'); +		send_ui_raw(hcmd("/qon [channel] <file> [1-$_quiz_types/name] [0-$_max_teams]") . 'start the quiz; you can give its type and number of teams'); +		send_ui_raw(hcmd('/qstats [places]') . 'display scoreboard (0: teams only)'); +	} else { +		send_ui_raw(hcmd("/qon [channel] <file> [1-$_quiz_types/name]") . 'start the quiz; you can give its type'); +		send_ui_raw(hcmd('/qstats [places]') . 'display scoreboard'); +	} +	send_ui_raw(hcmd('/qhint') . 'show next hint') if (($type != QT_FAM) && ($type != QT_MUL));  	send_ui_raw(hcmd('/qremind') . 'remind a question');  	send_ui_raw(hcmd('/qskip') . 'skip current question');  	send_ui_raw(hcmd('/qoff') . 'stop the quiz');  	send_ui_raw(hcmd('/qdelay <seconds>') . 'change delay between questions'); -	send_ui_raw(hcmd('/qtime <seconds>') . 'change round duration (Familiada/Multi only)'); +	send_ui_raw(hcmd('/qtime <seconds>') . 'change round duration') if (($type == QT_FAM) || ($type == QT_MUL));  	send_ui_raw(hcmd('/qreload') . 'reload questions from file');  	send_ui_raw(hcmd('/qinit') . 'reset settings to defaults');  	send_ui_raw('%_Available settings (/set):%_');  	send_ui_raw(hvar('quiz_type', V_INT) . 'quiz type (1: Dizzy, 2: Mieszacz/Literaki, 3: Familiada, 4: Multi (Familiada w/o teams), 5: Pomieszany)'); -	send_ui_raw(hvar('quiz_teams', V_INT) . "number of teams (2-$_max_teams; Familiada only)"); +	send_ui_raw(hvar('quiz_teams', V_INT) . "number of teams (2-$_max_teams)") if ($type == QT_FAM);  	send_ui_raw(hvar('quiz_delay', V_INT) . 'delay between questions (sec)'); -	send_ui_raw(hvar('quiz_delay_long', V_INT) . 'delay between questions (sec; Familiada/Multi only)'); -	send_ui_raw(hvar('quiz_round_duration', V_INT) . 'round duration (sec; Familiada/Multi only)'); -	send_ui_raw(hvar('quiz_max_hints', V_INT) . 'max. number of hints (0: no limit, >0: number of hints, <0: number of hidden chars; not for Familiada/Multi)'); -	send_ui_raw(hvar('quiz_words_style', V_INT) . 'word\'s style (0: no change, 1: l-case, 2: U-case, 3: Caps; Mieszacz/Pomieszany only)'); -	send_ui_raw(hvar('quiz_anticheat_delay', V_INT) . '!hint/!remind protection delay (sec; 0: off)'); -	send_ui_raw(hvar('quiz_first_anticheat_delay', V_INT) . 'first !hint/!remind protection delay (sec; 0: off)'); -	send_ui_raw(hvar('quiz_points_per_answer', V_INT) . 'points per answer'); -	send_ui_raw(hvar('quiz_min_points', V_INT) . 'min. points (Familiada/Multi only)'); -	send_ui_raw(hvar('quiz_max_points', V_INT) . 'max. points (Familiada/Multi only)'); -	send_ui_raw(hvar('quiz_scoring_mode', V_INT) . 'scoring mode (1: ppa, 2: ppa++, 3: ppa++:max, 4: min++ppa, 5: min++ppa:max, 6: max--ppa:min, 7: max->min; Familiada/Multi only)'); -	send_ui_raw(hvar('quiz_ranking_type', V_INT) . 'ranking type (1: ordinal "1234", 2: dense "1223", 3: competition "1224")'); +	if (($type == QT_FAM) || ($type == QT_MUL)) { +		send_ui_raw(hvar('quiz_delay_long', V_INT) . 'delay between questions (sec)'); +		send_ui_raw(hvar('quiz_timeout', V_INT) . 'round duration (sec)'); +	} else { +		send_ui_raw(hvar('quiz_max_hints', V_INT) . 'max. number of hints (0: no limit, >0: number of hints, <0: number of hidden chars)'); +		send_ui_raw(hvar('quiz_autohinter', V_BOOL) . 'show automatic hints?'); +		send_ui_raw(hvar('quiz_hints_interval', V_INT) . 'automatic hints interval (sec)'); +	} +	send_ui_raw(hvar('quiz_words_style', V_INT) . 'word\'s style (0: no change, 1: l-case, 2: U-case, 3: Caps)') if (($type == QT_MIX) || ($type == QT_SCR));  	send_ui_raw(hvar('quiz_antigoogler', V_BOOL) . 'mask questions with antigoogler?');  	send_ui_raw(hvar('quiz_split_long_lines', V_BOOL) . 'split long lines?'); -	send_ui_raw(hvar('quiz_show_first_hint', V_BOOL) . 'show questions along with first hint? (not for Familiada/Multi)'); -	send_ui_raw(hvar('quiz_first_hint_dots', V_BOOL) . 'first hint as dots only? (not for Familiada/Multi)'); -	send_ui_raw(hvar('quiz_random_hints', V_BOOL) . 'reveal random chars in hints? otherwise left to right (not for Familiada/Multi)'); -	send_ui_raw(hvar('quiz_nonrandom_first_hint', V_BOOL) . 'reveal random chars in hints, except first hint? (not for Familiada/Multi)'); -	send_ui_raw(hvar('quiz_hint_alpha', V_STR) . 'character substitution in place of letters (not for Familiada/Multi)'); -	send_ui_raw(hvar('quiz_hint_digit', V_STR) . 'character substitution in place of digits (not for Familiada/Multi)'); -	send_ui_raw(hvar('quiz_words_mode', V_BOOL) . 'scramble with word separation? otherwise all together (Mieszacz only)'); -	send_ui_raw(hvar('quiz_smart_mix', V_BOOL) . 'anchor some characters? (Pomieszany only)'); -	send_ui_raw(hvar('quiz_smart_mix_chars', V_STR) . 'anchor these characters (regex; Pomieszany only)'); -	send_ui_raw(hvar('quiz_mix_on_remind', V_BOOL) . 'scramble letters with each !remind? (Mieszacz/Pomieszany only)'); -	send_ui_raw(hvar('quiz_strict_match', V_BOOL) . 'allow only strict answers? or allow *matching* (not for Familiada/Multi)'); -	send_ui_raw(hvar('quiz_join_anytime', V_BOOL) . 'allow joining teams at any time? (Familiada only)' ); -	send_ui_raw(hvar('quiz_team_play', V_BOOL) . 'allow only team players to answer? (Familiada only)'); -	send_ui_raw(hvar('quiz_transfer_points', V_BOOL) . 'transfer scores when player changes teams? (Familiada only)'); -	send_ui_raw(hvar('quiz_limiter', V_BOOL) . 'limit best person scores to 50%+1 points? (not for Familiada/Multi)'); +	send_ui_raw(hvar('quiz_anticheat_delay', V_INT) . '!hint/!remind protection delay (sec; 0: wylaczone)'); +	send_ui_raw(hvar('quiz_first_anticheat_delay', V_INT) . 'first !hint/!remind protection delay (sec; 0: wylaczone)'); +	if (($type != QT_FAM) && ($type != QT_MUL)) { +		send_ui_raw(hvar('quiz_show_first_hint', V_BOOL) . 'show questions along with first hint?'); +		send_ui_raw(hvar('quiz_first_hint_dots', V_BOOL) . 'first hint as dots only?'); +		send_ui_raw(hvar('quiz_random_hints', V_BOOL) . 'reveal random chars in hints? otherwise left to right'); +		send_ui_raw(hvar('quiz_nonrandom_first_hint', V_BOOL) . 'reveal random chars in hints, except first hint?'); +		send_ui_raw(hvar('quiz_hint_alpha', V_STR) . 'character substitution in place of letters'); +		send_ui_raw(hvar('quiz_hint_digit', V_STR) . 'character substitution in place of digits'); +	} +	send_ui_raw(hvar('quiz_words_mode', V_BOOL) . 'scramble with word separation? otherwise all together') if ($type == QT_MIX); +	if ($type == QT_SCR) { +		send_ui_raw(hvar('quiz_smart_mix', V_BOOL) . 'anchor some characters?'); +		send_ui_raw(hvar('quiz_smart_mix_chars', V_STR) . 'anchor these characters (regex)'); +	} +	send_ui_raw(hvar('quiz_mix_on_remind', V_BOOL) . 'scramble letters with each !remind?') if (($type == QT_MIX) || ($type == QT_SCR)); +	if ($type == QT_FAM) { +		send_ui_raw(hvar('quiz_join_anytime', V_BOOL) . 'allow joining teams at any time?'); +		send_ui_raw(hvar('quiz_team_play', V_BOOL) . 'allow only team players to answer?'); +		send_ui_raw(hvar('quiz_transfer_points', V_BOOL) . 'transfer scores when player changes teams?'); +	} +	send_ui_raw(hvar('quiz_strict_match', V_BOOL) . 'allow only strict answers? or allow *matching*') if (($type != QT_FAM) && ($type != QT_MUL)); +	send_ui_raw(hvar('quiz_points_per_answer', V_INT) . 'points per answer'); +	if (($type == QT_FAM) || ($type == QT_MUL)) { +		send_ui_raw(hvar('quiz_min_points', V_INT) . 'min. points'); +		send_ui_raw(hvar('quiz_max_points', V_INT) . 'max. points'); +		send_ui_raw(hvar('quiz_scoring_mode', V_INT) . 'scoring mode (1: ppa, 2: ppa++, 3: ppa++:max, 4: min++ppa, 5: min++ppa:max, 6: max--ppa:min, 7: max->min'); +	} else { +		send_ui_raw(hvar('quiz_limiter', V_BOOL) . 'limit best person scores to 50%+1 points?'); +	} +	send_ui_raw(hvar('quiz_ranking_type', V_INT) . 'ranking type (1: ordinal "1234", 2: dense "1223", 3: competition "1224")');  	send_ui_raw(hvar('quiz_keep_scores', V_BOOL) . 'keep scores between quizes?'); +	send_ui_raw(hvar('quiz_keep_teams', V_BOOL) . 'keep chosen teams?') if ($type == QT_FAM);  	send_ui_raw(hvar('quiz_cmd_hint', V_BOOL) . '!hint command is enabled?');  	send_ui_raw(hvar('quiz_cmd_remind', V_BOOL) . '!remind command is enabled?');  } @@ -777,11 +810,14 @@ sub cmd_start {  		$quiz{teams} = [];  		@quiz{qw/score answers/} = (0) x 2;  	} else { -		#delete $quiz{players}{$_}{team} for (keys %{$quiz{players}}); #? unsure... +		if (!settings_get_bool('quiz_keep_teams') && ($type == QT_FAM)) { +			delete $quiz{players}{$_}{team} for (keys %{$quiz{players}}); +		}  	}  	send_irc('quiz_msg_start1', INSTANT);  	send_irc('quiz_msg_start2' . (($type == QT_FAM) ? '_f' : (($type == QT_MUL) ? '_m' : '')), $teams, INSTANT); -	@quiz{qw/stime qnum ison/} = (time(), 0, 1); +	@quiz{qw/stime ison/} = (time(), 1); +	@quiz{qw/qnum inq ended/} = (0) x 3;  	if ($type == QT_FAM) {  		$quiz{standby} = 1;  		@{$quiz{teams}[$_]}{qw/score answers/} = (0) x 2 for (0 .. $teams); @@ -810,12 +846,12 @@ sub cmd_stats {  		((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) && (settings_get_int('quiz_scoring_mode') != 1)));  	if ($quiz{type} == QT_FAM) {  		my @teams; -		push(@{$teams[$quiz{players}{$_}{team}]}, get_format('quiz_inc_team_nick', $quiz{players}{$_}{nick})) for (keys %{$quiz{players}}); +		push(@{$teams[$quiz{players}{$_}{team}]}, get_format('quiz_inc_team_nick', $quiz{players}{$_}{nick})) for (sort { $quiz{players}{$a}{joined} <=> $quiz{players}{$b}{joined} } keys %{$quiz{players}});  		foreach my $team (1 .. $quiz{tcnt}) {  			my ($score, $answers) = @{$quiz{teams}[$team]}{qw/score answers/};  			send_irc('quiz_msg_team_score' . $suffix, [  				$team, -				(!defined $teams[$team]) ? '' : join(', ', @{$teams[$team]}), +				(!defined $teams[$team]) ? '' : join($_team_separator, @{$teams[$team]}),  				$score, score_str($score), percents($score, $quiz{score}),  				$answers, answers_str($answers), percents($answers, $quiz{answers})]);  		} @@ -862,8 +898,7 @@ sub cmd_stats {  						$quiz{players}{$a}{timestamp} <=> $quiz{players}{$b}{timestamp}  					} keys %{$quiz{players}}) {  		push(@nicks, get_format('quiz_inc_scores_record', [$place, $quiz{players}{$player}{nick}, $quiz{players}{$player}{besttime}])); -		last if ($place >= $_qstats_records); -		$place++; +		last if (++$place > $_qstats_records);  	}  	send_irc('quiz_msg_scores_times', join(', ', @nicks)) if (@nicks);  	$place = 1; @@ -873,8 +908,7 @@ sub cmd_stats {  						$quiz{players}{$a}{timestamp} <=> $quiz{players}{$b}{timestamp}  					} keys %{$quiz{players}}) {  		push(@nicks, get_format('quiz_inc_scores_record', [$place, $quiz{players}{$player}{nick}, $quiz{players}{$player}{bestspeed}])); -		last if ($place >= $_qstats_records); -		$place++; +		last if (++$place > $_qstats_records);  	}  	send_irc('quiz_msg_scores_speeds', join(', ', @nicks)) if (@nicks);  } @@ -892,7 +926,7 @@ sub cmd_time {  	my $duration = shift;  	#? send_ui('quiz_err_na'), return if (($quiz{type} != QT_FAM) && ($quiz{type} != QT_MUL));  	send_ui('quiz_err_duration'), return if (($duration !~ /^\d+$/) || ($duration < 1)); -	settings_set_int('quiz_round_duration', $duration); +	settings_set_int('quiz_timeout', $duration);  	send_irc('quiz_msg_duration', $duration) if ($quiz{ison});  	send_ui('quiz_inf_duration', $duration);  } @@ -1017,7 +1051,7 @@ sub evt_next_question {  		%{$quiz{lookup}} = map { lc($_) => $_ } keys %{$quiz{data}[$quiz{qnum}]{answers}};  		$suffix = '_fm';  	} -	my $duration = abs(settings_get_int('quiz_round_duration')) || $_round_duration; # abs in case of <0, || in case of ==0 +	my $duration = abs(settings_get_int('quiz_timeout')) || $_round_duration; # abs in case of <0, || in case of ==0  	my @lines = make_remind();  	my $line = 1;  	foreach my $text (@lines) { @@ -1039,17 +1073,21 @@ sub evt_next_question {  		}  	} else {  		send_irc('quiz_msg_hint', make_hint()) if (settings_get_bool('quiz_show_first_hint')); -	} -	$quiz{inq} = 1; -	my $delay = settings_get_int('quiz_first_anticheat_delay'); -	if ($delay > 0) { -		$quiz{hprot} = 1; -		$quiz{thint} = timeout_add_once($delay * 1000, sub { $quiz{hprot} = 0 }, undef); -		if ((($quiz{type} == QT_MIX) || ($quiz{type} == QT_SCR)) && settings_get_bool('quiz_mix_on_remind')) { -			$quiz{rprot} = 1; -			$quiz{tremind} = timeout_add_once($delay * 1000, sub { $quiz{rprot} = 0 }, undef); +		my $delay = settings_get_int('quiz_first_anticheat_delay'); +		if ($delay > 0) { +			$quiz{hprot} = 1; +			$quiz{thint} = timeout_add_once($delay * 1000, sub { $quiz{hprot} = 0 }, undef); +			if ((($quiz{type} == QT_MIX) || ($quiz{type} == QT_SCR)) && settings_get_bool('quiz_mix_on_remind')) { +				$quiz{rprot} = 1; +				$quiz{tremind} = timeout_add_once($delay * 1000, sub { $quiz{rprot} = 0 }, undef); +			} +		} +		if (settings_get_bool('quiz_autohinter')) { +			$delay = abs(settings_get_int('quiz_hints_interval')) || $_hints_interval; # abs in case of <0, || in case of ==0 +			$quiz{thinter} = timeout_add_once($delay * 1000, 'evt_show_hint', undef);  		}  	} +	$quiz{inq} = 1;  }  sub evt_round_timeout_warn { @@ -1061,6 +1099,10 @@ sub evt_round_timeout {  	init_next_question(get_format('quiz_msg_timeout')); #? INSTANT?  } +sub evt_show_hint { +	show_hint(); +} +  ##### User interaction - responses / handlers #####  sub show_score {  	my ($nick, $addr, $who) = @_; @@ -1086,7 +1128,8 @@ sub show_score {  sub join_team {  	my ($nick, $addr, $team) = @_;  	return unless (($quiz{type} == QT_FAM) && (settings_get_bool('quiz_join_anytime') || $quiz{standby})); -	return unless (($team >= 1) && ($team <= $quiz{tcnt})); +	return if (($team < 1) || ($team > $quiz{tcnt})); +	my $time = time();  	if (exists $quiz{players}{$addr}) {  		if (settings_get_bool('quiz_transfer_points')) {  			my ($score, $answers) = @{$quiz{players}{$addr}}{qw/score answers/}; @@ -1098,21 +1141,21 @@ sub join_team {  			$quiz{teams}[$team]{score} += $score;  			$quiz{teams}[$team]{answers} += $answers;  		} -		$quiz{players}{$addr}{team} = $team;  	} else { -		@{$quiz{players}{$addr}}{qw/nick timestamp team/} = ($nick, time(), $team); +		@{$quiz{players}{$addr}}{qw/nick timestamp/} = ($nick, $time);  		@{$quiz{players}{$addr}}{qw/score answers besttime alltime bestspeed allspeed/} = (0) x 6;  	} +	@{$quiz{players}{$addr}}{qw/team joined/} = ($team, $time);  	my @teams; -	push(@{$teams[$quiz{players}{$_}{team}]}, get_format('quiz_inc_team_nick', $quiz{players}{$_}{nick})) for (keys %{$quiz{players}}); -	send_irc_whisper('quiz_msg_team_join', [$team, join(', ', @{$teams[$team]})], $nick) if (defined $teams[$team]); +	push(@{$teams[$quiz{players}{$_}{team}]}, get_format('quiz_inc_team_nick', $quiz{players}{$_}{nick})) for (sort { $quiz{players}{$a}{joined} <=> $quiz{players}{$b}{joined} } keys %{$quiz{players}}); +	send_irc_whisper('quiz_msg_team_join', [$team, join($_team_separator, @{$teams[$team]})], $nick) if (defined $teams[$team]);  }  sub show_hint { -	return unless (($quiz{type} != QT_FAM) && ($quiz{type} != QT_MUL) && settings_get_bool('quiz_cmd_hint')); -	return if ($quiz{hprot}); +	return if ($quiz{hprot} || ($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL) || !settings_get_bool('quiz_cmd_hint'));  	my $hints_limit = settings_get_int('quiz_max_hints'); -	make_hint(PREPDOTS) if (!@{$quiz{dots}} && ($hints_limit < 0)); +	make_hint(PREPDOTS) if (($quiz{hnum} == 0) && ($hints_limit < 0)); +	return unless (($quiz{hnum} == 0) || !$_no_hints_spam || ($quiz{dcnt} > 0));  	if (($hints_limit == 0) ||  		(($hints_limit > 0) && ($quiz{hnum} < $hints_limit)) ||  		(($hints_limit < 0) && ($quiz{dmax} > abs($hints_limit)))) { @@ -1123,6 +1166,11 @@ sub show_hint {  				$quiz{thint} = timeout_add_once($delay * 1000, sub { $quiz{hprot} = 0 }, undef);  			}  	} +	if (settings_get_bool('quiz_autohinter')) { +		stop_timer('thinter'); +		my $delay = abs(settings_get_int('quiz_hints_interval')) || $_hints_interval; # abs in case of <0, || in case of ==0 +		$quiz{thinter} = timeout_add_once($delay * 1000, 'evt_show_hint', undef); +	}  }  sub show_remind { @@ -1149,9 +1197,11 @@ sub show_remind {  sub check_answer {  	my ($nick, $addr, $answer) = @_;  	if (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) { -		return unless (exists($quiz{lookup}{$answer}) && ($quiz{data}[$quiz{qnum}]{answers}{$quiz{lookup}{$answer}} > 0)); -		return unless (($quiz{type} == QT_MUL) || !settings_get_bool('quiz_team_play') || (exists($quiz{players}{$addr}) && exists($quiz{players}{$addr}{team}) && ($quiz{players}{$addr}{team} != 0))); # last condition: for non team players there is no record -		my ($time, $match) = (time(), $quiz{lookup}{$answer}); +		return unless (exists($quiz{lookup}{lc $answer}) && +						($quiz{data}[$quiz{qnum}]{answers}{$quiz{lookup}{lc $answer}} > 0)); +		return unless (($quiz{type} == QT_MUL) || !settings_get_bool('quiz_team_play') || +						(exists($quiz{players}{$addr}) && exists($quiz{players}{$addr}{team}) && ($quiz{players}{$addr}{team} != 0))); # last condition: for non team players there is no record yet // autovivification... +		my ($time, $match) = (time(), $quiz{lookup}{lc $answer});  		my $answers = keys %{$quiz{data}[$quiz{qnum}]{answers}};  		my $id = $quiz{data}[$quiz{qnum}]{answers}{$match};  		my $value = $answers - $id + 1; @@ -1192,11 +1242,12 @@ sub check_answer {  			init_next_question(get_format('quiz_msg_all_answers')); #? not INSTANT  		}  	} else { -		return unless (($answer eq lc($quiz{data}[$quiz{qnum}]{answer})) || -			(!settings_get_bool('quiz_strict_match') && (index($answer, lc $quiz{data}[$quiz{qnum}]{answer}) >= 0))); +		return unless ((lc($answer) eq lc($quiz{data}[$quiz{qnum}]{answer})) || +			(!settings_get_bool('quiz_strict_match') && (index(lc $answer, lc $quiz{data}[$quiz{qnum}]{answer}) >= 0)));  		my ($time, $points) = (time(), settings_get_int('quiz_points_per_answer')); -		return unless (!settings_get_bool('quiz_limiter') || !exists($quiz{players}{$addr}) || -			($quiz{players}{$addr}{score} < int($quiz{qcnt} * 0.5 + 1) * $points)); # 50%+1 +		my $limit = int($quiz{qcnt} * 0.5 + 1) * $points; # 50%+1 +		my $limiter = settings_get_bool('quiz_limiter'); +		return unless (!$limiter || !exists($quiz{players}{$addr}) || ($quiz{players}{$addr}{score} < $limit));  		stop_question();  		correct_answer($addr, $nick, $time, $points, $answer);  		init_next_question(get_format('quiz_msg_congrats', [ @@ -1205,7 +1256,7 @@ sub check_answer {  			$quiz{data}[$quiz{qnum}]{answer},  			$time - $quiz{qtime},  			length($answer) / ($time - $quiz{qtime}), -			$quiz{players}{$addr}{score}]), INSTANT); +			$quiz{players}{$addr}{score}]) . (($limiter && ($quiz{players}{$addr}{score} >= $limit)) ? ' ' . get_format('quiz_msg_limit') : ''), INSTANT);  	}  } @@ -1221,19 +1272,17 @@ sub sig_pubmsg {  		# \002 - bold  \003$fg(,$bg)? - color  \017 - plain  \026 - reverse  \037 - underline  	}  	return if ($msg eq ''); -	my $lmsg = lc $msg; -	if ($lmsg =~ /^!score(?:\s+([^\s]+))?/) { -		show_score($nick, $addr, $1) -	} elsif ($lmsg =~ /^!join\s+(\d)$/) { -		join_team($nick, $addr, $1); -	} +	show_score($nick, $addr, $1) if ($msg =~ /^!score(?:\s+([^\s]+))?/i); +	return if ($quiz{ended}); +	join_team($nick, $addr, $1) if ($msg =~ /^!join\s+(\d)$/i);  	return if (!$quiz{inq}); +	my $lmsg = lc $msg;  	if ($lmsg eq '!hint') {  		show_hint();  	} elsif ($lmsg eq '!remind') {  		show_remind();  	} -	check_answer($nick, $addr, $lmsg); +	check_answer($nick, $addr, $msg);  }  ##### Bindings ##### | 
