∷ Skripte ∷ PowerShell
Die nachfolgenden Skripte sind für Anschauungs- und Unterrichtszwecke gedacht.
Sie können kopiert und in der Microsoft PowerShell ISE oder sogar online ausprobiert/
① Wochentag der Geburt und Anzahl der seitdem vergangenen Tage
Die Funktion BirthCalc
im nachfolgenden Skript nimmt ein Geburtsdatum im Format JJJJ-MM-TT
entgegen und ermittelt sowohl den Wochentag der Geburt als auch die seitdem vergangenen Tage:
function BirthCalc([String]$s) { $f = "Du bist vor {0} Tagen an einem {1} geboren worden!" $w = "Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag" if ($s -match '^\d{4}(-\d\d){2}$' -and ($d = $s -as [DateTime])) { $f -f ((Get-Date).Date - $d).Days, (-split $w)[$d.DayOfWeek] } } BirthCalc 2002-02-20 # Du bist vor 6666 Tagen an einem Mittwoch geboren worden!
Um die Berechnungen für ein anderes Datum vorzunehmen, muss nur der obige Funktionsaufruf vor seiner Ausführung entsprechend angepasst werden. Eine Schnaps- oder runde Zahl hinsichtlich der Anzahl der Lebenstage kann einen guten Vorwand liefern, um einfach mal jemanden überraschend zu kontaktieren und mit Verweis auf das Ereignis zu gratulieren.
② GPX-Track verkleinern/anonymisieren
Manchmal möchte man einen Track im GPX-Format weitergeben/track.gpx
nach track-min.gpx
und entfernt dabei die Attribute ele
, time
, src
, sat
, course
, speed
, hdop
, vdop
, pdop
sowie geoidheight
.
Hinweis: Sollte die Zieldatei bereits existieren, wird sie überschrieben!
(Get-Content -Raw -Path .\track.gpx) -replace '(?s)[\t ]*<(ele|time|geoidheight|src|sat|[hvp]dop|course|speed).+?\1>\r?\n?' | Set-Content -NoNewline -Path .\track-min.gpx
Der Parameter -Raw
ist erst seit PowerShell 3 verfügbar, -NoNewline
setzt sogar mindestens Version 5 voraus; mit Einschränkungen (bzgl. der nachfolgend genannten Aspekte) ist jedoch das Skript auch ohne diese beiden Argumente lauffähig.
Die Bereinigung der GPX-Datei wird durch einen einzigen regulären Ausdruck erledigt, welcher an individuelle Bedürfnisse angepasst werden kann und auch für den Fall funktioniert, dass öffnendes und schließendes Schlüsselwort nicht in derselben Zeile stehen.
Die Art des Zeilenumbruchs (Windows, UNIX) oder der Zeichenkodierung (ASCII, UTF-8, UTF-8-BOM) bleibt unangetastet.
③ Konvertierung römischer Zahlen
Das nachfolgende Skript deklariert 3 Prozeduren für den Umgang mit römischen Zahlen:
- Die Funktion
RomanToInt
nimmt eine Zeichenkette mit denrömischen Ziffern
I
,V
,X
,L
,C
,D
,M
entgegen und konvertiert sie in eine natürliche Zahl. - Im Gegenzug wandelt die Funktion
IntToRoman
eine natürliche Zahl in eine römische um. - Das Cmdlet
ConvertTo-Roman
nimmt eine Menge von natürlichen und/oder römischen Zahlen entgegen und stellt sie tabellarisch in beiden Formaten dar, wobei implizit auch eine Normalisierung der Schreibweise vorgenommen wird (siehe nachfolgende Erläuterungen zur Variablen$r
).
Der Eingabebereich liegt dabei jeweils zwischen 1 und 3999 (bzw. I
und MMMCMXCIX
).
Für notwendige Umrechnungen greift ConvertTo-Roman
auf die Funktionen RomanToInt
und IntToRoman
zurück.
function RomanToInt([String]$s) { $n = 0 $r = '(M{1,3})?' + # Or M{1,9} for numbers up to 9999 '(?:((?:CC?|XX?|II?)[MD])|((?:DC?|C)C{0,3}))?' + '(?:((?:XX?|II?)[CL])|((?:LX?|X)X{0,3}))?' + '(?:((?:II?)[XV])|((?:VI?|I)I{0,3}))?' if ($s -and $s -cmatch "^$r`$") { $h = @{ I=1; V=5; X=10; L=50; C=100; D=500; M=1000 } foreach ($k in $Matches.Keys -gt 0) { $n += $h[[String]$Matches[$k][$k % 2 - 1]] + $h[[String]$Matches[$k][-$k % 2]] * ($Matches[$k].Length - 1) * (-1, 1)[$k % 2] } } return $n } function IntToRoman([ValidateRange(0,3999)][Int]$n) { $s, $a = "", "I", "V", "X", "L", "C", "D", "M" for ($i = 0; $n; ($n = [Math]::Floor($n / 10)), ($i += 2)) { # The following line is only required for numbers > 3999 # if ($a.Count - $i -lt 3) { return $a[$i] * $n + $s } switch ($n % 10) { { 1..3 -eq $_ % 5 } { $s = $a[$i] * ($_ % 5) + $s } { 4 -le $_ } { $s = $a[$i + 1 + ($_ -eq 9)] + $s } { 4, 9 -eq $_ } { $s = $a[$i] + $s } } } return $s } function ConvertTo-Roman { param([Parameter(ValueFromPipeline=$true)]$InputObject) Begin { $o = 1 | Select-Object -Property Arabic, Roman $a = { $e -as [Int] }, { RomanToInt $e } } Process { foreach ($e in $InputObject) { $o.Arabic = &$a[$e -is [String]] $o.Roman = IntToRoman $o.Arabic Write-Output -InputObject $o } } } RomanToInt MXXM # 1980 IntToRoman 1980 # MCMLXXX ConvertTo-Roman MDCCCCLXXXIIII, MCMLXXXIV, $true, 1666 # Arabic Roman # ------ ----- # 1984 MCMLXXXIV # 1984 MCMLXXXIV # 1 I # 1666 MDCLXVI 1..3999 | ConvertTo-Roman | Format-Table -Autosize
In der Funktion RomanToInt
wird der Variablen $r
ein regulärer Ausdruck zugewiesen, durch den festgelegt wird, wie eine römische Zahl aussehen muss.
Dieser ist im obigen Skript derart gestaltet, dass auch einige alternative Schreibweisen wie MXXM
für 1980 (also Verdoppelung des Zeichens in subtraktiver Stellung) oder VIIII
für 9 (d. h. keine Anwendung einer Subtraktionsregel) akzeptiert werden.
Soll dies nicht erlaubt sein, so muss das Muster restriktiver formuliert werden, beispielsweise wie folgt:
$r = '(M{1,3})?' +'(?:((?:CC?|XX?|II?)[MD])|((?:DC?|C)C{0,3}))?' +'(?:((?:XX?|II?)[CL])|((?:LX?|X)X{0,3}))?' +'(?:((?:II?)[XV])|((?:VI?|I)I{0,3}))?'$r = '(M{1,3})?' + '(?:(CM|CD)|(DC{0,3}|C{1,3}))?' + '(?:(XC|XL)|(LX{0,3}|X{1,3}))?' + '(?:(IX|IV)|(VI{0,3}|I{1,3}))?'
Mittels des regulären Ausdrucks wird nicht nur eine Zeichenkette aus römischen Zahlzeichen auf Gültigkeit überprüft, sondern auch in Einer-, Zehner, Hunderter- und Tausenderstellen zerlegt.
Dabei gibt es für jede Wertigkeit (bis auf die höchste) zwei Gruppierungen, wobei die jeweils erste subtraktive Schreibungen erkennt (z. B. IV
oder IIX
) und die andere alle additiven/übrigen (z. B. VI
oder IIII
, aber auch V
).
Der Schleifenkörper in der Funktion RomanToInt
lässt sich übrigens auch anders formulieren, so dass deutlicher wird, dass es sich um dieselben 3 Werte handelt, die jedoch auf unterschiedliche Art in Berechnungen einfließen – je nachdem, ob eine römische Zifferngruppe in subtraktiver oder additiver Schreibweise vorliegt:
$n += $h[[String]$Matches[$k][$k % 2 - 1]] +$h[[String]$Matches[$k][-$k % 2]] *($Matches[$k].Length - 1) * (-1, 1)[$k % 2]$a = $h[[String]$Matches[$k][-1]] $b = $Matches[$k].Length - 1 $c = $h[[String]$Matches[$k][0]] $n += &({ $a - $b * $c }, { $a * $b + $c })[$k % 2]
④ Türme von Hanoi
Die nachfolgende rekursive Funktion schlägt eine Zugfolge zur Lösung der Türme von Hanoi vor, ein Knobel- und Geduldsspiel, bei dem ein geordneter (z. B. nach oben hin immer schmaler werdender) Stapel scheibchenweise versetzt werden muss, wobei nur eine einzige weitere Hilfsposition genutzt und zu keinem Zeitpunkt an irgendeiner der 3 Stellen (Start-/Hilfs-/Zielposition) die Ordnung verletzt werden darf.
Als Parameter müssen die Anzahl der Scheiben sowie die Namen der 3 Ablegepositionen (z. B. L für Links, M für Mitte und R für Rechts) angegeben werden. Auf der ersten Ablegeposition (hier: L) ruht der Turm zu Beginn; Ziel ist es, ihn unter Einhaltung der Regeln auf die letzte Ablegeposition (hier: R) zu versetzen. Die Einrückungen in den ausgegebenen Zeilen entsprechen jeweils der Breite der gerade bewegten Scheibe.
function Tower-of-Hanoi { param([Int]$num, [String]$src, [String]$tmp, [String]$dst) if ($num-- -gt 0) { Tower-of-Hanoi $num $src $dst $tmp Write-Host -Object (" " * $num + $src + "->" + $dst) Tower-of-Hanoi $num $tmp $src $dst } } Tower-of-Hanoi 3 L M R # L->R # L->M # R->M # L->R # M->L # M->R # L->R
Der obige Algorithmus löst die Aufgabe für n
Scheiben, indem er
n-1
Scheiben von der linken zur mittleren Ablegeposition versetzt und dabei die rechte Position temporär benutzt,- dann die breiteste Scheibe von der linken zur rechten Ablegeposition zieht und
- abschließend die
n-1
Scheiben von der mittleren zur rechten Ablegeposition versetzt, wobei die linke Position temporär benutzt wird.
Durch dieses rekursive Vorgehen ist die Aufgabe für n
Scheiben lösbar, wenn sie es zweimal für n-1
Scheiben ist.
Hieraus folgt, dass jede Scheibe doppelt so oft bewegt wird wie die nächstbreitere.
Die Rekursion baut einen vollständigen Binärbaum der Tiefe n
an Funktionsaufrufen auf, wobei in jedem Knoten 1 Zug stattfindet, so dass sich insgesamt 2n-1
Züge und somit ein exponentieller Aufwand ergeben.
Sind die Scheiben von oben nach unten mit Farbe1
, …, Farben
eingefärbt, dann enthält in jedem Funktionsaufruf der Parameter $num
den Farbindex der zu bewegenden Scheibe.
Dies lässt sich auf andere Eigenschaften übertragen.
Um z. B. die Ausgabe an einen Stapel von (bis zu) 8 Euro-Münzen anzupassen, genügt es, im obigen Quellcode einfach nur das Cmdlet Write-Host
etwas anders zu parametrisieren:
Write-Host -Object (" " * $num + $src + "->" + $dst)Write-Host (" 1 Cent", " 2 Cent", "10 Cent", " 5 Cent", "20 Cent", " 1 Euro", "50 Cent", " 2 Euro")[$num] "von $src nach $dst"
⑤ Tic-Tac-Toe / Drei gewinnt
Bei diesem klassischen, einfachen Strategiespiel für 2 Personen geht es darum, als Erster eine Horizontale, Vertikale oder Diagonale auf einem Gitter von 3×3 Feldern vollständig in den Besitz zu nehmen. Dafür besetzen zwei Spieler abwechselnd ein noch freies Feld auf dem Spielbrett. Wer anfängt, hat einen kleinen Vorteil und sollte deshalb per Los bestimmt werden.
Das nachfolgende Skript ermöglicht es, dieses Spiel gegen den Computer anzutreten.
Wegen der Interaktion mit dem Menschen kann der Quellcode leider nicht online ausgeführt werden.
Stattdessen sollte er kopiert und in der PowerShell ISE eingefügt werden, um ihn dort zu starten.
Auf dem Spielbrett werden durch den Computer besetzte Felder durch das Zeichen #
markiert, und die vom Menschen belegten durch X
.
Felder, die keinem gehören und insofern noch frei sind, werden mit einer Ziffer versehen.
Wenn der Mensch am Zug ist, gibt dieser einfach eine der angezeigten Nummern zwischen 1 und 9 ein, um das entsprechende Feld zu besetzen.
$h = @{ TITLE = "TIC-TAC-TOE" # for PowerShell >=2.0, written by mawe-web.de
HOWTO = @("My symbol is '#', yours 'X';",
"enter a field number between",
"1 and 9 when it is your turn")
MYTURN = "My move".PadRight(9)
YOURTURN = "Your move"
MYWIN = "Sorry, I have won."
YOURWIN = "Congratulations, you won!"
TIE = "The game ended in a tie..."
PAD = @{ CALCULATOR = 7..9 + 4..6 + 1..3; PHONE = 1..9 }.CALCULATOR
}
# Decision making function
function f { param($p # Patterns to examine
, $m = 0 # Operating mode / expectancy value (0, 1, 2 or 3)
<# , $n = $n, $a = $a #>) # Read access variables defined outside: $n, $a
$s = "" # Initialize the list of suitable moves
foreach ($e in $p) { # Loop through the patterns
$d = 0 # Initialize the delta/score
$t = "" # Clear temporary string variable used for various purposes
foreach ($c in [String[]][Char[]]"$e") { # Loop through the positions...
if ($a[$c]) { $d += $a[$c] } # ...and calculate the pattern score...
else { $t += $c } # ...and determine blank fields
}
if ($m -eq 3 -and [Math]::Abs($d) -eq 3)
{ return -"$d".Length } # Game over! The winner is: -2=human, -1=machine
if (!$m -and $t -eq $e -or # If mode=0 and each pattern field is blank...
($m -eq 3 -and $d -eq 2) -or # ...or mode=3 and the delta is +2...
($m -and [Math]::Abs($d) -ge $m)) # ...or delta >= expectancy value...
{ $s += $t } # ...then add the blank fields to the list of useable moves
}
if ($n -lt 6 -and ($t = $s -replace "[2468]"))
{ $s = $t } # Prefer odd fields during the first moves
if ($s) { [Int][String]$s[(Get-Random -Maximum $s.Length)] } # Select a move
else { 0 } # Otherwise return 0 since there is no move to recommend
}
# Main procedure
Write-Host -Object ((,$h.TITLE + $h.HOWTO) -join "`n".PadRight(3)) # Say hello
$a = ,0 * 10 # Game board with fields numbered from 1 to 9
$p = 123, 147, 159, 258, 357, 369, 456, 789 # List of all winning patterns
$i = 10 # Index (must be 10 to skip Read-Host cmdlet during first loop)
$n = -(Get-Random -Maximum 2) # Roll the dice who starts: -1=human, 0=machine
while (($f = f $p 3) -ge 0 -and $n -le 9) { # While exit condition is not met:
while (!(1..10 -eq $i) -or $a[($i %= 10)] -or $a[$i]--)
{ $i = Read-Host -Prompt $h.YOURTURN } # (1) Get and set the human's move
if (2..10 -eq ($n += 2) -and # (2) Calculate/do the machine's move...
(($i = f $p 3) -or # ...using OR-linked ruleset
($i = f $p 2) -or
($i = f (,5)) -or
($a[5] -ge 0 -and (f (,1379) 2) -and ($i = f (,2468))) -or
($i = f (12369, 12347, 14789, 36789) 2) -or
($i = f (1..9))
) -and $i -gt 0 -and ++$a[$i]) { Write-Host -Object "$($h.MYTURN): $i" }
Write-Host -NoNewline -Object "", # (3) Display the game board
($h.PAD | ForEach-Object -Process { "${_}#X"[$a[$_]] + " `n"[!($_ % 3)] })
}
Write-Host -Object $h[("TIE", "YOURWIN", "MYWIN")[$f]] # Announce the result
Da sämtliche Texte gemeinsam in der Variablen $h
tabellarisch hinterlegt sind, ist eine sprachliche Anpassung mit keinem großen Aufwand verbunden:
$h = @{ TITLE = "TIC-TAC-TOE"HOWTO = @("My symbol is '#', yours 'X';","enter a field number between","1 and 9 when it is your turn")MYTURN = "My move".PadRight(9)YOURTURN = "Your move"MYWIN = "Sorry, I have won."YOURWIN = "Congratulations, you won!"TIE = "The game ended in a tie..."HOWTO = @() MYTURN = "Mein Zug" YOURTURN = "Dein Zug" MYWIN = "Du wurdest besiegt." YOURWIN = "Du hast gewonnen!" TIE = "Unentschieden." PAD = 7..9 + 4..6 + 1..3 }
Insofern könnte ein möglicher Spielverlauf wie folgt aussehen, so dass sich an der Ausgabe im Nachhinein recht gut nachverfolgen lässt, wie sich eine Partie entwickelt hat: (sowohl im Original als auch in Deutsch)
TIC-TAC-TOE My symbol is '#', yours 'X'; enter a field number between 1 and 9 when it is your turn My move : 5 7 8 9 4 # 6 1 2 3 Your move: 6 My move : 3 7 8 9 4 # X 1 2 # Your move: 7 My move : 2 X 8 9 4 # X 1 # # Your move: 8 My move : 1 X X 9 4 # X # # # Sorry, I have won.
TIC-TAC-TOE Mein Zug: 5 7 8 9 4 # 6 1 2 3 Dein Zug: 6 Mein Zug: 3 7 8 9 4 # X 1 2 # Dein Zug: 7 Mein Zug: 2 X 8 9 4 # X 1 # # Dein Zug: 8 Mein Zug: 1 X X 9 4 # X # # # Du wurdest besiegt.
Der Funktion f()
im obigen Quellcode werden Muster von Feldern in Form von Ziffernfolgen sowie ein Erwartungswert als Parameter übergeben, ferner greift sie lesend auf die extern deklarierten Variablen $n
und $a
zu [Zeilen 13-15].
Sie berechnet für jedes Muster eine Punktzahl, die der Differenz zwischen den eigenen und generischen Steinen auf seinen Feldern entspricht, und notiert sich außerdem die Nummern der noch unbesetzten Felder [Zeilen 20-23], z. B.:
Gehören im Muster 123 die Felder 2 sowie 3 ein und derselben Partei und ist das Feld 1 leer, dann führt dies zur Punktzahl 2 und Notierung der Ziffer 1.
Wenn die Differenz/Punktzahl mindestens dem Erwartungswert entspricht, werden die Nummern der unbesetzten Felder der Ergebnismenge hinzugefügt; für den Erwartungswert 0 wird dies jedoch nur dann gemacht, wenn sämtliche Felder des Musters unbesetzt sind [Zeilen 26-29].
Aus der Ergebnismenge wird eine Nummer zufällig ausgewählt und als Zugvorschlag von der Funktion zurückgegeben, oder 0, wenn die Ergebnismenge leer ist [Zeilen 33-34].
Für den Erwartungswert 3 findet die Funktion Muster, die vollständig von einer der beiden Spielparteien besetzt sind, und gibt in diesem Fall -1 oder -2 zurück, je nachdem, ob das gefundene Muster im Besitz des Computers oder des menschlichen Gegners ist [Zeilen 24-25].
Ansonsten findet die Funktion für den Erwartungswert 3 zudem alle Muster mit der Punktzahl 2, die der Computer mit nur einem Zug auf das noch freie Feld vervollständigen kann; ggf. gibt sie die Nummer eines der Freifelder als Zugempfehlung zurück [Zeile 27].
Solange die Partie noch jung ist und bislang weniger als 5 Züge erfolgt sind, wird der Zugvorschlag – wenn möglich – nur aus den ungeraden Nummern der Ergebnismenge ausgewählt [Zeilen 31-32].
In der Hauptprozedur des Skripts wird die Funktion f()
wiederholt mit verschiedenen Mustern und Erwartungswerten aufgerufen, und zwar solange, bis ein Zugvorschlag zurückgegeben wird und getätigt werden kann [Zeilen 45-52]; dabei wird von der Booleschen Kurzschlussauswertung umfangreich Gebrauch gemacht.
Ferner werden in der Hauptschleife der Zug des menschlichen Gegners (als Ziffer zwischen 1 und 9) erfragt, geprüft und gesetzt [Zeilen 43-44] sowie die aktuelle Spielsituation visualisiert, wobei vom Computer besetzte Felder mit einer Raute (#
) markiert werden und alle gegnerischen mit einem Kreuz (X
) [Zeilen 53-54].
Die Partie wird beendet, wenn eine Horizontale, Diagonale oder Vertikale durch eine der Parteien alleinig und vollständig besetzt ist oder es kein unbesetztes Feld mehr gibt; wer anfängt, bestimmt der Zufall [Zeilen 41-42].
Der im Skript implementierte Algorithmus kämpft nicht ganz so verbissen um einen Sieg wie die von Newell und Simon verwendete Strategie, sollte aber zumindest ein Unentschieden erzielen.
Und er kommt mit äußerst wenigen Code-Zeilen aus: Gerade mal 2 ineinander verschachtelte Schleifen [Zeilen 16-30], 4 If
-Bedingungen [Zeilen 21-22, 26-29, 31-32 und 33-34] und 6 Zugregeln [Zeilen 46-51] bilden den Kern der Entscheidungsfindung; das meiste andere ist eher der Ablaufsteuerung oder Benutzerschnittstelle zuzurechnen.
Durch Auskommentierung der Anweisungen in den Zeilen 47, 48, 49, 50 und/oder 31-32 kann der Computer in seiner Spielweise geschwächt und – evtl. mit ein bisschen Glück – geschlagen werden.
Siehe auch:
darebalogun/tic-tac-toe (in C++)
WesleyyC/Tic-Tac-Toe (in Python)
Creating Tic-tac-toe in Swift: Artificial intelligence
⑥ Und zum Schluss ist alles im Fluss …
… aber in welchem? – Obwohl der nachfolgende Zeichensalat
so aussieht, als sei das Haustier kurz mal eben über die Tastatur geflitzt, schafft es PowerShell, ihn zu einem durchaus lesbaren Ergebnis auszuwerten.
Wer erfahren im Umgang mit der Skriptsprache ist und Spaß am Rätseln hat, kommt vielleicht auch ohne eine explizite Ausführung darauf, welche Ausgabe dieser Einzeiler bewirkt:
,[Char](6%-4*''''[-1]-1)+("$("p","s")"[@(1/.5)+-!!8..!0]|%{"$_".Trim()*2})-Join[Char](1*3*5*7)#~9)
Tipp: Forme den obigen Ausdruck um und vereinfache/
Alle Angaben sind ohne jegliche Gewähr. Der Autor übernimmt keinerlei Verantwortung oder Haftung für Fehler oder Ungenauigkeiten, die in diesem Dokument auftreten können. Anmerkungen, Ergänzungen und Verbesserungsvorschläge zu diesem Artikel werden gerne entgegengenommen.