【ASP.NET Core MVC】ASP.NET Core MVCであみだくじを作ってみる 6 (Razorを使う あみだを辿る編)

# 概要
前回：[ASP.NET Core MVCであみだくじを作ってみる 5 (Razorを使う)](https://qiita.com/plusM/items/add1ab06bfb5f9677fea)
今回は、Razorを使って選択した番号のあみだを辿る処理を作ります。単なるおまけです。

前回同様、[ASP.NET Core の Razor 構文リファレンス](https://docs.microsoft.com/ja-jp/aspnet/core/mvc/views/razor?view=aspnetcore-3.0)を参照しながら作っていきます。

# 描画したあみだを保持する
あみだを辿るには、ランダムに描画したあみだを保持しておく必要があります。
Kuji\Index.cshtmlの\<div class="kuji-line">を以下のように変更します。
追加した行に+の印を付けました。

```html:Kuji\Index.cshtml
        <div class="kuji-line">
            @{
                var rd = new Random();
                var rcount = Math.Max(20, Model.Result.Count * 4);   // グリッドレイアウトの行数

                // 行番号リスト(4行目以降、全行数 - 上下のボタンや結果表示域で使用している行を除く行数)
                var rows = Enumerable.Range(4, rcount - 6).ToList();

+               // 列ごとに横線の行番号を保持する用のリスト
+               var lines = new List<List<int>>();

                <!--人数の分だけループ-->
                for (var i = 1; i < Model.Result.Count + 1; i++)
                {
                    <!--選択肢ボタン-->
                    <button class="select-btn" style="grid-row:2;grid-column:@i" asp-route-id="@i">@i</button>

                    <!--縦線-->
                    <div class="vertical-line" style="grid-row:3 / span @(rcount - 4);grid-column:@i"></div>

+                   var hlines = new List<int>();   // 横線を行番号を保持する用

                    <!--横線-->
                    if (i < Model.NumberOfKuji)
                    {
                        var hcount = rd.Next(1, rcount / 4);   // 横線の数

                        for (var j = 0; j < hcount; j++)
                        {
                            if (rows.Count <= 0) break; // 行が足りなくなったら終了

                            int rno = rows[rd.Next(0, rows.Count - 1)]; // 横線を引く行番号
                            rows.Remove(rno);   // 重複する行番号を使わないようにするため

                            <p class="horizontal-line" style="grid-row:@rno;grid-column:@i"></p>

+                           hlines.Add(rno);
                        }
                    }
+                   lines.Add(hlines);  // 列ごとに横線の行番号を保持する
+                   
                    <!--結果-->
                    <!--横幅を揃えるため、PadRightで5文字分に揃える-->
                    <div style="grid-row:@(rcount - 1);grid-column:@i">@Model.Result[i - 1].Item.PadRight(5, '　')</div>
                }

                <!--1行分を余白とする-->
                <div style="grid-row:@rcount;grid-column:1 / span @Model.NumberOfKuji"></div>
            }

            <!--あみだを隠すためのマスク領域-->
            @if (Model.SelectId == 0)
            {
                <div class="mask"></div>
            }
        </div>
```
# 番号を選択した後の処理を追加する
Kuji\Index.cshtmlの\<div class="kuji-line">の\<!--1行分を余白とする-->の前に、以下の処理を追加します。
選択した番号の縦線を辿り、右への横線と左への横線、どちらが上かを判定して上にあるほうの横線を辿ります。
横線を辿って縦線を移動したら、その縦線を辿り、同じように上にあるほうの横線を辿ります。

```html:Kuji\Index.cshtml
                if (Model.SelectId > 0) // 番号を選択した場合
                {
                    // 選択した番号の列から始める
                    var currentCol = Model.SelectId - 1;
                    // 縦線が始まる行から始める
                    var currentRow = 3;

                    while (currentRow < rcount - 1)
                    {
                        // 現在の列の右側に延びる横線のうち、現在の行より下にある横線を取得する
                        var rightRow = (currentCol == Model.NumberOfKuji) ? null : lines[currentCol].Where(c => currentRow < c).ToList();
                        // 現在の列の左側に延びる横線のうち、現在の行より下にある横線を取得する
                        var leftRow = (0 == currentCol) ? null : lines[currentCol - 1].Where(c => currentRow < c).ToList();

                        // 右への線のうち、一番上にある横線を取得する
                        var rightMin = (rightRow != null && rightRow.Count > 0) ? rightRow.Min() : 999;
                        // 左への線のうち、一番上にある横線を取得する
                        var leftMin = (leftRow != null && leftRow.Count > 0) ? leftRow.Min() : 999;

                        if (rightMin < leftMin)
                        {
                            // 右へ進む

                            // 現在の行から右へ進む横線までの縦線
                            <div class="vertical-line follow" style="grid-row:@currentRow / span @Math.Max(1, (rightMin - currentRow));grid-column:@(currentCol + 1)"></div>
                            // 右への横線
                            <p class="horizontal-line follow" style="grid-row:@rightMin;grid-column:@(currentCol + 1)"></p>

                            currentCol++;   // 右へ進んだ
                            currentRow = (currentRow == rightMin) ? currentRow + 1 : rightMin;
                        }
                        else if (rightMin > leftMin)
                        {
                            // 左へ進む

                            // 現在の行から左へ進む横線までの縦線
                            <div class="vertical-line follow" style="grid-row:@currentRow / span @Math.Max(1, (leftMin - currentRow));grid-column:@(currentCol + 1)"></div>

                            currentCol--;   // 左へ進んだ

                            // 左への横線
                            <p class="horizontal-line follow" style="grid-row:@leftMin;grid-column:@(currentCol + 1)"></p>
                            currentRow = (currentRow == leftMin) ? currentRow + 1 : leftMin;
                        }
                        else
                        {
                            <div class="vertical-line follow" style="grid-row:@currentRow;grid-column:@(currentCol + 1)"></div>
                            currentRow++;
                        }
                    }
                }
```

いろいろ触ってきたので、Kuji\Index.cshtmlの全体を載せておきます。

```html:Kuji\Index.cshtml
@model Amidakuji.Models.KujiModel

@{
    ViewData["Title"] = "あみだくじ";
}

<form method="post" asp-controller="Kuji" asp-action="Index">
    <div class="kuji-container">
        <h1>@Model.Title</h1>
        <p>くじの数は、@Model.NumberOfKuji 本です。</p>

        <!--あみだくじの領域(グリッドレイアウト)-->
        <div class="kuji-line">
            @{
                var rd = new Random();
                var rcount = Math.Max(20, Model.Result.Count * 4);   // グリッドレイアウトの行数

                // 行番号リスト(4行目以降、全行数 - 上下のボタンや結果表示域で使用している行を除く行数)
                var rows = Enumerable.Range(4, rcount - 6).ToList();

                // 列ごとに横線の行番号を保持する用のリスト
                var lines = new List<List<int>>();

                <!--人数の分だけループ-->
                for (var i = 1; i < Model.Result.Count + 1; i++)
                {
                    <!--選択肢ボタン-->
                    <button class="select-btn" style="grid-row:2;grid-column:@i" asp-route-id="@i">@i</button>

                    <!--縦線-->
                    <div class="vertical-line" style="grid-row:3 / span @(rcount - 4);grid-column:@i"></div>

                    var hlines = new List<int>();   // 横線を行番号を保持する用

                    <!--横線-->
                    if (i < Model.NumberOfKuji)
                    {
                        var hcount = rd.Next(1, rcount / 4);   // 横線の数

                        for (var j = 0; j < hcount; j++)
                        {
                            if (rows.Count <= 0) break; // 行が足りなくなったら終了

                            int rno = rows[rd.Next(0, rows.Count - 1)]; // 横線を引く行番号
                            rows.Remove(rno);   // 重複する行番号を使わないようにするため

                            <p class="horizontal-line" style="grid-row:@rno;grid-column:@i"></p>

                            hlines.Add(rno);
                        }
                    }
                    lines.Add(hlines);  // 列ごとに横線の行番号を保持する

                    <!--結果-->
                    <!--横幅を揃えるため、PadRightで5文字分に揃える-->
                    <div style="grid-row:@(rcount - 1);grid-column:@i">@Model.Result[i - 1].Item.PadRight(5, '　')</div>
                }

                <!--1行分を余白とする-->
                <div style="grid-row:@rcount;grid-column:1 / span @Model.NumberOfKuji"></div>

                if (Model.SelectId > 0) // 番号を選択した場合
                {
                    // 選択した番号の列から始める
                    var currentCol = Model.SelectId - 1;
                    // 縦線が始まる行から始める
                    var currentRow = 3;

                    while (currentRow < rcount - 1)
                    {
                        // 現在の列の右側に延びる横線のうち、現在の行より下にある横線を取得する
                        var rightRow = (currentCol == Model.NumberOfKuji) ? null : lines[currentCol].Where(c => currentRow < c).ToList();
                        // 現在の列の左側に延びる横線のうち、現在の行より下にある横線を取得する
                        var leftRow = (0 == currentCol) ? null : lines[currentCol - 1].Where(c => currentRow < c).ToList();

                        // 右への線のうち、一番上にある横線を取得する
                        var rightMin = (rightRow != null && rightRow.Count > 0) ? rightRow.Min() : 999;
                        // 左への線のうち、一番上にある横線を取得する
                        var leftMin = (leftRow != null && leftRow.Count > 0) ? leftRow.Min() : 999;

                        if (rightMin < leftMin)
                        {
                            // 右へ進む

                            // 現在の行から右へ進む横線までの縦線
                            <div class="vertical-line follow" style="grid-row:@currentRow / span @Math.Max(1, (rightMin - currentRow));grid-column:@(currentCol + 1)"></div>
                            // 右への横線
                            <p class="horizontal-line follow" style="grid-row:@rightMin;grid-column:@(currentCol + 1)"></p>

                            currentCol++;   // 右へ進んだ
                            currentRow = (currentRow == rightMin) ? currentRow + 1 : rightMin;
                        }
                        else if (rightMin > leftMin)
                        {
                            // 左へ進む

                            // 現在の行から左へ進む横線までの縦線
                            <div class="vertical-line follow" style="grid-row:@currentRow / span @Math.Max(1, (leftMin - currentRow));grid-column:@(currentCol + 1)"></div>

                            currentCol--;   // 左へ進んだ

                            // 左への横線
                            <p class="horizontal-line follow" style="grid-row:@leftMin;grid-column:@(currentCol + 1)"></p>
                            currentRow = (currentRow == leftMin) ? currentRow + 1 : leftMin;
                        }
                        else
                        {
                            <div class="vertical-line follow" style="grid-row:@currentRow;grid-column:@(currentCol + 1)"></div>
                            currentRow++;
                        }
                    }
                }
            }

            <!--あみだを隠すためのマスク領域-->
            @if (Model.SelectId == 0)
            {
                <div class="mask"></div>
            }
        </div>
    </div>

    <!--モデルをコントローラーへ通知-->
    <input type="hidden" asp-for="Title" value="@Model.Title" />
    <input type="hidden" asp-for="NumberOfKuji" value="@Model.NumberOfKuji" />
    @for (var i = 0; i < Model.Result.Count; i++)
    {
        <input type="hidden" asp-for="Result[i].Item" value="@Model.Result[i].Item" />
    }
    <input type="hidden" asp-for="SelectId" value="@Model.SelectId" />
</form>
```

# 実行してみる
6番を選択してみた結果、晩御飯はぶり大根になったようです。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520699/5a03ec8e-cf1b-26c1-c008-272c9449a3f8.png)

ちなみに、くじの数が多くなると、行数が足りなくなって横線が入らない場合があります。
そのうち、気が向いたら改善する・・・かもしれません。

# まとめ
今回は、Razorを使って選択した番号のあみだを辿る処理を作成しました。
駆け足ですが、ASP.NET Core MVCの学習(入門)は果たせたかな、と思いますので、今回で終わりにします！
GitHubの使い方を勉強したら、ソース全体をUPしようと思います。
→pushしました。[ソース全体はこちら](https://github.com/plusM000/test/tree/master/ASP.NET%20Core%20MVC/Amidakuji)