perl/cgi newbie. hangman.cgi script adding html-like code on web and other trouble
Hi all,
I'm a newbie trying to learn perl and cgi. I have written a script which you can find here. There are several problems.
1. When you first arrive at that url, you'll notice that there is additional html-like code visible on the page, as well as an extra "New Game" button. Why are those visible?
2. You'll also notice that when you first arrive at that url, the game doesn't actually work. You can input letters, but nothing happens when you guess.
3. If you click one of the "New Game" buttons, though, it will THEN start a "working" game! Why doesn't it just do that from the get-go?
4. Even when a new game is started, you only get 9 tries for letters, not 10. Why?
5. When you complete a game (win or lose), it should flash text saying "Sorry (or Congratulations), you're word was 'whatever'. It does, but it also adds more of that html-like code there. Why?
6. The "letter-guessing" line is solid. How can I make it dashed, so that each dash represents a letter in the word?
7. If at that point you start a new game, the last letter you guessed on the previous game starts off as a newly-chosen letter for the new game. Why?
I think that's it for problems. Any ideas would be most appreciated! Thank you so much! Here's the code the the perl/cgi script:
Code:
#!/usr/bin/perl
use CGI ':standard';
use CGI::Carp 'fatalsToBrowser';
use Fcntl;
use SDBM_File;
$max_tries = 10;
%current = ( 'word' => "evermeet",
'tries_left' => "10",
'letters' => "",
'revealed' => "________",);
tie(%hash, 'SDBM_File', '/tmp/game_file.sdbm', O_CREAT¦O_RDWR, 0666) ¦¦ die $!;
&PrintHeader();
my $tries_left = $current{'tries_left'};
my @guessed_letters = split( //, $current{'letters'} );
my $secret_word = $current{'word'};
my $revealed_word = $current{'revealed'};
my $this_guess = "";
my $guessed_string = "";
my $found_letter = 0;
$this_guess = param('guess');
$found_letter = &GuessLetter($this_guess);
if( $tries_left > 0 ) {
if( ($current{'word'} eq $current{'revealed'}) && ($tries_left < $max_tries) )
{
&PrintContent();
&win();
}
else {
if( $found_letter == 0 ) {
$tries_left--;
}
push(@guessed_letters, $this_guess );
$guessed_string = join( "", @guessed_letters );
$current{'tries_left'} = $tries_left;
$current{'letters'} = $guessed_string;
&PrintContent();
$hash{$id} = $current{'word'}."~".$current{'tries_left'}."~". $current{'letters'}."~".$current{'revealed'};
}
}
else {
&PrintContent();
&lose();
}
untie(%hash);
sub generate_gameid() {
for ($j = 0; $j <= 9; $j++) {
$id .= int(rand 9);
}
return $id;
}
sub win {
my $the_cookie = cookie ( -name=>'gameid',
-value=>$id,
-expires=>'+1h');
print header(-cookie=>$the_cookie),
start_html("Hangman - Congratulations!"),
"Congratulations, your word was:",
h2($current{'word'}),
start_form,
submit (-name=>"new_game", -label=>"New Game"),
end_form,
end_html;
}
sub lose
{
my $the_cookie = cookie ( -name=>'gameid',
-value=>$id,
-expires=>'+1h');
print header(-cookie=>$the_cookie),
start_html("Hangman - Sorry!"),
"Sorry, your word was:",
h2($current{'word'}),
start_form, submit (-name=>"new_game", -label=>"New Game"),
end_form,
end_html;
}
sub PrintHeader() {
my $the_cookie;
my $saved_game;
if(param('new_game')) {
$id = &generate_gameid();
$the_cookie = cookie ( -name=>'gameid',
-value=>$id,
-expires=>'+1h');
&InitGame();
}
else {
$id = cookie('gameid');
$saved_game = $hash{$id};
&LoadGame($saved_game);
}
print header(-cookie=>$the_cookie);
}
sub PrintContent() {
print start_html( -title=>"Hangman!", -onLoad=>"document.forms[0].guess.select()"),
h1("Hangman!"),
h1($current{'revealed'}),
"Tries Remaining: ",
b($current{'tries_left'}),
br,
"Letters guessed so far: ",
b(em(join " ", split //, $current{'letters'})),
br,
start_form,
"Next Guess: ",
textfield ( -name=>"guess", -size=>5, -maxlength=>1, -default=>''),
br, br,
submit (-name=>"submit", -label=>"Guess"),
submit (-name=>"new_game", -label=>"New Game"),
end_form,
end_html;
}
sub InitGame() {
my $word = &FetchWord();
$current{'word'} = $word;
$current{'tries_left'} = $max_tries;
$current{'letters'} = "";
$current{'revealed'} = &MakeLines($word);
}
sub LoadGame() {
my $foo = $_[0];
my @saved_game = split(/~/,$foo);
$current{'word'} = $saved_game[0];
$current{'tries_left'} = $saved_game[1];
$current{'letters'} = $saved_game[2];
$current{'revealed'} = $saved_game[3];
}
sub FetchWord() {
my $word = "superman";
my @lines;
my @words;
my $line_count = 0;
my $word_count = 0;
my $rand_line = 0;
my $rand_word = 0;
open( DICTIONARY, "words.txt" ) or die "Cannot open file: $!\n";
@lines = <DICTIONARY>;
$line_count = @lines;
$rand_line = int(rand $line_count);
@words = split(/,/, $lines[$rand_line] );
$word_count = @words;
$rand_word = int(rand $word_count);
$word = $words[$rand_word];
chomp($word);
close( DICTIONARY );
return $word;
}
sub MakeLines() {
my @word = split(//, $_[0]);
my $letter = "";
my $blanks = "";
foreach $letter(@word) {
$blanks .= "_";
}
return $blanks;
}
sub GuessLetter() {
my $guess = $_[0];
my @word = split(//,$current{'word'});
my $letter = "";
my @old_revealed = split(//,$current{'revealed'});
my @new_revealed = @old_revealed;
my $new_revealed_string = "";
my $got_one = 0;
my $count = 0;
foreach $letter(@word) {
if($guess eq $letter) {
print LOG "Matched letter [$guess] at position [$count]\n";
$new_revealed[$count] = $guess;
$got_one = 1;
}
else {
$new_revealed[$count] = $old_revealed[$count];
}
$count++;
}
$new_revealed_string = join("",@new_revealed);
$current{'revealed'} = $new_revealed_string;
return $got_one;
}
Again, I'm a newbie, so please be gentle! Thanks, guys!
you are printing the http header more than once per page load which is causing the extra html-ish stuff you see. Your script runs and always executes &PrintHeader, but then you reprint the header in &win or &lose. You must also print the cookie before the http header is completed printing or the cookie will be printed to the screen instead of the cookie file on the client computer.
Sorry about saying "guys". I didn't realize at first that both posts were yours, millenium. Thanks a lot.
You were right about the http headers. I didn't even see that. Thanks! There are some new issues...
When first visiting the site, it says:
"Sorry, your word was:"
New Game button
If I then press the "New Game" button, that game starts and works fine. Other than that, your iteration of the script seems to work fine! Great, thanks!
I changed this in the script:
Code:
sub MakeLines {
my $blanks;
$blanks .= "_" for (1..length($_[0]));
return $blanks;
}
to this:
Code:
sub MakeLines {
my $blanks;
$blanks .= "_ " for (1..length($_[0]));
return $blanks;
}
Notice the whitespace now inside the quotation marks?
Code:
"_ " instead of "_".
I did that so that when a game is played, it will show, say 10 spaces if the word in question happens to have 10 letters to guess. The problem now is that, when I do that, I have noticed that, say once again that a word has 10 letters, fifteen spaces will actually appear, and even if you get the word right, there are still empty spaces and the game will force a loss on the player if you continue down to 0 guesses. Why is that? I would appreciate any further help, millenium! Thank you!
Sorry about saying "guys". I didn't realize at first that both posts were yours, millenium. Thanks a lot.
You were right about the http headers. I didn't even see that. Thanks! There are some new issues...
When first visiting the site, it says:
"Sorry, your word was:"
New Game button
If I then press the "New Game" button, that game starts and works fine. Other than that, your iteration of the script seems to work fine! Great, thanks!
I changed this in the script:
Code:
sub MakeLines {
my $blanks;
$blanks .= "_" for (1..length($_[0]));
return $blanks;
}
to this:
Code:
sub MakeLines {
my $blanks;
$blanks .= "_ " for (1..length($_[0]));
return $blanks;
}
Notice the whitespace now inside the quotation marks?
Code:
"_ " instead of "_".
I did that so that when a game is played, it will show, say 10 spaces if the word in question happens to have 10 letters to guess. The problem now is that, when I do that, I have noticed that, say once again that a word has 10 letters, fifteen spaces will actually appear, and even if you get the word right, there are still empty spaces and the game will force a loss on the player if you continue down to 0 guesses. Why is that? I would appreciate any further help, millenium! Thank you!
Chris
Reference the white space, I did not try and determine the cause of the effect you noticed because another solution is very simple and easy, insert the spaces only when the word is displayed, like so:
Code:
sub PrintContent {
print start_html( -title=>"Hangman!", -onLoad=>"document.forms[0].guess.select()"),
h1("Hangman!"),
h1({-style=>'letter-spacing:5px'},$current{'revealed'}),
The game starts off with "sorry......" because there is no initial condition the script defaults to the first time the script is run. It just jumps into the game assuming some input has been supplied. You need to create a default condition which will display some brief instructions or a welcome screen, something along those lines.
Ok... Ugh, I have tried a million things to no avail regarding the problem when first visiting the site. I either get errors, or I get the welcome message I create in ADDITION to the "Sorry, etc... This is driving me crazy. Can you help further? Give me an example, please? Thanks! My brain is fried...
Ok... Ugh, I have tried a million things to no avail regarding the problem when first visiting the site. I either get errors, or I get the welcome message I create in ADDITION to the "Sorry, etc... This is driving me crazy. Can you help further? Give me an example, please? Thanks! My brain is fried...
Chris
LOL... the old fried brain
actually I was having fun with the script so I added a couple of things this afternoon. I think you can figure out what I did for the default condition, plus I added a routine that gives a score if you win, its not anything very well thought out but hopefully it adds to the fun. Anyways, here it is (not real well tested but looks like it should work OK):
Code:
#!/usr/bin/perl
use CGI ':standard';
use Fcntl;
use SDBM_File;
use strict;
my $max_tries = 10;
my $id;
my %current = ('tries_left' => 10, 'letters' => '', 'revealed' => '');
tie(my %hash, 'SDBM_File', '/tmp/game_file.sdbm', O_CREAT|O_RDWR, 0666) || die $!;
&PrintHeader();
my $tries_left = $current{'tries_left'};
my @guessed_letters = split(//, $current{'letters'} );
my $secret_word = lc $current{'word'};
my $this_guess = 0;
my $guessed_string = "";
my $found_letter = 0;
my $playing = 0;
$playing = 1 if param('new_game') || param('start_game') || param('submit');
$this_guess = lc param('guess');
undef($this_guess) if param('new_game') || param('start_game');
$found_letter = &GuessLetter($this_guess) if $this_guess;
if ($playing) {
&win() if (($current{'word'} eq $current{'revealed'}) && ($tries_left <= $max_tries));
$tries_left-- if ($found_letter == 0 && $this_guess);
&lose() if ($tries_left < 1 && $this_guess);
push(@guessed_letters, $this_guess) if $found_letter == 0;
$guessed_string = join( "", @guessed_letters);
$current{'tries_left'} = $tries_left;
$current{'letters'} = $guessed_string;
&PrintContent();
$hash{$id} = $current{'word'}."~".$current{'tries_left'}."~".$current{'letters'}."~".$current{'revealed'};
}
else {&enter;}
untie(%hash);
exit(0);
sub enter {
print start_html( -title=>"Hangman!"),
h1("Lets Play Hangman!"),
"The rules: guess letters to reveal the secret word.",
br,
"You win if you guess the secret word before your tries remaining run out.",
start_form,
submit (-name=>'start_game', -label=>'Start Game'),
end_form,
end_html;
}
sub generate_gameid {
$id .= int(rand 9) for (0..8);
return $id;
}
sub win {
my ($score,$max_score) = &score($current{'revealed'},$current{'word'});
print start_html("Hangman - Congratulations!"),
"Congratulations, your word was:",
h2($current{'word'}),
"Your score was <b>$score</b> of a possible <b>$max_score</b> points.",
start_form,
submit (-name=>"new_game", -label=>"Play Again"),
end_form,
end_html;
untie(%hash);
exit(0);
}
sub lose {
my ($score,$max_score) = &score($current{'revealed'},$current{'word'});
print start_html("Hangman - Sorry!"),
"Sorry, your word was:",
h2($current{'word'}),
"Your score was <b>$score</b> of a possible <b>$max_score</b> points.",
start_form, submit (-name=>"new_game", -label=>"Play Again"),
end_form,
end_html;
untie(%hash);
exit(0);
}
sub PrintHeader {
my $the_cookie;
my $saved_game;
if(param('new_game')) {
$id = &generate_gameid();
$the_cookie = cookie ( -name=>'gameid',
-value=>$id,
-expires=>'+1h');
&InitGame();
}
else {
$id = cookie('gameid');
$saved_game = $hash{$id};
&LoadGame($saved_game);
}
print header(-cookie=>$the_cookie);
}
sub PrintContent {
print start_html( -title=>"Hangman!", -onLoad=>"document.forms[0].guess.select()"),
span({-style=>'font-size:30px;font-weight:bold;'},'Secret Word: '),
span({-style=>'letter-spacing:5px;font-size:30px;font-weight:bold;'},$current{'revealed'}),
br,br,
"Tries Remaining: ",
b($current{'tries_left'}),
br,
"Incorrect guesses: ",
b(em(join " ", split //, $current{'letters'})),
br,
start_form,
"Your Guess: ",
textfield ( -name=>'guess', -size=>5, -maxlength=>1, -default=>'', override=>1),
submit (-name=>'submit', -label=>'Guess'),br,br,
submit (-name=>'new_game', -label=>'New Game'),
end_form,
end_html;
}
sub InitGame {
my $word = &FetchWord();
$current{'word'} = $word;
$current{'tries_left'} = $max_tries;
$current{'letters'} = "";
$current{'revealed'} = &MakeLines($word);
}
sub LoadGame {
my $foo = $_[0];
my @saved_game = split(/~/,$foo);
$current{'word'} = $saved_game[0];
$current{'tries_left'} = $saved_game[1];
$current{'letters'} = $saved_game[2];
$current{'revealed'} = $saved_game[3];
}
sub FetchWord {
#my $word = "superman";
my @lines;
my @words;
my $line_count = 0;
my $word_count = 0;
my $rand_line = 0;
my $rand_word = 0;
open( DICTIONARY, "words.txt" ) or die "Cannot open file: $!\n";
@lines = <DICTIONARY>;
close( DICTIONARY );
$line_count = @lines;
$rand_line = int(rand $line_count);
@words = split(/,/, $lines[$rand_line] );
$word_count = @words;
$rand_word = int(rand $word_count);
my $word = $words[$rand_word];
chomp($word);
return $word;
}
sub MakeLines {
my $blanks;
$blanks .= "_" for (1..length($_[0]));
return $blanks;
}
sub GuessLetter {
my $guess = $_[0];
my @word = split(//,$current{'word'});
my $letter = "";
my @old_revealed = split(//,$current{'revealed'});
my @new_revealed = @old_revealed;
my $new_revealed_string = "";
my $got_one = 0;
my $count = 0;
foreach $letter(@word) {
if(lc($guess) eq lc($letter)) {
$new_revealed[$count] = $guess;
$got_one = 1;
}
$count++;
}
$new_revealed_string = join("",@new_revealed);
$current{'revealed'} = $new_revealed_string;
return $got_one;
}
sub score {
$tries_left = 1 if $tries_left < 1;
my ($score,$score2,$max_score) = (0,0,0);
my @guess = split(//,shift);
my @word = split(//,shift);
my @points = qw(aeiou ytrnlchds bfgkmp jqvwxz);
foreach (@guess) {
$score+=3 if (/[$points[0]]/i);
$score+=7 if (/[$points[1]]/i);
$score+=10 if (/[$points[2]]/i);
$score+=15 if (/[$points[3]]/i);
}
foreach (@word) {
$score2+=3 if (/[$points[0]]/i);
$score2+=7 if (/[$points[1]]/i);
$score2+=10 if (/[$points[2]]/i);
$score2+=15 if (/[$points[3]]/i);
}
$max_score = $score2*$max_tries;
$score = $score*$tries_left;
return ($score,$max_score);
}