10
\$\begingroup\$

I had read the question: Bush Wanderer - code intended for teaching and wanted to play with PowerShell. Including the rules of the game from that same question.

Bush Wanderer

Task 1

Create a 5x5 grid of hashes(#) where the player is an x on the board

Prompt the player to move in either N, E, S or W direction and then reprint the board to show the new position.

Example:

x # # # #
# # # # # 
# # # # #
# # # # #
# # # # #
Which way would you like to move?(N,E,S or W) 

Task 2

  • Ensure the player does not move off of the board

  • Place a hidden chest somewhere on the board at random

  • End the game when the chest is found

  • Tell the player how many moves it took to find the chest

  • Add a visible (but blind) dragon that can teleport around the board at random and will eat you and end your game if it gets you before
    you get the chest


Made with PowerShell v4.0. I tried to use an enum but had to create a type using c# which I don't do much. I take advantage of how PowerShell does variable scoping a far bit in this code. From about_scopes

An item you include in a scope is visible in the scope in which it was created and in any child scope

Instead of passing more parameters into my functions I just call them in the parent scope of the script which occurs by default. I also changed the player character to P as that made more sense to me.

I use the PromptForChoice to get the users choice. I had wanted to use that logic and remove the choices that are not available to the user but I can't control the choice index that is returned so I present all choices and will continue to prompt until the user enters a valid direction choice.

If someone reviews this and questions my function names that is all well and good but just know I am trying to adhere to the approved verb list for PowerShell. You are welcome to comment on this either way though. Don't hold back!

# Coordinate template to use to represent object locations in the board jagged array.
[char]$bushSymbol = "#"
$entityTemplate = @{X=0;Y=0;Character=$bushSymbol}
$moves = 0

# Determine the size of the board.
$boardSize = 5

# Base direction choices enum
Add-Type -TypeDefinition @"
   public enum Direction
   {
      North = 0,
      South = 1,
      East = 2,
      West = 3
   }
"@

# Game board flags.
$gameOver = $false

# Initialize Game board entities from template
$playerCharacter = $entityTemplate.psobject.Copy()
$playerCharacter.Character = "P"
$dragonCharacter = $entityTemplate.psobject.Copy()
$dragonCharacter.Character = "D"
$treasureChest = $entityTemplate.psobject.Copy()

# Initialize Game board object as a jagged array
$gameBoard = 1..$boardSize | ForEach-Object{,(@($bushSymbol) * $boardSize)}

# Menu text. 
$title = "Which way?"
$message = "Which direction would you like to travel?"

function Get-PlayerDirectionChoice(){
    # This function will present the end user with viable direction choices. Choices can be limited based on current location.
    $options = ([Direction]::GetNames([Direction]) | ForEach-Object{
        New-Object System.Management.Automation.Host.ChoiceDescription "&$_", "Character will attempt to travel $($_.ToLower())"    
    })

    return $host.UI.PromptForChoice($title, $message, $options, 0) 
}

function Get-RandomBoardPosition([hashtable]$Entity, [int]$BoardSize){
    # Get a random x and y. Number are between 0 and the boardsize minus 1
    $entity.X = Get-Random -Maximum $boardSize
    $entity.Y = Get-Random -Maximum $boardSize
}

function Test-ValidPlayerDirection([Direction]$choice){
    # Function will return a boolean based on the propsed movement of the player character on the board.
    $result = $true        # Assume true

    # Check to see if the player is on any of the boundries
    if($playerCharacter.Y -eq 0 -and $choice -eq [Direction]::North){$result = $false}
    if($playerCharacter.Y -eq ($boardSize - 1) -and $choice -eq [Direction]::South){$result = $false}
    if($playerCharacter.X -eq ($boardSize - 1) -and $choice -eq [Direction]::East){$result = $false}
    if($playerCharacter.X -eq 0 -and $choice -eq [Direction]::West){$result = $false}

    return $result
}

function Test-EntityCollision($FirstEntity, $SecondEntity){
    # Test to see if two entities have the same X and Y values. If they do return true.
    if($FirstEntity.X -eq $SecondEntity.X -and $FirstEntity.Y -eq $SecondEntity.Y){
        return $true
    } else {
        return $false
    }
}

function Show-GameBoard{
    # Function will display on console the current game board layout as populated with it entities. 
    Clear-Host 

    # Edit in the positions of the objects.
    $gameBoard[$playerCharacter.Y][$playerCharacter.X] = $playerCharacter.Character
    $gameBoard[$dragonCharacter.Y][$dragonCharacter.X] = $dragonCharacter.Character

    # Function will draw the game board to the console. 
    0..($boardSize - 1) | ForEach-Object{$gameBoard[$_] -join " "}
}

# Get the starting location of all board entities
$treasureChest, $dragonCharacter, $playerCharacter | ForEach-Object{
    Get-RandomBoardPosition -Entity $_ -BoardSize $boardSize
}

# Start the game. Show the board
Show-GameBoard

Do{
    # Get the gardener to put the bushes back before the character leaves.
    $gameBoard[$playerCharacter.Y][$playerCharacter.X] = $bushSymbol

    # Determine where the player is going to go. 
    $choice = Get-PlayerDirectionChoice
    if(Test-ValidPlayerDirection ([Direction]::GetName([Direction],$choice))){
        # Move the character based on the choice selected. 
        switch($choice){
            ([Direction]::North -as [int]) {$playerCharacter.Y = $playerCharacter.Y - 1}
            ([Direction]::South -as [int]) {$playerCharacter.Y = $playerCharacter.Y + 1}
            ([Direction]::East -as [int])  {$playerCharacter.X = $playerCharacter.X + 1}
            ([Direction]::West -as [int])  {$playerCharacter.X = $playerCharacter.X - 1}
        }
        # Raise the moves
        $moves++

        # Show the updated board
        Show-GameBoard

        # Check to see if the dragon caught the player
        if(Test-EntityCollision $playerCharacter $dragonCharacter){"The Dragon eats you and you took $moves move(s)."; $gameOver = $true}

        # Check to see if the player found the treasure
        if(Test-EntityCollision $playerCharacter $treasureChest){"You found the treasure in $moves move(s)."; $gameOver = $true}

        # Get the gardener to put the bushes back before the dragon leaves.
        $gameBoard[$dragonCharacter.Y][$dragonCharacter.X] = $bushSymbol

        # Move the dragon 
        Get-RandomBoardPosition -Entity $dragonCharacter -BoardSize $boardSize

    } else {
        # The choice used would place the player out of bounds.
        Write-Host "You are blocked by a magical force and cannot move that way."
    }
} While(-not $gameOver)

What the start of the game looks like

# # # # #
# # # # #
# D # # P
# # # # #
# # # # #

Which direction would you like to travel?
You are looking for a chest that is hidden in the bushes. Watch out for the clumsy Dragon that will eat you if he
catches you.
[N] North  [S] South  [E] East  [W] West  [?] Help (default is "N"):

A sample success screen

# # # # P
# # # # #
# D # # #
# # # # #
# # # # #
You found the treasure in 14 move(s).
\$\endgroup\$
4
\$\begingroup\$

I like it, I learnt a few things about objects from this. I also really liked the board size can be easily changed.

Immediate Collision

When playing I noticed the Dragon was allowed to start in the same position as the Player. While it's not explicitly stipulated in the tasks, it might be nice to avoid that:

$treasureChest, $dragonCharacter | ForEach-Object {
    Get-RandomBoardPosition -Entity $_ -BoardSize $boardSize
}
Do {
   Get-RandomBoardPosition -Entity $playerCharacter -BoardSize $boardSize
} Until ($playerCharacter.Y -ne $dragonCharacter.Y -or $playerCharacter.X -ne $dragonCharacter.X)

The C# Type

Replace the C# type with:

$Directions = @{
   North = 0
   South = 1
   East = 2
   West = 3
}

Replace all instances of [Direction]::North and [Direction]::North -as [Int] with $Directions.North.

Change parameter type of Test-ValidPlayerDirection from [Direction] to [String] (or omit the type altogether).

New Get-PlayerDirectionChoice function:

function Get-PlayerDirectionChoice(){
    $options = $Directions.GetEnumerator() | % {
       New-Object System.Management.Automation.Host.ChoiceDescription $_.Name
    }

    $host.UI.PromptForChoice($title, $message, $options, 0) 
}

And testing the direction:

$choice = Get-PlayerDirectionChoice
if(Test-ValidPlayerDirection ($choice)){
...

Function full of Ifs

Instead of evaluating conditions, setting return values then returning a [Bool], return the condition itself:

function Test-ValidPlayerDirection($choice){
   Return -not (
      ($playerCharacter.Y -eq 0 -and $choice -eq $Directions.North) -or
      ($playerCharacter.Y -eq ($boardSize - 1) -and $choice -eq $Directions.South) -or
      ($playerCharacter.X -eq ($boardSize - 1) -and $choice -eq $Directions.East) -or
      ($playerCharacter.X -eq 0 -and $choice -eq $Directions.West)
   )
}

function Test-EntityCollision($FirstEntity, $SecondEntity){
    Return $FirstEntity.X -eq $SecondEntity.X -and $FirstEntity.Y -eq $SecondEntity.Y
}

The loop was working fine, but it seemed odd the Dragon would move before the next turn and after a collision had been checked. You could reorder the loop from this:

# Get the gardener to put the bushes back before the character leaves.
# Determine where the player is going to go. 
# Move the character based on the choice selected. 
# Raise the moves
# Show the updated board
# Check to see if the dragon caught the player
# Check to see if the player found the treasure
# Get the gardener to put the bushes back before the dragon leaves.
# Move the dragon 

To this:

# Determine where the player is going to go. 
# Get the gardener to put the bushes back before the character leaves.
# Move the character based on the choice selected. 
# Get the gardener to put the bushes back before the dragon leaves.
# Move the dragon 
# Raise the moves
# Show the updated board
# Check to see if the dragon caught the player
# Check to see if the player found the treasure
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.