Add Random Question Features to a PowerShell Game

Add Random Question Features to a PowerShell Game

  • Comments 1
  • Likes


Summary: Learn how to add random question features to a Windows PowerShell script game.

Microsoft Scripting Guy Ed Wilson here. Well, the Scripting Wife and I have really enjoyed Ottawa. The class I am teaching on Windows PowerShell has absolutely rocked! Ottawa is one of my favorite cities (there is a really cool woodworking store here), and I have been able to spend a decent amount of time with my friend George. All in all, it has seemed like vacation more than work. Next week, we are in Montreal.

Anyway, I have continued playing with my Windows PowerShell quiz script, and today I want to share my latest iteration. It provides the ability to choose a specific number of questions. The questions themselves are selected in a random manner. The script will also grade performance and provide the number of right and wrong answers, and the percentage of correct answers as feedback.

Note This is the fourth part of a multipart series of articles about writing a Windows PowerShell quiz script. On the first day, I looked at replacing random letters in a string. Next, I moved the code into a function and added parameter validation to limit the values that can be supplied to the function. In this way I was able to prevent a divide by zero error that could arise depending on what someone supplied from the command line. This is actually a great way to do error handling – prevent the error from arising in the first place. Yesterday I added the question and answer feature for the Windows PowerShell cmdlet name game. Today I am adding the ability to choose a specific number of questions, as well as a grade feature.

The complete New-CmdletPuzzleCountQuestions.ps1 script is shown here.

function New-CmdletPuzzle

{

 Param(

  [Parameter(Position=0,

             HelpMessage="A number between 2 and 7")]

  [alias("Level")]

  [ValidateRange(2,7)]

  [int]$difficulty = 4

 ) #end param

 $array = @()

 $hash = New-Object hashtable

 $array = Get-Command -CommandType cmdlet |

 ForEach-Object { $_.name.tostring() }

 

 Foreach($cmdlet in $array)

 {

  $rndChar = get-random -InputObject ($cmdlet.tochararray()) `

           -count ($cmdlet.length/$difficulty)

  foreach($l in $rndchar)

  {

   $cmdletP = $cmdlet

   $cmdletP = $cmdletP.Replace($l,"_")

  }# end foreach l

  $hash.add($cmdlet,$cmdletP)

 } #end foreach cmdlet

 $hash

} #end function New-CmdletPuzzle

 

Function New-Question

{

 Param(

  [hashtable]$Puzzle,

  [int]$num = 10

 )

  $quiz = @{}

  $right = $wrong = 0

  Get-Random -InputObject $($puzzle.keys) -Count $num |

   ForEach-Object { $quiz.add($_,$puzzle.item($_)) }

   Foreach ($p in $quiz.KEYS)

    {

     $rtn = Read-host "What is the cmdlet name $($quiz.item($P))"

     If($quiz.contains($rtn))

      {

       "Correct $($quiz.item($P)) equals $p"

       $right ++

       }

     ELSE

      {

       "Sorry. $rtn is not right. $($quiz.item($P)) is $p"

       $wrong ++

       }

     } #end foreach $P

     New-Object PSObject -Property @{

       "right" = $right

       "Wrong" = $wrong

       "Percent" = "{0:N2}" -f ($right/$num*100)  }

    

} #end function New-Question

 

# *** Entry point to script ***

 

$puzzle = New-CmdletPuzzle -level 5

New-Question -puzzle $puzzle -num 5

I did not make any changes to the New-CmdletPuzzle function, so there is no need to discuss it today. I did make several changes to the New-Question function, and that is what I will discuss today. The first change I made was to add an addition parameter to allow the user to determine the number of questions to receive. I set the default value to 10, which seems like a reasonable number (it also made it easy to check the accuracy of the percentage right feature). This portion of the script is shown here:

Function New-Question

{

Param(

  [hashtable]$Puzzle,

  [int]$num = 10

)

Next I create an empty hash table that I store in the $quiz variable. This hash table creates the quiz after the specific number of random questions is chosen. I then initialize the $right and $wrong variables to the number zero. At first I used $null, but when all the questions were right or all wrong, it was not obvious because the output would be blank. The number zero makes it obvious that a field is empty. This portion of the function is shown here:

  $quiz = @{}

  $right = $wrong = 0

The hardest part of the revised function is the code that accepts the original puzzle containing all cmdlet names and cmdlet names with obscured string. The Get-Random cmdlet will not select key value pairs from a hash table. So what I had to do was get a collection of keys, and allow the Get-Random cmdlet to choose a specific number of keys from the hash table. I pipe the keys to a Foreach-object cmdlet where I use the key to retrieve the associated value from the hash table. I then add both the key and the value to the $quiz hash table. This provides me with a hash table that contains the newly selected cmdlet and obscured cmdlet names. This portion of the script is shown here:

  Get-Random -InputObject $($puzzle.keys) -Count $num |

   ForEach-Object { $quiz.add($_,$puzzle.item($_)) }

Because I created a new hash table to hold the newly selected questions and answers, I had to change the name of the variables used in the Foreach loop. This is also where I added the use of $right counter variable. This portion of the script is shown here:

Foreach ($p in $quiz.KEYS)

    {

     $rtn = Read-host "What is the cmdlet name $($quiz.item($P))"

     If($quiz.contains($rtn))

      {

       "Correct $($quiz.item($P)) equals $p"

       $right ++

      }

The same type of changes are made to the else portion of the Foreach loop. This portion of the script is shown here:

ELSE

      {

       "Sorry. $rtn is not right. $($quiz.item($P)) is $p"

       $wrong ++

       }

     } #end foreach $P

After all of the questions are answered, it is time to evaluate the performance. I use a custom psobject to do this. I add the right, wrong, and percent properties to it. Here is the code for the New-Object.

New-Object PSObject -Property @{

       "right" = $right

       "Wrong" = $wrong

       "Percent" = "{0:N2}" -f ($right/$num*100)  }

    

} #end function New-Question

The entry point to the script does not really need to change. I decided to use the num parameter to change the number of questions asked from the default of 10 to 5. Here is the entry point:

$puzzle = New-CmdletPuzzle -level 5

New-Question -puzzle $puzzle -num 5

When the script runs, each question is evaluated as it is answered. After complete, the performance is displayed. This is illustrated in the following figure.

Image of quiz performance

 

That is about all there is for now. I think See you tomorrow when I will bring this series to an exciting conclusion. I have a bit of cleanup I am wanting to do, and I also want to add a couple more features.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hi Ed,

    nice addition to the cmdlet puzzle!

    I have to repeat yesterday's error ... you already agreed upon it but maybe this blog entry was already ready then: We have to move    "$cmdletP = $cmdlet" before the foreach loop!

    But I wouldn't have even mentioned it, if I haven't had overseen a much more important error, which I realized just now!

    In fact you can enter ANY valid Cmlet name as a solution to yesterday's puzzle!

    And today, we can add any that is contained in the hash "$quiz" which will evaluated as a right solution!

    The problem is ( in today's article ) the Test after the solution is read:

            $rtn = Read-host "What is the cmdlet name $($quiz.item($P))"

            If($quiz.contains($rtn))

             {

              "Correct $($quiz.item($P)) equals $p"

              $right ++

             }

    "$rtn" is entered by the user. ... OK!

    The next line only compares $rtn to any of the cmdlet names included in the "set" that have been randomly choosen to be part of our hash: $quiz

    " If($quiz.contains($rtn))" says nothing more than: Is my answer equal to any cmdlet name, that is contained in my $quiz. If so, we are correct ( which maybe wrong of course )

    We have to ask: "If ($rtn -eq $P)" to be correct!

    The same is true for yesterday's blog entry!!

    And I really really ask myself, why I haven't noticed this at first sight!!??

    Maybe I have been too eagerly trying to get the Cmdlets right ... I never tried to enter any correct Cmdlet name that is unrelated to the solution ... ( This is nothing a usually do if have to test our applications :-)

    Klaus