Last active
March 24, 2019 18:37
-
-
Save JohnLBevan/08b3bde2c00e0dd32e701e77e2fe297b to your computer and use it in GitHub Desktop.
Simulation of the Coin Toss game, based on Matt Parker's Lecture https://youtu.be/6JwEYamjXpA?t=1870
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Simulation of https://youtu.be/6JwEYamjXpA?t=1870 | |
function Get-CoinTossPossibleValues {@('H','T')} | |
function Get-CoinToss { | |
[CmdletBinding()] | |
Param ( | |
[Parameter()] | |
[int]$NumberOfTosses = 1 | |
, | |
[Parameter()] | |
[string[]]$PossibleValues = (Get-CoinTossPossibleValues) | |
) | |
Process { | |
@(1..$NumberOfTosses) | ForEach-Object { | |
Get-Random -InputObject $PossibleValues | |
} | |
} | |
} | |
function Get-CoinTossPrediction { | |
[CmdletBinding(DefaultParameterSetName = 'OpponentFirst')] | |
Param ( | |
[Parameter(ParameterSetName = 'OpponentFirst')] | |
[ValidateScript({$_.Count -gt 0})] | |
[string[]]$OpponentsPrediction | |
, | |
[Parameter(ParameterSetName = 'MeFirst')] | |
[ValidateScript({$_ -gt 0})] | |
[int]$SequenceLength | |
, | |
[Parameter()] | |
[Switch]$Random #i.e.disable the intelligent/competitive answer, just throw out some values in any order | |
) | |
Process { | |
if ($Random.IsPresent) { | |
if ($PSCmdlet.ParameterSetName -eq 'OpponentFirst') { | |
$SequenceLength = $OpponentsPrediction.Count | |
} | |
1..$SequenceLength | ForEach-Object {Get-CoinToss} | |
} else { | |
if ($PSCmdlet.ParameterSetName -eq 'OpponentFirst') { | |
switch ($OpponentsPrediction.Count) { | |
0 { throw 'The opponent must make a prediction!' } #this will never happen thanks to validation above | |
1 { Get-CoinTossPossibleValues | Where-Object {$_ -ne $OpponentsPrediction[0]} | Select-Object -First 1 } | |
default { | |
if ($OpponentsPrediction[0] -eq $OpponentsPrediction[1]) { | |
Get-CoinTossPrediction -OpponentsPrediction @($OpponentsPrediction[0]) | |
} else { | |
$OpponentsPrediction[0] | |
} | |
$OpponentsPrediction[0..($OpponentsPrediction.Count-2)] | |
} | |
} | |
} else { | |
$possibleValues = Get-CoinTossPossibleValues | |
0..($SequenceLength-1) | ForEach-Object { | |
#put in logic to mod from the list of possible values here | |
$possibleValues[$_ % $possibleValues.Count] | |
} | |
} | |
} | |
} | |
} | |
function Invoke-CoinTossGame { | |
[CmdletBinding(DefaultParameterSetName = 'PredefinedSequence')] | |
Param ( | |
[Parameter(ParameterSetName = 'PredefinedSequence', Mandatory = $true)] | |
[string[]]$OpponentSequence | |
, | |
[Parameter(ParameterSetName = 'PredefinedSequence', Mandatory = $true)] | |
[string[]]$MySequence | |
#, | |
#at some point I may add other parametersets to allow sequences to be genertated within the game; i.e. to allow more realistic gameplay / taking turns / using the other approaches (e.g. random vs competitive, and user inputs), just so people can play around a bit more | |
, | |
[Parameter()] | |
[int]$BestOf = 1000 | |
) | |
Begin { | |
if ($PSCmdlet.ParameterSetName -eq 'PredefinedSequence') { | |
if (Test-SequenceMatches -SequenceA $OpponentSequence -SequenceB $MySequence) { | |
throw @" | |
You must not guess the same as your opponent; that would result in a draw, which I can''t be bothered to add code for :/ | |
Opponent Sequence $($OpponentSequence -join ', ') | |
My Sequence $($MySequence -join ', ') | |
"@ | |
} | |
} | |
} | |
Process { | |
1..$BestOf | ForEach-Object { | |
[string[]]$Sequence = New-CoinTossSequence -StopWhenSequence @($OpponentSequence, $MySequence) | |
([PSCustomObject]@{ | |
YouPlayed = $OpponentSequence -join ',' | |
IPlayed = $MySequence -join ',' | |
Actual = $Sequence -join ',' | |
IWin = Test-SequenceMatches -SequenceA (Get-Tail -Sequence ([System.Collections.Generic.List[string]]::new($Sequence)) -Length $MySequence.Count) -SequenceB $MySequence | |
}) | |
} | |
} | |
} | |
function Test-SequenceMatches { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory = $true)] | |
[string[]]$SequenceA | |
, | |
[Parameter(Mandatory = $true, ValueFromPipeline = $true)] | |
[string[]]$SequenceB | |
) | |
Process { | |
[boolean]$result = $true | |
if ($SequenceA -eq $null) {throw 'Sequence A is null'} | |
if ($SequenceB -eq $null) {throw 'Sequence B is null'} | |
if ($SequenceA.Length -ne $SequenceB.Length) { | |
$result = $false | |
} else { | |
0..($SequenceA.Length-1) | ForEach-Object { | |
if ($SequenceA[$_] -ne $SequenceB[$_]) {$result = $false} | |
} | |
} | |
$result | |
} | |
} | |
function New-CoinTossSequence { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory = $true)] | |
[string[][]]$StopWhenSequence #for now we'll just assume that all input sequences are the same length to avoid the overhead of checking each time | |
) | |
[System.Collections.Generic.List[string]]$Sequence = [System.Collections.Generic.List[string]]::new() | |
Get-CoinToss -NumberOfTosses $StopWhenSequence[0].Count | ForEach-Object {$Sequence.Add($_)} | |
while (-not ($StopWhenSequence | Test-SequenceMatches -SequenceA (Get-Tail -Sequence $Sequence -Length $StopWhenSequence[0].Count) | Where-Object {$_})) { | |
$newVal = (Get-CoinToss) | |
$Sequence.Add($newVal) | |
} | |
$Sequence.ToArray() | |
} | |
function Get-Tail { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory = $true)] | |
[System.Collections.Generic.List[string]]$Sequence | |
, | |
[Parameter(Mandatory = $true)] | |
[int]$Length | |
) | |
Process { | |
$Sequence.GetRange(($Sequence.Count - $Length), $Length).ToArray() | |
} | |
} | |
function Invoke-CoinTossGameSimulation { | |
[CmdletBinding()] | |
Param() | |
Process { | |
[System.Collections.Generic.List[string[]]]$AllCombos = [System.Collections.Generic.List[string[]]]::new() | |
foreach ($a in (Get-CoinTossPossibleValues)) {foreach ($b in (Get-CoinTossPossibleValues)) {foreach ($c in (Get-CoinTossPossibleValues)) { | |
$AllCombos.Add(@($a,$b,$c)) | |
}}} | |
$results = $AllCombos | ForEach-Object { | |
Write-Verbose "Testing Combo: $_" | |
$mySequence = Get-CoinTossPrediction -OpponentsPrediction $_ | |
Invoke-CoinTossGame -OpponentSequence $_ -MySequence $mySequence | |
} | |
$results | |
$results | Group-Object YouPlayed, IPlayed | ForEach-Object {[PSCustomObject]@{ | |
YouPlayed = $_.Group[0].YouPlayed | |
IPlayed = $_.Group[0].IPlayed | |
IWin = ($_.Group | Measure-Object IWin -Average | Select-Object -ExpandProperty Average).ToString("P") | |
Actual = 'TOTAL' | |
}} | |
[PSCustomObject]@{IWin = ($results | Measure-Object IWin -Average | Select-Object -ExpandProperty Average).ToString("P");YouPlayed='TOTAL';IPlayed='TOTAL';Actual='TOTAL'} | |
} | |
} | |
Clear-Host | |
Invoke-CoinTossGameSimulation -Verbose | Format-Table IWin, YouPlayed, IPlayed, Actual -AutoSize | |
<# Sample Output (truncated) | |
IWin YouPlayed IPlayed Actual | |
---- --------- ------- ------ | |
True H,H,H T,H,H H,T,H,H | |
True H,H,H T,H,H T,T,T,T,T,T,T,T,H,H | |
True H,H,H T,H,H H,T,H,T,H,H | |
True H,H,H T,H,H T,T,H,T,T,T,H,T,T,H,T,H,H | |
False H,H,H T,H,H H,H,H | |
True H,H,H T,H,H T,H,H | |
## ... truncated ... ## | |
True T,T,T H,T,T H,H,H,T,H,H,T,T | |
True T,T,T H,T,T H,T,T | |
True T,T,T H,T,T H,H,T,H,T,T | |
87.60% H,H,H T,H,H TOTAL | |
73.50% H,H,T T,H,H TOTAL | |
66.00% H,T,H H,H,T TOTAL | |
69.70% H,T,T H,H,T TOTAL | |
66.90% T,H,H T,T,H TOTAL | |
65.40% T,H,T T,T,H TOTAL | |
73.00% T,T,H H,T,T TOTAL | |
87.80% T,T,T H,T,T TOTAL | |
73.74% TOTAL TOTAL TOTAL | |
#> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment