Excalibur's Sheath

Writing Hangman in Perl

Oct 10, 2016 • scripting,perl

I was challenged to write Hangman. It started out with a simple enough idea. Have the computer pick a word from a wordlist, and present the hangman structure, and proceed through the game. As is often the case, while the idea is simple, it took me a bit to figure it out. See my Github Repo for the full code.

Subroutines

I created the following subroutines for this program:

readInDictionary

readInDictionary looks for a file called dict.txt, opens it, puts it into an array, and returns a reference to the array.

sub readInDictionary
{
	#Opens the dictionary file
	open FILE, "dict.txt" or die $!;
	#Saves the file to an array
	my @dict = <FILE>;
	#Returns a pointer to the array
	return \@dict;
}

chooseWord

chooseWord takes in the array reference for the dictionary, and selects a random word. It then turns that word into an array, and returns the array reference for the word array.

sub chooseWord
{
	#Gets the dictionary array reference from pass in
	my $dict = shift;
	my @dict = @$dict;
	#Removes extra whitespace and uppercases the word.
	chomp(my $word = uc($dict[rand @dict]));
	#Splits the string to an array
	my @theWord = split('',$word);
	#Returns the reference of the array
	return \@theWord;
}

initGuessArray

initGuessArray takes in the reference to the Word array, and uses that to create the array which is used to capture the correct guesses. The fillIn Array, is poplulated with _ unless a non letter is found. It then returns the reference to the initalized array.

sub initGuessArray
{
	#Initalize variables
	#Split the string into an array
	my $theWord = shift;
	my @theWord = @$theWord;
	#Make an array to fill in
	my @fillIn;
	#Counter to use to populate the fill-in array.  
	my $counter = 0;
	#For each character in the @theWord Array, fill the fillIn array with an _ if its a letter, otherwise fill it with the character.from the 
	foreach (@theWord)
	{
		#If its a letter populate an underscore
		if ($_ =~ /[a-zA-Z]/)
		{
			$fillIn[$counter] = "_";
		}
		#If its not a letter populate the character
		else
		{
			$fillIn[$counter] = $theWord[$counter];
		}
		#Increment the counter for the space in the arrays
		$counter++;
	}
	#Returns an array reference where the letters are represented by underscores.
	return \@fillIn;
}

checkLetter

checkLetter checks the guess for any instances of it in the word. If they are it changes the fillInArray values for those elements to the letter. It also adds letters that are not in the word to the wrong guess array.

sub initGuessArray
{
	#Initalize variables
	#Split the string into an array
	my $theWord = shift;
	my @theWord = @$theWord;
	#Make an array to fill in
	my @fillIn;
	#Counter to use to populate the fill-in array.  
	my $counter = 0;
	#For each character in the @theWord Array, fill the fillIn array with an _ if its a letter, otherwise fill it with the character.from the 
	foreach (@theWord)
	{
		#If its a letter populate an underscore
		if ($_ =~ /[a-zA-Z]/)
		{
			$fillIn[$counter] = "_";
		}
		#If its not a letter populate the character
		else
		{
			$fillIn[$counter] = $theWord[$counter];
		}
		#Increment the counter for the space in the arrays
		$counter++;
	}
	#Returns an array reference where the letters are represented by underscores.
	return \@fillIn;
}

printArray

printArray prints the contents of arrays. I implemented “Color Schemes” so that I can pass to the array what color scheme to use.

sub printArray
{
	#Get in the array
	my $arrayRef = shift;
	my $colorScheme = shift;
	my $counter = 0;
	#Prints each letter
	foreach (@$arrayRef)
	{
		given ($colorScheme)
		{
			when(1)
			{
				if (@$arrayRef[$counter] eq '_')
				{	
					print colored ['red'], @$arrayRef[$counter];
				}
				else
				{
					print colored ['cyan'], @$arrayRef[$counter];
				}
				break;
			}
			when(2)
			{
				print colored ['yellow'], @$arrayRef[$counter];
				break;
			}
			when(3)
			{
				print colored ['red'], @$arrayRef[$counter];
				break;
			}
		}
		$counter++;
	}
}

drawHang

This is where I defined the various stages of hangman, and draw it all out.

sub drawHang
{
	my $turn = shift;
	given ($turn)
	{
                when(0)
                {
                        print colored ['bright_green'],"┌────────┐\n";
                        print colored ['bright_green'],"│        │\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_blue'], "██████████\n";
                        print colored ['bright_blue'], "████████████\n";
                        print colored ['bright_blue'], "██████████████\n";
                        break;
                }
		when(1)
		{
			print colored ['bright_green'],"┌────────┐\n";
			print colored ['bright_green'],"│        │\n";
			print colored ['bright_green'], "";
			print colored ['red'], "@\n";
			print colored ['bright_green'], "\n";
			print colored ['bright_green'], "\n";
			print colored ['bright_green'], "\n";
			print colored ['bright_green'], "\n";
			print colored ['bright_blue'],  "██████████\n";
			print colored ['bright_blue'],  "████████████\n";
			print colored ['bright_blue'],  "██████████████\n";
			break;
		}
		when(2)
		{
                        print colored ['bright_green'], "┌────────┐\n";
                        print colored ['bright_green'], "│        │\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "@\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "|\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "|\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_blue'],  "██████████\n";
                        print colored ['bright_blue'],  "████████████\n";
                        print colored ['bright_blue'],  "██████████████\n";
			break;
		}
		when(3)
		{
                        print colored ['bright_green'], "┌────────┐\n";
                        print colored ['bright_green'], "│        │\n";
                        print colored ['bright_green'], "";
                        print colored ['red'], "@\n";
                        print colored ['bright_green'], "";
                        print colored ['red'], "/|\n";
                        print colored ['bright_green'], "";
                        print colored ['red'], "|\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_blue'],  "██████████\n";
                        print colored ['bright_blue'],  "████████████\n";
                        print colored ['bright_blue'],  "██████████████\n";
			break;
		}
		when(4)
		{
                        print colored ['bright_green'], "┌────────┐\n";
                        print colored ['bright_green'], "│        │\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "@\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "/|\\\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "|\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_blue'],  "██████████\n";
                        print colored ['bright_blue'],  "████████████\n";
                        print colored ['bright_blue'],  "██████████████\n";
			break;
		}
                when(5)
                {
                        print colored ['bright_green'], "┌────────┐\n";
                        print colored ['bright_green'], "│        │\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "@\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "/|\\\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "|\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "/ \n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_blue'],  "██████████\n";
                        print colored ['bright_blue'],  "████████████\n";
                        print colored ['bright_blue'],  "██████████████\n";
                        break;
                }
                when(6)
                {
                        print colored ['bright_green'], "┌────────┐\n";
                        print colored ['bright_green'], "│        │\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "@\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "/|\\\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "|\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "/ \\\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_blue'],  "██████████\n";
                        print colored ['bright_blue'],  "████████████\n";
                        print colored ['bright_blue'],  "██████████████\n";
                        break;
                }
                when(7)
                {
                        print colored ['bright_green'], "┌────────┐\n";
                        print colored ['bright_green'], "│        │\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "@\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "_/|\\\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "|\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "/ \\\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_blue'],  "██████████\n";
                        print colored ['bright_blue'],  "████████████\n";
                        print colored ['bright_blue'],  "██████████████\n";
                        break;
                }
                when(8)
                {
                        print colored ['bright_green'], "┌────────┐\n";
                        print colored ['bright_green'], "│        │\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "@\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "_/|\\_\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "|\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "/ \\\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_blue'],  "██████████\n";
                        print colored ['bright_blue'],  "████████████\n";
                        print colored ['bright_blue'],  "██████████████\n";
                        break;
                }
                when(9)
                {
                        print colored ['bright_green'], "┌────────┐\n";
                        print colored ['bright_green'], "│        │\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "@\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "_/|\\_\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "|\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "_/ \\\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_blue'],  "██████████\n";
                        print colored ['bright_blue'],  "████████████\n";
                        print colored ['bright_blue'],  "██████████████\n";
                        break;
                }
                when(10)
                {
                        print colored ['bright_green'], "┌────────┐\n";
                        print colored ['bright_green'], "│        │\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "@\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "_/|\\_"."\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "|\n";
                        print colored ['bright_green'], "";
			print colored ['red'], "_/ \\_"."\n";
                        print colored ['bright_green'], "\n";
                        print colored ['bright_blue'],  "██████████\n";
                        print colored ['bright_blue'],  "████████████\n";
                        print colored ['bright_blue'],  "██████████████\n";
                        break;
                }
	}
}

takeInputAndValidate

takeInputAndValidate takes in input, verifies that it is only a capital letter, and that it has not been used before.

sub takeInputAndValidate
{
	#Initalize wrongLetter Array
	my $wrongLetter = shift;
	#Make a Hash of the letters
	my %wrongLetters = map {$_ => 1 } @$wrongLetter;
	#Initalize the letter with invalid content
	my $letter = "AAA112";
	#Create error Flag
	my $errorFlag = 0;
	#Take Input while the length of the letter is greater than 1, while it does not only contain letters, and if it has already been guessed.
	while (length $letter >1 || $letter =~ m/[^A-Z]/ || exists($wrongLetters{$letter}))
	{
		if ($errorFlag >0)
		{
			print "You entered a character, which is not a letter, or multiple letters.\n";
		}
		print "Please Enter a letter. \n##";
		#Take Input and Uppercase it.
		chomp($letter = uc(<STDIN>));
		$errorFlag++;
	}
	return $letter;
}

gameLogic

gameLogic holds the majority of the actual hangman game. It returns the value of the win flag to the subroutine that calls it.

sub gameLogic
{
	#Initalizes variables
	my $letter = "";
	my $winFlag = 0;
	my $theWord = shift;
	my $fillIn = shift;
	my $wrongLetter = shift;
	my $wrongGuesses = 0;
	#Run the game through 10 errors.  The equivalant of head, body, arms, legs, hands, feet
	for (my $i = 0; $i < 11;)
	{
		system("clear");
		drawHang($i);
		print "\n";
		#Print FillIn Array
		printArray($fillIn,1);
		print "\n";
		#Print wrong letter array
		printArray($wrongLetter,2);
		print "\n";
		#Ask for Input
		$letter = takeInputAndValidate($wrongLetter);
		#Run Checkletter to check if its in the word
		checkLetter($theWord, $fillIn, $wrongLetter, $letter);
		#Make the counter equal to the length of the wrong letter array to count how many errors have been made.
		$i = scalar @$wrongLetter;
		#Make the arrays strings so they can be compared
		my $word = join ("",@$theWord);
		my $fillItIn = join ("",@$fillIn);
		#Compare the strings.  If they match Set win condition
		if ($word eq $fillItIn)
		{
			$winFlag = 1;
			last;
		}
	}
	#Print messages for win and loose conditions
	given ($winFlag)
	{
		when (0) 
		{
			print "You Loose!  The word was ";
			&printArray($theWord,3);
			print ".\n";
			break; 
		}
		when (1) 
		{
			print " You Win! The Word was "; 
			printArray($theWord, 2);
			print ".\n";
			break; 
		}
	}
	pressAKey();
	print "\n\n";
	return $winFlag;
}

pressAKey

pressAKey just waits for a key to be pressed.

sub pressAKey
{
	print "Press a Key to Continue\n";
	my $waitForIt = <STDIN>;
}

titlePage

titlePage displays a title page for the game.

sub titlePage
{
	system("clear");
#Create a 80x60 character title screen
	print colored ['bright_blue'],"╔══════════════════════════════════════════════════════════════════════════════╗\n";
	print colored ['bright_blue'],"║									       ║\n";
	print colored ['bright_blue'],"";
	print colored ['red'],        "      ██░ ██  ▄▄▄       ███▄    █   ▄████  ███▄ ▄███▓ ▄▄▄       ███▄    █     ";       
        print colored ['bright_blue'],"\n";
	print colored ['red'],        "     ▓██░ ██▒▒████▄     ██ ▀█   █  ██▒ ▀█▒▓██▒▀█▀ ██▒▒████▄     ██ ▀█   █     ";
        print colored ['bright_blue'],"\n";
	print colored ['red'],        "     ▒██▀▀██░▒██  ▀█▄  ▓██  ▀█ ██▒▒██░▄▄▄░▓██    ▓██░▒██  ▀█▄  ▓██  ▀█ ██▒    ";
        print colored ['bright_blue'],"\n";
	print colored ['red'],        "     ░▓█ ░██ ░██▄▄▄▄██ ▓██▒  ▐▌██▒░▓█  ██▓▒██    ▒██ ░██▄▄▄▄██ ▓██▒  ▐▌██▒    ";
        print colored ['bright_blue'],"\n";
	print colored ['red'],        "     ░▓█▒░██▓ ▓█   ▓██▒▒██░   ▓██░░▒▓███▀▒▒██▒   ░██▒ ▓█   ▓██▒▒██░   ▓██░    ";
        print colored ['bright_blue'],"\n";
	print colored ['red'],        "      ▒ ░░▒░▒ ▒▒   ▓▒█░░ ▒░   ▒ ▒  ░▒   ▒ ░ ▒░   ░  ░ ▒▒   ▓▒█░░ ▒░   ▒ ▒     ";
        print colored ['bright_blue'],"\n";
	print colored ['red'],        "      ▒ ░▒░ ░  ▒   ▒▒ ░░ ░░   ░ ▒░  ░   ░ ░  ░      ░  ▒   ▒▒ ░░ ░░   ░ ▒░    ";
        print colored ['bright_blue'],"\n";
	print colored ['red'],        "      ░  ░░ ░  ░   ▒      ░   ░ ░ ░ ░   ░ ░      ░     ░   ▒      ░   ░ ░     ";
        print colored ['bright_blue'],"\n";
	print colored ['red'],        "      ░  ░  ░      ░  ░         ░       ░        ░         ░  ░         ░     ";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"║                                                                              ║\n";
        print colored ['bright_blue'],"║                                                                              ║\n";
	print colored ['bright_blue'],"";
        print colored ['bright_green'], "                                ┌────────┐                                    ";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"";
        print colored ['bright_green'], "                                │        │                                    ";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"";
        print colored ['bright_green'], "";
        print colored ['red'], "@                                    ";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"";
        print colored ['bright_green'], "";
        print colored ['red'], "_/|\\_                                  ";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"";
        print colored ['bright_green'], "";
        print colored ['red'], "|                                    ";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"";
        print colored ['bright_green'], "";
        print colored ['red'], "_/ \\_                                  ";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"";
        print colored ['bright_green'], "";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"";
        print colored ['bright_blue'],  "                                ██████████                                    ";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"";
        print colored ['bright_blue'],  "                                ████████████                                  ";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"";
        print colored ['bright_blue'],  "                                ██████████████                                ";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"║                                                                              ║\n";
        print colored ['bright_blue'],"║                                                                              ║\n";
        print colored ['bright_blue'],"";
	print colored ['cyan'],"                              By: Jordan McGilvray                            ";
        print colored ['bright_blue'],"\n";
        print colored ['bright_blue'],"║                                                                              ║\n";
	print colored ['bright_blue'],"";
	print colored ['cyan'],"           Licenced under the GNU General Public Licence (GPL) v. 333.0         ";
        print colored ['bright_blue'],"\n";
	print colored ['bright_blue'],"║                                                                              ║\n";
        print colored ['bright_blue'],"╚══════════════════════════════════════════════════════════════════════════════╝\n";
	print "\n";
	pressAKey();
}

mainMenu takes in a y, n, yes, or no. It capitalizes the input, so it only evaluates on Y, N, YES, or NO. It displays a Main Menu text, and quits if N or NO are entered. It also captures how many games have been won. and how many have been played.

sub mainMenu
{
	my $gamesPlayed = 0;
	my $gamesWon = 0;
	for ($gamesPlayed = 0;; $gamesPlayed++)
	{
		if ($gamesPlayed > 0)
		{
			system("clear");
		        print colored ['green'],"#     #                    #     #\n";
        		print colored ['green'],"##   ##   ##   # #    #    ##   ## ###### #    # #    #\n";
		        print colored ['green'],"# # # #  #  #  # ##   #    # # # # #      ##   # #    #\n";
        		print colored ['green'],"#  #  # #    # # # #  #    #  #  # #####  # #  # #    #\n";
		        print colored ['green'],"#     # ###### # #  # #    #     # #      #  # # #    #\n";
        		print colored ['green'],"#     # #    # # #   ##    #     # #      #   ## #    #\n";
	        	print colored ['green'],"#     # #    # # #    #    #     # ###### #    #  ####\n";
	        	print "\n\n";
			my $errorFlag = 0;
			my $playGame ='asdf';
			while ($playGame =~ /[^Y|YES|N|NO]/ )
			{
				if ($errorFlag > 0)
				{
					print "Please Enter Y, YES, N, or NO\n";
				}
				print "Do you want to play a game of Hangman?\nEnter (Y)ES to play or (N)O to Quit.\n##";
				chomp ($playGame = uc(<STDIN>));
				$errorFlag++;
			}
			given ($playGame)
			{
				when ($playGame =~ /Y|YES/)
				{	
					$gamesWon = $gamesWon + mainRoutine();
					break;
				}
				when ($playGame =~ /N|NO/)
				{
					system("clear");
					print "Goodbye\n";
					last;
				}
			}		
		}
		else
		{
			$gamesWon = $gamesWon + mainRoutine();
		}
	}
	print "You won $gamesWon games of $gamesPlayed games played.\n";
}

mainRoutine

mainRoutine initalizes all of the game variables, and runs the other subroutines for the game to work.

sub mainRoutine
{
	#Initialize the arrays
	my @wrongLetter = ();
	my @theWord = ();
	my @fillIn = ();
	my $theWord = chooseWord(readInDictionary());
	@theWord = @$theWord;
	my $fillIn = initGuessArray(\@theWord);
	@fillIn = @$fillIn;
	#Run the Game
	my $win = gameLogic(\@theWord, \@fillIn, \@wrongLetter);
	return $win;
}

main

main just runs titlePage, and mainMenu.

sub main
{
	titlePage();
	mainMenu();
}

That is how I built hangman in Perl.

dict.pl

I also wrote the dict.pl script to get larger words from a list of all the words in English.

#! /usr/bin/perl
use warnings;
use strict;

sub readInDictionary
{
	#Opens the dictionary file
	open FILE, "dict.new" or die $!;
	#Saves the file to an array
	my @dict = <FILE>;
	#Returns a pointer to the array
	return \@dict;
}

sub getWords
{
	my $words = shift;
	my @keep;
	foreach (@$words)
	{
		if (length $_ > 5)
		{
			push (@keep, $_);
		}
	}
	return \@keep;
}

sub writeToFile
{
	my $InputArray = shift;
	open FILE, ">> dict.txt";
	print FILE @$InputArray;
	close(FILE);
}

sub main
{
	my $dictionary = readInDictionary();
	my @dict = @$dictionary;
	my $dictionary1 = getWords($dictionary);
	print $dictionary1."\n";
	my @dict1 = @$dictionary1;
	writeToFile($dictionary1);
}

main ();