PS-Edge(PowerShell+WebView2)でダブルパックマン

右手と左手のどちらが強いのか気になってしまい左右の手でじゃんけんをしていたら日が暮れてしまった。これでは埒が明かない。仕方ないので左右の手にゲームをさせて勝負させることにする。

# ■ PS-Edgeについて
PowerShell + WebView2 という言い回しが長くて使いづらいので `PS-Edge` と言う名称を立ててみる。"PowerShell" で作成したウィンドウに Microsoft "Edge" を埋め込む手法である事から、これらをつなげて略して PS-Edge とする安直な考えである。

# ■ 実装内容
## ◇ 実装方針
PowerShell でウィンドウを作成し、WebView2 の画面を2つ用意しそれぞれパックマンを表示させる。
ウィンドウ自体がキー入力を受け付け、キー入力を上手いこと左右のパックマン画面に送信する。
また、ゲーム終了してしまったらすぐにやり直しを出来るようにリロードボタンも追加する。

なお、パックマンを実装するのは大変なので、Shadertoy に公開されているパックマン的なゲームを読み込んで遊ぶことにする。
>[参考](#参考)#1
> * Pacman Game - Shadertoy BETA
>   https://www.shadertoy.com/view/Ms3XWN

## ◇ 本記事にも関わる以前の記事
PowerShell + XAML + WebView2 で実装する。以前書いた下記記事のコードをベースにする。

1. 次期HTA(HtmlApplication)としてのPowerShell+XAML+WebView2の利用 - Qiita
   https://qiita.com/tkmry/items/c8987fe57a7469d53977

## ◇ 環境
* Windows 10 Pro (21H2)
* PSVersion: 5.1.19041.1645
* WebView2: 1.0.1222-prerelease

## ◇ ファイル構成

```:構成
double_pacman01\
  + script01.ps1    (1)
  + ui01.xaml       (2)
  + lib\
    + Microsoft.Web.WebView2.Core.dll
    + Microsoft.Web.WebView2.Wpf.dll
    + WebView2Loader.dll
  + data\  … スクリプト側で自動生成
```
lib 配下のファイルは Nuget ([参考](#参考)#2) よりパッケージをダウンロードして、実行環境のアーキテクチャのDLLファイルを配置する。
なお、当記事の起稿時点では最新版では WebView2 が適切に起動されないためプレリリース版を利用している。

## ◇ ファイル内容

```powershell:(1) script01.ps1
Add-Type -AssemblyName PresentationFramework  # WPF 用

<# WebView2 用アセンブリロード #>
[void][reflection.assembly]::LoadFile((Join-Path $PSScriptRoot "lib\Microsoft.Web.WebView2.Wpf.dll"))  # 本記事時点 microsoft.web.webview2.1.0.1222-prerelease
[void][reflection.assembly]::LoadFile((Join-Path $PSScriptRoot "lib\Microsoft.Web.WebView2.Core.dll")) # 本記事時点 microsoft.web.webview2.1.0.1222-prerelease

<# キー入力用 #>
$leftKeys = @(
    'A'
    'W'
    'S'
    'D'
    'SPACE'
)

$rightKeys = @(
    'Left'
    'Up'
    'Right'
    'Down'
    'Return'
)

$keyHash = @{
    # for Left
    'A' = 37
    'W' = 38
    'D' = 39
    'S' = 40
    'SPACE' = 13

    # for Right
    'Left' = 37
    'Up' = 38
    'Right' = 39
    'Down' = 40
    'Return' = 13
}

<# XAML にて Window 構築 #>
[xml]$xaml  = (Get-Content (Join-Path $PSScriptRoot .\ui01.xaml))
$nodeReader = (New-Object System.XML.XmlNodeReader $xaml)
$window     = [Windows.Markup.XamlReader]::Load($nodeReader)

<# WebView2 初期設定 #>
$webview2Left = $window.findName("webViewLeft")
$webview2Left.CreationProperties = New-Object 'Microsoft.Web.WebView2.Wpf.CoreWebView2CreationProperties'
$webview2Left.CreationProperties.UserDataFolder = (Join-Path $PSScriptRoot "data")

$webview2Right = $window.findName("webViewRight")
$webview2Right.CreationProperties = New-Object 'Microsoft.Web.WebView2.Wpf.CoreWebView2CreationProperties'
$webview2Right.CreationProperties.UserDataFolder = (Join-Path $PSScriptRoot "data")

<# Contols #>
$resetButton = $window.findName("resetButton")
$playGround  = $window.findName("playGround")

<# for Events ScriptBlock #>
$clickResetButton = {
    $window.findName("webViewLeft").Reload()
    $window.findName("webViewRight").Reload()
}

$keyanyPlayGround = {
    $eventType = ""
    if ($_.isUp) {
        $eventType = "keyup"
    }
    elseif ($_.isDown) {
        $eventType = "keydown"
    }

    $curKey = [string]$_.Key
    $wv = $null

    if ($curKey -in $leftKeys) {
        $wv = $webview2Left
    }
    elseif ($curKey -in $rightKeys) {
        $wv = $webview2Right
    }
    else {
        return
    }
    $wv.ExecuteScriptAsync("demogl.dispatchEvent(new KeyboardEvent('" + $eventType + "', { keyCode: " + ($keyHash[$curKey]) + " })); ")
}

<# add Event Listeners #>
$resetButton.add_Click($clickResetButton)
$window.add_PreviewKeyDown($keyanyPlayGround)
$window.add_PreviewKeyUp($keyanyPlayGround)

<# Window の表示 #>
[void]$window.ShowDialog()
$window.Close()
```

```xml:(2) ui01.xaml
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
        Title="Double Pacman" Height="430" Width="800"
    >
    <DockPanel x:Name="RootDock">
        <DockPanel DockPanel.Dock="Top">
            <Button DockPanel.Dock="Right" x:Name="resetButton" Content="Reset" Padding="3" Width="100" />
            <TextBlock Name="playGround" Padding="0, 0, 30, 0">
                ■遊び方<LineBreak />
                1. ゲーム画面以外の空白部分をクリック<LineBreak />
                2. キー入力をして遊ぶ<LineBreak /><LineBreak />
            </TextBlock>
            <TextBlock Padding="0, 0, 10, 0" TextAlignment="Right">
                操作方法<LineBreak />
                左移動<LineBreak />
                上移動<LineBreak />
                下移動<LineBreak />
                右移動<LineBreak />
                決定
            </TextBlock>
            <TextBlock Padding="5, 0, 5, 0" TextAlignment="Center">
                左側<LineBreak />
                A<LineBreak />
                W<LineBreak />
                S<LineBreak />
                D<LineBreak />
                スペース
            </TextBlock>
            <TextBlock Padding="5, 0, 5, 0" TextAlignment="Center">
                右側<LineBreak />
                ←<LineBreak />
                ↑<LineBreak />
                ↓<LineBreak />
                →<LineBreak />
                Enter
            </TextBlock>
            <TextBlock>
            </TextBlock>
        </DockPanel>
        <DockPanel DockPanel.Dock="Top">
	        <wv2:WebView2 Name="webViewLeft"  Width="400" Height="300" Margin="2" Source="https://www.shadertoy.com/embed/Ms3XWN?gui=true&amp;t=10&amp;paused=false&amp;muted=false" />
	        <wv2:WebView2 Name="webViewRight" Width="400" Height="300" Margin="2" Source="https://www.shadertoy.com/embed/Ms3XWN?gui=true&amp;t=10&amp;paused=false&amp;muted=false" />
        </DockPanel>
    </DockPanel>
</Window>
```

## ◇ 実行画面
できた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124578/f15de573-f8af-d976-2e2a-2b1c3c247607.png)

難しい。左右共にいい感じに進める事ができない。クリアできなくて何だか気持ち悪くなってきた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124578/6bbc4c52-bf4a-7a32-c1a0-e1c123840686.png)

# ■ 後書き
PS-Edge という名称の有る無しの差は大きいと思い、とりあえずこの名前を世に出すべく書き出してみた。
しかし、それだけでは Qiita の記事足り得ないので何かしらコードらしい雰囲気をまとわせようと思いパックマンを2画面で遊べるようにしてみた。

書き始め時点では edge://surf という MS Edge のイースターエッグを2画面に出して遊ぼうとしたが、どうやら WebView2 では edge:// にアクセスできないらしく早くも頓挫した。そして何か良いものはないかと探してパックマンに行き着いたのだった。
この遊び、QWOP のようなゲームも一人で対戦出来るのではないだろうか。もしかすると一人遊びの新しい分野開拓に成功したのではないだろうか。

# 参考
記事内で参照しているもの、参照していないが参考とさせていただいたもの。

1. Pacman Game - Shadertoy BETA
   https://www.shadertoy.com/view/Ms3XWN
1. NuGet Gallery | Microsoft.Web.WebView2 1.0.1222-prerelease
   https://www.nuget.org/packages/Microsoft.Web.WebView2/1.0.1222-prerelease
1. KeyEventArgs クラス (System.Windows.Input) | Microsoft Docs
   https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.input.keyeventargs?view=windowsdesktop-6.0
1. 文字化け - Wikipedia
   https://ja.wikipedia.org/wiki/%E6%96%87%E5%AD%97%E5%8C%96%E3%81%91#Mojibake
1. Microsoft Edge にサーフィン ゲームが新登場 - Windows Blog for Japan
   https://blogs.windows.com/japan/2020/06/25/surf-game-edge-stable/
1. バンダイナムコ，パックマンなど人気ゲームの知的財産権開放｜スター綜合法律事務所
   https://www.star-law.jp/news/post-521.html
1. 次期HTA(HtmlApplication)としてのPowerShell+XAML+WebView2の利用 - Qiita
   https://qiita.com/tkmry/items/c8987fe57a7469d53977
1. 次期HTAとしてのPowerShell+WebView2の利用 - Qiita
   https://qiita.com/tkmry/items/dede9cadc0d2a2df6c1a