guess_number3.cgi (6670B) - raw
1 #!/usr/bin/perl 2 3 use warnings; 4 use strict; 5 use CGI; 6 use CGI::Cookie (); 7 use MD5 (); 8 9 use constant TRIES => 6; 10 use constant COOKIE_NAME => 'guess_number_3'; 11 use constant SECRET => 'mySuperSecretSecret'; 12 my $game = CGI->new; 13 14 # state maintenance subroutines 15 sub get_state { 16 my %state = $game->cookie( COOKIE_NAME ); 17 return undef unless %state; 18 authentication_error() unless cookie_check( \%state, 'check'); 19 return \%state; 20 } 21 22 sub initialize { 23 my $state = shift; 24 $state = {} unless $state; 25 $state->{NUMBER} = int( rand( 99 ) ) + 1; 26 $state->{GAMENO} += 1; 27 $state->{GUESSES_LEFT} = TRIES; 28 $state->{WON} += 0; 29 $state->{GUESSED} = ''; 30 return $state; 31 } 32 33 sub save_state { 34 my $state = shift; 35 cookie_check( $state, 'generate' ); 36 return CGI::Cookie->new ( 37 -name => COOKIE_NAME, 38 -value => $state, 39 -expires => '+1M', 40 ); 41 } 42 43 # game play logic 44 sub process_guess { 45 my ( $guess, $state ) = @_; 46 47 # process list of previous guesses 48 my %guessed = map { $_ => 1 } split /,/, $state->{GUESSED}; 49 50 # do nothing if user did not enter a guess 51 return ('', 'continue') unless $guess; 52 53 # ensure guess is a valid number between 1 and 100 54 return ("Not a valid guess.", 'error') 55 unless $guess =~ m/^[1-9][0-9]?$|^100$/; 56 57 # check if user already entered this guess previously 58 return ("You already guessed that number.", 'error') 59 if $guessed{ $guess }; 60 61 # we have a valid guess. update list of guesses 62 $guessed{ $guess } = 1; 63 $state->{GUESSED} = join ',', sort keys %guessed; 64 65 $state->{GUESSES_LEFT}--; 66 67 # the order of the checks is important here. At this point, it's 68 # possible that the player has used their last guess. Therefore, 69 # we first check whether the guess is correct 70 71 # guess is correct! 72 if ( $guess == $state->{NUMBER} ) { 73 $state->{WON}++; 74 return ("Congratulations! You guessed that the correct number 75 was $state->{NUMBER}!", 'won'); 76 } 77 78 # if the guess is not correct AND we are out of guesses... 79 return ("Sorry, you ran out of turns. The number was $state->{NUMBER}.", 80 'lost') unless ($state->{GUESSES_LEFT} && $guess != $state->{NUMBER}); 81 82 # otherwise... 83 # guess too low 84 return ("Your guess is too low.", 'continue') 85 if ( $guess < $state->{NUMBER} ); 86 87 # ...or 88 # guess is too high 89 return ("Your guess is too high.", 'continue') 90 if ( $guess > $state->{NUMBER} ); 91 92 } 93 94 # html generation subroutines 95 sub print_header { 96 my $state = shift; 97 print $game->header( 98 -type => "text/html", 99 -charset => 'utf-8', 100 -cookie => save_state( $state ), 101 ); 102 print $game->start_html( 103 -title => "Guess a Secret Number!", 104 -onLoad => 'if (document.gf) document.gf.guess.focus()' 105 ); 106 print $game->h1( 'Guess a Secret Number Interactive Game' ); 107 } 108 109 sub print_status { 110 my ( $message, $state ) = @_; 111 112 # draw table 113 print 114 $game->table( { -border=>"0", -width=>"50%" }, 115 $game->Tr( 116 $game->td( $game->b("Game #: "), $state->{GAMENO} ), 117 $game->td( $game->b("Wins: "), $state->{WON} ) 118 ), 119 $game->Tr( 120 $game->td( $game->b("Guesses Left: "), $state->{GUESSES_LEFT} ), 121 $game->td( $game->b("Numbers Guessed: "), $state->{GUESSED} ) 122 ) 123 ); 124 print $game->h2( $game->font( {-color => 'red'}, $message )) 125 if $message; 126 } 127 128 sub show_restart_form { 129 my $state = shift; 130 print $game->start_form(); 131 print $game->p( 132 #-style => 'padding-right: 25px', 133 'Do you want to play again?', 134 ); 135 print $game->submit ( 136 -name => 'restart', 137 -value => 'Play again', 138 #-style => 'padding-left: 25px; padding-right: 25px', 139 ); 140 print $game->checkbox ( 141 -name => 'clear', 142 -label => 'Clear history', 143 ); 144 print $game->end_form(); 145 } 146 147 sub show_guess_form { 148 my $state = shift; 149 print $game->start_form( -name => 'gf' ); 150 print "Your guess: "; 151 print $game->textfield ( 152 -inputmode => 'numeric', # force number pad on mobile 153 -pattern => '[0-9]*', # force number pad on mobile 154 -name => 'guess', 155 -value => '', 156 -override => 1 157 ); 158 print $game->submit ( 159 -name => '_guess', 160 -value => 'Guess' 161 ); 162 print $game->end_form(); 163 } 164 165 sub print_footer { 166 print $game->hr; 167 print $game->p( 168 'inspired by Hangman game from ', 169 $game->a( 170 { -href => "https://www.oreilly.com/library/view/writing-apache-modules/156592567X/"}, 171 $game->i( 'Writing Apache Modules with Perl and C') 172 ), 173 ' by Lincoln Stein and Doug MacEachern.' 174 ); 175 print $game->end_html(); 176 } 177 178 # message authentication checks 179 sub cookie_check { 180 my ($state, $action) = @_; 181 return unless ref( $state ); 182 my @fields = @{$state}{qw(NUMBER GUESSES_LEFT GUESSED GAMENO WON)}; 183 my $newmac = MD5->hexhash( 184 SECRET . MD5->hexhash( join '', SECRET, @fields ) 185 ); 186 return $state->{MAC} = $newmac if $action eq 'generate'; 187 return $newmac eq $state->{MAC} if $action eq 'check'; 188 return undef; 189 } 190 191 sub authentication_error { 192 my $cookie = CGI::Cookie->new(-name => COOKIE_NAME, -value=>'',-expires => '-1d'); 193 print $game->header(-cookie => $cookie), 194 $game->start_html(-title => 'Authentication Error'), 195 $game->h1($game->font({-color => 'red'}, 'Authentication Error')), 196 $game->p('This application was unable to confirm the integrity of the', 197 'cookie that holds your current score.', 198 'Please reload the page to start a fresh session.'), 199 $game->p('If the problem persists, contact the webmaster.'); 200 exit 0; 201 } 202 203 # begin main program 204 # retrieve current state 205 my $state = get_state() unless $game->param( 'clear' ); 206 207 # initialize state if we are starting from the beginning (!$state) 208 # or if we are playing another game ($game->param( 'restart' )). 209 if ( !$state || $game->param( 'restart' ) ) { 210 $state = initialize( $state ); 211 } 212 213 # process current guess, if any 214 my ( $message, $status ) = 215 process_guess( $game->param( 'guess' ) || '', $state ); 216 217 # generate the HTML page 218 print_header( $state ); 219 print_status( $message, $state ); 220 221 # prompt user 222 if ( $status =~ /^(won|lost)$/ ) { 223 show_restart_form( $state ); 224 } else { 225 show_guess_form( $state ); 226 } 227 228 print_footer();