初めてのBlazorアプリ

初めてのBlazorアプリとして簡単なアプリを作ってみます。
以前にディスクトップアプリとしてC#でマッチ棒パズルソルバーをつくったので、これをBlazor WebAsemblyで開発し、レンタルサーバーにアップして動かしてみようと思います。

マッチ棒パズルソルバー

マッチ棒パズルとは複数のマッチ棒を組み合わせて作られた間違った数式を、マッチ棒を1本だけ動かして正しい数式を作るパズルです。
例えば下のようなマッチ棒で作られた数式は 1+6=5 という間違った数式になっています。

問題

これをマッチ棒を1本動かして正しい数式にするには、6にあるマッチを5に移動させて、次の様に 1+5=6 にします。

答え

マッチ棒パズルソルバーはこのような問題を解くためのアプリです。

プロジェクトの作成

VisualStudioを開いて「新しいプロジェクトの作成」を行います。
ちなみに私の作成時の環境は VisualStudio 2022 Version 17.14.8 です。
プロジェクト作成の設定は次の通りです。

テンプレート

Blazor WebAssembly アプリが空です

プロジェクト名

MatchPuzzleSolver

フレームワーク

.NET8.0を選びたいのですが゛、何故か選べないのでそのままとします

上記の設定でプロジェクトを作成します。

プロジェクトファイルの変更

私の環境では何故かフレームワークとして「.Net7.0」しか選べないので、プロジェクトファイルを修正します。
ソリューションエクスプローラーでプロジェクトファイルをダブルクリックして開き、次のようにバージョン記述を変更しました。

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.18" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.18" PrivateAssets="all" />
  </ItemGroup>

</Project>

プロジェクトファイルの変更を保存します。このときVisual Studioでプロジェクトの再読み込みを求められる場合は再読み込みを行います。

プロジェクト確認

プロジェクトが正しく作られたか確認するためにデバックビルドして実行します。

プロジェクト確認

プロジェクト作成が確認できました。

ブラウザ画面の作成

ブラウザに表示されるソルバー用の画面を作ります。
ブラウザ画面には問題を入力する問題テキストボックスと、問題を解決する解決ボタン、および答えを表示する答え領域を配置します。
index.razorを開いて次のように修正します。

@page "/"

<h1>マッチ棒クイズ ソルバー</h1>

マッチ棒クイズとはマッチ棒で作った数式をマッチ棒1本を動かして正しい式にするパズルです。<br />
問題はYouTubeにたくさんあります。<br /><br />

<b>問題:</b>
<input @bind="Question" />
<button @onclick="Confirm" class="btn shape">確認</button>
<br />
マッチ棒クイズを数式にしてここに入れて下さい。<br />
数字と + - * X ÷ / = が使用できます。(例)33+46=8x9<br />
確認ボタンを押すとマッチ棒で表示されます。<br />

@((MarkupString)QuestionShape)<br />

<b>答え:</b><button @onclick="Solve" class="btn solve">解答</button><br />
@((MarkupString)AnswerShape)

@code {
    private string Question { get; set; } = "";
    private string QuestionShape { get; set; } = "";
    private string AnswerShape { get; set; } = "解答ボタンを押すとここに答えが出ます。";

    private void Confirm()
    {
        QuestionShape = "";
        foreach (char c in Question)
        {
            QuestionShape += string.Format("<img src='img/{0}.gif' width='40'>", c == '/' ? "d" : c.ToString());
        }
    }

    private void Solve()
    {
        AnswerShape = QuestionShape;
    }
}

問題テキストボックスに問題を入力し、解決ボタンを押すと、答え領域に答えを表示するようにします。

HTML部

<input @bind="Question" />

問題テキストボックスです。
Questionプロパティとバインドされています。

<button @onclick="Confirm" class="btn shape">確認</button>

問題の確認ボタンです。
ボタンを押すとConfirmメソッドが呼び出されます。

@((MarkupString)QuestionShape)

問題をマッチ棒図形で表示する問題確認領域です。
QuestionShapeプロパティの内容が反映されます。
QuestionShapeプロパティにはHTMLタグが含まれますので、タグをHTMLとして機能させるために (MarkupString )を付けています。

<button @onclick="Solve" class="btn solve">解答</button>

解答ボタンです。
ボタンを押すとSolveメソッドが呼び出されます。

@((MarkupString)AnswerShape)

答えを表示する領域です。
AnswerShapeプロパティの内容が反映されます。
AnswerShapeプロパティにはHTMLタグが含まれますので、タグをHTMLとして機能させるために (MarkupString )を付けています。

プロパティ

private string Question { get; set; } = "";

Questionプロパティです。入力された問題を保持する文字列です。

private string QuestionShape { get; set; } = "";

QuestionShapeプロパティです。入力された問題をマッチ棒図形で表現するHTMLを保持する文字列です。

private string AnswerShape { get; set; } = "解答ボタンを押すとここに答えが出ます。";

AnswerShapeプロパティです。答えをマッチ棒図形で表現するHTMLを保持する文字列です。
複数の回答がある場合は HTMLタグ </br> で区切られた文字列になります。

メソッド

private void Confirm()

確認ボタン押下で呼び出される確認メソッドです。
Questionプロパティの問題をマッチ棒図形で表現するHTMLに変換して、QuestionShapeプロパティに格納します。
Questionプロパティの各文字列に対応する図形ファイルをimgタグで連結しています。
QuestionShapeプロパティの内容は問題確認領域に反映されます。

private void Solve()

解答ボタン押下で呼び出される解決メソッドです。
Questionプロパティの問題を解析して、答えをAnswerShapeプロパティに格納します。
AnswerShapeプロパティの内容は答え領域に反映されます。
現段階ではSolveメソッドはAnswerShapeプロパティiにQuestionShapeプロパティをコピーしているだけです。

画像ファイルの配置

数式をマッチ棒を用いた図形で表現するために、画像ファイルを使用します。
画像ファイルは、数式で使用する文字に対応した名前の gifファイルです。
数値0~9に対応したファイルは 0.gig ~ 9.gif です。

0123456789

+ - x / = には各々 +.gif  -.gif  x.gif  d.gif  =.gif  が対応します。

+-x/=

これらの画像ファイルは wwwroot フォルダ直下に img フォルダを作り、その中に格納します。

ブラウザ画面確認

ここで一度、ブラウザ画面を確認するためにデバックビルドして実行します。
ブラウザに次の画面が表示されます。

ブラウザ画面確認1

ブラウザで問題テキストボックスに数式を入れて解答ボタンを押すと答え領域に問題がそのまま表示されます。

ブラウザ画面確認2

ロジックの実装

解答ボタンを押したときに答えを得るためのロジックを実装します。
index.razorを開いて次ように変更します。

@page "/"

<h1>マッチ棒クイズ ソルバー</h1>

マッチ棒クイズとはマッチ棒で作った数式をマッチ棒1本を動かして正しい式にするパズルです。<br />
問題はYouTubeにたくさんあります。<br /><br />

<b>問題:</b>
<input @bind="Question" />
<button @onclick="Confirm" class="btn shape">確認</button>
<br />
マッチ棒クイズを数式にしてここに入れて下さい。<br />
数字と + - * X ÷ / = が使用できます。(例)33+46=8x9<br />
確認ボタンを押すとマッチ棒で表示されます。<br />

@((MarkupString)QuestionShape)<br />

<b>答え:</b><button @onclick="Solve" class="btn solve">解答</button><br />
@((MarkupString)AnswerShape)

@code {
    private string Question { get; set; } = "";
    private string QuestionShape { get; set; } = "";
    private string AnswerShape { get; set; } = "解答ボタンを押すとここに答えが出ます。";

    private void Confirm()
    {
        // 問題文の空白の除去と文字の標準化
        string question = Solver.TrimQuestion(Question);

        QuestionShape = "";
        foreach (char c in question)
        {
            QuestionShape += string.Format("<img src='img/{0}.gif' width='40'>", c == '/' ? "d" : c.ToString());
        }
    }

    private void Solve()
    {
        List<string> answerList = Solver.Solve(Question);
        if (answerList.Count == 0)
        {
            AnswerShape = "この問題は解けません";
            return;
        }
        AnswerShape = "";
        foreach (string answer in answerList)
        {
            foreach (char c in answer)
            {
                AnswerShape += string.Format("<img src='img/{0}.gif' width='40'>", c == '/' ? "d" : c.ToString());
            }
            AnswerShape += "<br />";
        }
    }
}

変更点は次の2点です。

1.Confirmメソッドの変更

Questionプロパティに空白や全角等の標準以外の文字が使用されている可能性があるので、文字列を標準の形に整形します。

string question = Solver.TrimQuestion(Question);

問題文の空白の除去と文字の標準化を行い整形します。

整形された文字列questionを用いてマッチ棒図形で表現するHTMLに変換し、QuestionShapeに格納します。

2.Solveメソッドの変更

Solverクラスを用いて答え取得し、それを表示する様にします。

List<string> answerList = Solver.Solve(Question);

Solverクラスは静的なクラスで、Solver.Solveメソッドは問題の式を文字列として与え、答えの式を文字列配列として返す仕様です。
戻り値が配列となっているのは答えが複数ある場合を考慮しているからです。

answerListに答えの文字列配列が返ってくるので、それにより答えを表示しています。
答えの配列が空の場合は「この問題は解けません」と表示します。
答えがある場合は、配列の各値をマッチ棒図形で表現するHTMLに変換し<br />を付加します。複数の答えがある場合はこれらを連結して表示します。

Solverクラス

Solverクラスはマッチ棒パズルを解く静的なクラスです

クラス

public static class Solver

メソッド

public static string TrimQuestion(string question)

問題文の空白の除去と文字の標準化をします。
問題文に全角及び半角の文字が含まれている場合はこれを除去します。
問題文に数字や加減乗除文字が全角や異形文字になっていた場合に標準の文字に変換します。

パラメータ: question 問題の数式

戻り値: 問題文より空白の除去と文字の標準化を行った文字列

public static List Solve(string question)

問題を与えて答えの配列を取得します。

パラメータ: question 問題の数式

戻り値: 答えの配列 (問題に対して複数の答えがある場合があります。)

Solverクラスは以前にディスクトップアプリとして作ったものがあるので、ソリューションエクスプローラより「既存の項目」としてSolver.csファイルを追加します。
C#の資産がそのまま使えるのがとても便利ですね。
ソースはSolver.csよりダウンロードできます。

動作確認

デバックビルドして実行し動作確認をします。
問題に 12-8-3=9 と入れて解答ボタンを押すと 答えに 12+0-3=9 と 12-6+3=9 の2つが表示されればOKです。

動作確認

期待通りの動きになりました。

サーバーへのアップロード

完成したアプリをWebサーバーに配置して、どの端末からも利用できるようにします。

1. 発行:

プロジェクトをディスク内のターゲットフォルダに発行します。
ソリューションエクスプローラでをプロジェクトファイルを右クリックして「発行」を選択し、任意のターゲットフォルダを選択し発行します。
ターゲットフォルダに必要なプロジェクトファイル一式がが発行されます。

2. 配置:

発行されたファイルを、Webサーバーに配置します。
配置先のサーバーは、私の借りている「スターレンタルサーバー」という無料のレンタルサーバーです。
私のドメインのルートにmatchフォルダを作成して、その中に配置しようと思います。
FTPソフトを使用してターゲットフォルダ内にある wwwroot フォルダ内の全てのファイルとフォルダを Webサーバーの /machフォルダ内に転送します。

3. index.html の設定:

Webサーバーの転送先フォルダ直下にあるindex.htmlファイル内の  タグを、配置先のサブパスに合わせて調整します。
今回は、/match に配置したので  に設定します。

4. 起動

任意の端末のブラウザでURLhttps://eternalboy.stars.ne.jp/matchを指定すればマッチ棒クイズソルバーが起動します。