PSWordle.psm1

function Get-PSWordleUpdate {
    begin {
        [string]$version = "0.0.7"
        try { 
            [string]$PublishedVersion = (Invoke-RestMethod "https://raw.githubusercontent.com/bwya77/PSModules/main/PSWordle/version.txt").Trim()
        } 
        catch {
            $_.Exception.Response.StatusCode.Value__
        }
    }
    Process {
        if (($version -ne $PublishedVersion) -and ($PublishedVersion.count -gt 0)) {
            [string]$Message = "A new version of PSWordle is available!
Current version: $version
Published version: $PublishedVersion
Please run Update-Module -Name PSwordle to grab the latest version.

Note: You can hide the update message by including the -IgnoreUpdates parameter when starting a new game"

        }
    }
    End {
        $Message
    }
}
function Set-ConfigItem {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $configItemName,
        [Parameter()]
        [string]
        $configItemValue,
        [Parameter()]
        [string]
        $configFile
    )
    begin {
        if ([System.Environment]::OSVersion.Platform -eq "Unix") {
            [string]$configFile = "$env:HOME\PSWordle\config.json"
        }
        Else {
            [string]$configFile = "$env:APPDATA\PSWordle\config.json"
        }
    }
    Process {
        $myObject = [PSCustomObject]@{
            $configItemName = $configItemValue
        }
        #export myObject to JSON
        $myObject | ConvertTo-Json | Out-File $configFile -Force
    }
}
function Get-ConfigFile {
    Begin {
        if ([System.Environment]::OSVersion.Platform -eq "Unix") {
            [string]$configFile = "$env:HOME\PSWordle\config.json"
        }
        Else {
            [string]$configFile = "$env:APPDATA\PSWordle\config.json"
        }
    }
    Process {
        if (-not(Test-Path -Path $configFile)) {
            #New-Item -ItemType Directory -Path $configFile -Force | Out-Null
            New-Item -ItemType File -Path $configFile -Force | Out-Null
        }
    }
    End {
        $configFile
    }
}
function Get-ConfigItem {
    param (
        [Parameter()]
        [string]
        $configItem,
        [Parameter()]
        [string]
        $configFile
    )
    Process {
        #get the configured username
        $userName = (Get-Content -Raw -Path $configFile -erroraction:SilentlyContinue | ConvertFrom-Json).$configItem
    }
    End {
        $userName
    }
}
function Get-PSWordleDictionary {
    Begin {
        $Platform = [System.Environment]::OSVersion.Platform
    }
    Process {
        if ($Platform -eq "Unix") {
            #Get dictionary file
            $dictionary = Select-String "^[a-z]{5}$" "$PSScriptRoot/src/dictionary.txt"
        }
        #If we are on Windows
        Else {
            #Get dictionary file
            $dictionary = Select-String "^[a-z]{5}$" "$PSScriptRoot\src\dictionary.txt"
        }
    }
    End {
        $dictionary
    }
}
function New-PSWordleWord {
    begin {
        #Figure out what platform we're on
        $Platform = [System.Environment]::OSVersion.Platform
        #If we are on Unix
        if ($Platform -eq "Unix") {
            #Get 5 letter words from the files
            $words = Select-String "^[a-z]{5}$" "$PSScriptRoot/src/words.txt"   
        }
        #If we are on Windows
        Else {
            #Get 5 letter words from the files
            $words = Select-String "^[a-z]{5}$" "$PSScriptRoot\src\words.txt"
        }
    }
    process {
        #Get a random word from the word list
        Get-Random $Words
    }
}
function New-PSWordleUser {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $userName
    )
    Begin {

    }
    Process {
        While ($true) {
            Write-Host "Checking online to see if that username is available"
            $Check = Get-PSWordleLeaderboardUser -username $userName -Uri "https://funpswordle.azurewebsites.net/api/wordleleaderboard?code=LesznI7agk9vyt3pEu1YCb4ehbo4Mz1lQHewvRfgaw/FNOPXQMiSLg=="

            # It's returning a string not a boolean so I need to format the IF statement this way
            if ($Check -eq "True") {
                Write-Host "That username is already taken! Please enter a new one." -ForegroundColor Yellow
                $userName = Read-Host -Prompt "Please enter a new UserName "
            }
            else {
                Write-Host "Success! Username is available" -ForegroundColor Green; break
            }
        }
    }
    End {
        Set-ConfigItem -ConfigItemName username -ConfigItemValue $username
        $userName
    }
}
function Get-PSWordleLeaderBoard {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $Uri = "https://funpswordle.azurewebsites.net/api/wordleleaderboard?code=LesznI7agk9vyt3pEu1YCb4ehbo4Mz1lQHewvRfgaw/FNOPXQMiSLg=="
    )
    begin{
        $Platform = [System.Environment]::OSVersion.Platform
    }
    process{
        $Param = @{
            Uri  = $Uri
            Body = @{
                "Request" = "Results"
            }
        }
        $Results = Invoke-WebRequest @Param
    }
    end {
        #Get the results back which come back as JSON, convert to a object
        if ($Platform -eq "Unix") {
            $Results.Content | ConvertFrom-Json | select-object PlayerTag, @{N="Score"; E={[int32]$_.Score}} | sort-object Score -Descending
        }
        Else
        {
            $data = $Results.Content | ConvertFrom-Json 
            $data | select-object PlayerTag, @{N="Score"; E={[int32]$_.Score}} | sort-object Score -Descending
        }
    }
}
function Get-PSWordleLeaderboardUser {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]
        $userName,
        [Parameter(Mandatory)]
        [string]
        $Uri

    )
    begin
    {
        $Param = @{
            Uri = $Uri
            Body = @{
                "Request"  = "CheckUser"
                "Username" = $username
            }
        }
    }
    Process
    {
        $Results = Invoke-WebRequest @Param
    }
    End 
    {
        $Results.Content
    }
}
function Set-PSWordleScore {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]
        $User,
        [Parameter(Mandatory)]
        [int]
        $Score,
        [Parameter(Mandatory)]
        [string]
        $Uri
    )
    Begin {
        $Param = @{
            Uri  = $Uri
            Body = @{
                "Request"  = "CheckUser"
                "Username" = $user
            }
        }
        $Results = Invoke-WebRequest @Param 
    }
    Process {
        #If our user is currently on the leaderboard we need to adjust the score
        if ($Results.Content -eq 'True') {
            $ModifiedDateTime = get-date -Format yyyyMMdd:HHmmss
            $Param = @{
                Uri   = $Uri
                Body = @{
                    "Request"          = "AddUser"
                    "Username"         = $User
                    "Score"            = $Score
                    "ModifiedDateTime" = $ModifiedDateTime
                    "IsPresent"        = "true"
                }
            }
            $Results = Invoke-WebRequest @Param 
        }
        #if our user is not currently on the leaderboard, we can just add their score
        else {
            $CreatedDateTime = get-date -Format yyyyMMdd:HHmmss
            $Param = @{
                Uri = $Uri
                Body = @{
                    "Request"          = "AddUser"
                    "Username"         = $User
                    "Score"            = $Score
                    "ModifiedDateTime" = $CreatedDateTime
                    "CreatedDateTime"  = $CreatedDateTime
                    "IsPresent"        = "false"
                }
            }
            $Results = Invoke-WebRequest @Param 
        }
    }
    End
    {
        $Results.Content
    }
}
Function Get-MatchedItems {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $Guess,
        [Parameter()]
        [string]
        $Word
    )
    Begin {
        [array]$changechars = @()
        [int]$count = -1
        [string]$guessNew = ""
    }
    Process {
        0..4 | ForEach-Object {
            $count++
            if ($guess[$_] -eq $word[$_]) {
                $changechars += $count
            }
        }
        [int]$count = -1
        
        $Guess.ToCharArray() | ForEach-Object {
            $Count++
            if ($count -in $changechars) {
                $guessNew += $_
            }
            Else {
                $guessNew += "*"
            }
        }
    }
    End {
        $guessNew.toupper()
    }
}
Function New-PSWordleGame {
    [CmdletBinding()]
    param (
        [Parameter()]
        [Switch]
        $CompeteOnline,
        [Parameter()]
        [Switch]
        $HardMode,
        [Parameter()]
        [Switch]
        $IgnoreUpdates,
        [Parameter()]
        [Switch]
        $UseEmojiResponses
    )
    Begin {
        if (-not($IgnoreUpdates))
        {
            $Info = Get-PSWordleUpdate
            if (-not([string]::IsNullOrWhiteSpace($info))) {
                Write-Host $Info -ForegroundColor Yellow
            }
        }
        #region <start> Username items
        if ($CompeteOnline) {

            #Get the config file, if none exists, the function will create it and return the path
            $configFile = Get-ConfigFile
            #Read the config file and extract the username
            [string]$userName = Get-ConfigItem -ConfigItem username -ConfigFile $configFile
            if ([string]::IsNullOrWhiteSpace($userName))
            {
                $userNameAttempt = Read-Host "Please enter a username you wish to use"
                $userName = New-PSWordleUser -userName $userNameAttempt
            }
        }
        #Get a new random word
        $Word = New-PSWordleWord
        #Get Dictionary words
        $dictionaryWords = Get-PSWordleDictionary
        #Int counter to keep track of the number of times we have tried to guess the word
        [int]$guessCount = 1
        $wordleShare = "", "", "", "", "", "", ""
        #For hard mode, keep an array of correctly guessed letters
        [array]$correctLetters = @()
        [hashtable]$correctLetterPlacement = @{}
        #Create a variable to hold the letters that have been guessed
        [array]$guessedLetters = @()
        #Create a empty hashtable / dictionary that will hold letters that are NOT in the word
        [string[]]$notLetters = @()
        #Keep a table of the points for each guess
        [hashtable]$pointlookup = @{
            1 = 10
            2 = 8
            3 = 6
            4 = 4
            5 = 2
            6 = 1
        }
            #region <start> New game prompt and directions
            "
 _ _ __ ____ ____ __ ____
/ )( \ / \( _ \( \( ) ( __)
\ /\ /( O )) / ) D (/ (_/\ ) _)
(_/\_) \__/(__\_)(____/\____/(____)
                       "

                       if ($CompeteOnline) {
                           Write-Host -ForegroundColor Green "Welcome back: $username!"
               "
Get points based on how quickly you can guess the word!
If you guess the word in the first try, you get 10 points.
2nd try: 8 points
3rd try: 6 points
4th try: 4 points
5th try: 2 points
6th try: 1 point
If you don't guess it at all you will lose 1 point.
"

                   }
                   if ($HardMode)
                   {
"
Any revealed hints must be used in subsequent guesses. Green letters must remain in the correct position.
"

                   }
"The WORDLE word is 5 characters long."
Write-Host -ForegroundColor Green "GREEN" -NoNewline; Write-Host " means the letter is in the word and in the correct spot"
Write-Host -ForegroundColor Yellow "YELLOW" -NoNewline; Write-Host " means the letter is in the word but in the wrong spot"
Write-Host -ForegroundColor DarkGray "GRAY" -NoNewline; Write-Host " means the letter is not in the word"
        #region <end>
    }
    Process {
        while ($true) {
            If ($notLetters.count -gt 0)
            {
                Write-Host "Not in the word: $($notLetters | Sort-Object)" -ForegroundColor DarkGray
            }
            #Clear the guessed letter array
            $guessedLetters = @()
            #Prompt the user for a guess
            [string]$guess = (Read-Host "($guessCount) Guess a 5-letter word").ToUpper()

            if ($HardMode)
            {
                #For hard mode, if the guess does not contain correct previously guessed letters
                if (($guess -notcontains $correctLetters) -and ($correctLetters.Count -gt 0))
                {
                    Do {
                        #Iterate through the array and see if any letters are not in the guessed word
                        if (($correctLetters| ForEach-Object{$guess.contains($_)}) -contains $false)
                        {
                            Write-Host "You must use all the correct letters from the previous guess" -ForegroundColor Red
                            #Re-prompt the user for a guess
                            $guess = (Read-Host "($guessCount) Guess a 5-letter word").ToUpper()
                        }
                    }
                    Until (($correctLetters| ForEach-Object{$guess.contains($_)}) -notcontains $false)
                }
                #for hard mode, make sure the letters that were guesed in the correct position are still in the correct position
                if ($correctLetterPlacement.count -gt 0)
                {
                    $correctLetterPlacement.GetEnumerator() | ForEach-Object {
                        #Until all the letters are in the right spot
                        While ($guess[$_.name] -ne $_.value) {
                            Write-Host "Letters shown to be in the correct spot must remain in the correct spot" -ForegroundColor Red
                            #Re-prompt the user for a guess
                            $guess = (Read-Host "($guessCount) Guess a 5-letter word").ToUpper()
                        }
                    }
                }
            }
            
            #If you guess is the word, you win
            if ($guess -eq $word.Line) {
                #If we are running on PWSH then we can use emojis
                if ($PSVersionTable.PSEdition -eq "Core")
                {
                    Write-Host
                    Write-Host "🎉💥 You Win! 💥🎉" -ForegroundColor Green; $wordleShare[$guessCount] = "🟩" * 5; break
                }
                #If we are running on Windows PowerShell then we can't use emojis
                else {
                    Write-Host
                    Write-Host "You Win!" -ForegroundColor Green; $wordleShare[$guessCount] = "*" * 5; break
                }
            }
            #If your guess is too short or too long
            if ($guess.Length -ne 5) {
                Write-Host "Your guess must be 5 letters!" -ForegroundColor Red; continue 
            }
            #If the guess appears to not be a valid word
            if ($guess -notin $dictionaryWords.Line) {
                Write-Host "That word is not in our dictionary, please try again." -ForegroundColor Red ; continue 
            }
            #Get all letters that have been guessed in the correct spots
            [string]$Matches = Get-MatchedItems -Guess $Guess -Word $Word.line
            #for (<Init>; <Condition>; <Repeat>) { <Body> }
            #for 5 loops, do the following ( start at 0, while the number is less than 5 run the block, afterwards increment the number by 1)
            for ($pos = 0; $pos -lt 5; $pos++) {
                $shareImage = "⬛️"
                #Add guessed letters to the array
                
                #region <start> Reduce letter false positives
                $guessedLetters += $guess[$pos]
                #See how many instances of the guessed letter there are in the word
                [int32]$Appearances = ($Word.line[0..4] -eq $guess[$pos]).count
                #If we have guessed the letter more than it appears in the word
                    if ($guess[$pos] -eq $word.Line[$pos]) {
                        #Add the letter to the correct letters array
                        $correctLetters += $guess[$pos]

                        #Hard mode: Add correct letters and their placement in the hashtable
                        if ($HardMode) {
                            if ($correctLetterPlacement.Keys -notcontains $pos) {
                                [string]$Key = $pos
                                [string]$Value = $word.Line[$pos]
                                #Add our guessed letter to the hashtable / dictionary
                                $correctLetterPlacement.Add($Key, $Value)
                            }
                        }
                        if ($UseEmojiResponses) {
                            Write-Host "🟩" -NoNewLine; $shareImage = "🟩" 
                        }
                        else {
                            Write-Host -ForegroundColor Green $guess[$pos] -NoNewLine; $shareImage = "🟩" 
                        }
                    }
                    #If the letter is in the word, but not in the correct position, we have guessed the letter, but not the correct position
                    elseif ($guess[$pos] -in $word.Line.ToCharArray()) {
                        # If the letter appears once, and its in the $Matches string indicating that its in the correct spot, then any other instance of the letter is incorrect
                        if (($Appearances -eq 1) -and ($Matches.ToCharArray() -contains $guess[$pos])) {
                            if ($UseEmojiResponses) {
                                Write-Host "⬛️" -NoNewLine; $shareImage = "⬛️" 
                            }
                            else {
                                Write-Host -ForegroundColor DarkGray $guess[$pos] -NoNewLine; $shareImage = "⬛️" 
                            }
                        }
                        # Get the letters from the guessed word up until the current letter and then see how many times the current character appears
                        # Then get the times the current letter appears in the word
                        # If the guessed letter is stil lower than the total times it shows up in the word, then its valid but in the wrong spot
                        elseif(($guess[0..$pos] -eq $guess[$pos]).Count -le ($word.Line.ToCharArray() -eq $guess[$pos]).Count) {
                            #Add the letter to the correct letters array
                            $correctLetters += $guess[$pos]
                            if ($UseEmojiResponses) {
                                Write-Host "🟨"  -NoNewLine; $shareImage = "🟨" 
                            }
                            else {
                                Write-Host -ForegroundColor Yellow $guess[$pos] -NoNewLine; $shareImage = "🟨" 
                            }
                        }
                        Else {
                            #Add the letter to the correct letters array
                            $correctLetters += $guess[$pos]
                            if ($UseEmojiResponses) {
                                Write-Host "⬛️" -NoNewLine; $shareImage = "⬛️" 
                            }
                            else {
                                Write-Host -ForegroundColor DarkGray $guess[$pos] -NoNewLine; $shareImage = "⬛️" 
                            }
                        }
                    }
                    else {
                        if ($UseEmojiResponses) {
                            Write-Host "⬛️" -NoNewLine
                        }
                        else {
                            Write-Host -ForegroundColor DarkGray $($guess[$pos]) -NoNewLine 
                        }
                        if (-not($notLetters -contains $guess[$pos])) {
                            #Add our guessed letter to the array
                            $notLetters += $guess[$pos]
                        }
                    }
                
                $wordleShare[$guessCount - 1] += $shareImage 
            }
    
            $guessCount++
            if ($guessCount -eq 7) {
                #If you did not guess the word in 6 guesses, replace the guess counter with a X
                [string]$guessCount = "X"
                Write-Host; Write-Host "Too many guesses! The right word was: '$($word.Line.toupper())'"
                if ($CompeteOnline) {
                    Write-Host "You have lost 1 point."
                    Write-Host "Updating your score on the leaderboard..."
                    Set-PSWordleScore -user $username -Score -1 -uri "https://funpswordle.azurewebsites.net/api/wordleleaderboard?code=LesznI7agk9vyt3pEu1YCb4ehbo4Mz1lQHewvRfgaw/FNOPXQMiSLg=="
                }
                break 
            }
            Write-Host
        }
    }
    End {
        If ($CompeteOnline) {
            #using the hashtable, figure out how many points we get based on how quickly we guessed the word
            $points = $pointlookup[$guessCount] 
            write-host " "
            if ($Points -eq 1) {
                Write-Host "You have earned $points point!" 
            }
            else {
                Write-Host "You have earned $points points!" 
            }
            Write-Host "Adding your score to the leaderboard..."
            Set-PSWordleScore -user $username -Score $points -uri "https://funpswordle.azurewebsites.net/api/wordleleaderboard?code=LesznI7agk9vyt3pEu1YCb4ehbo4Mz1lQHewvRfgaw/FNOPXQMiSLg=="
        }
        #If we are running on PWSH or Windows PowerShell, if Windows PowerShell we cannot display emojis
        if ($PSVersionTable.PSEdition -eq "Core") {
            Write-Host "PSWORDLE $($word.LineNumber) $guessCount/6`r`n"
            $wordleShare | Where-Object { $_ }
        }
        Else {
            #Display the line number the wordle word was found on as well as how many guesses it took
            Write-Host "PSWORDLE $($word.LineNumber) $guessCount/6`r`n"
        }
    }
}