Unityの謎解きアプリで答えを判定する|C#で表記ゆれと空白を扱う

答えは「猫」なのに、「ねこ」と入力すると不正解になる。謎は解けたのに、アプリの都合で止まってしまうのは、もったいない体験です。

Unityで入力式の謎解きアプリを作るなら、答えの判定は、文字を比較する前に「何を同じ答えとして認めるか」を決めると整理できます。この記事では、C#だけで書ける小さな判定例を紹介します。

ひらがな・漢字の別解は、問題データに持たせる

「ねこ」と「猫」は、意味は同じでも文字列としては別です。読みを自動変換しようとするより、許容する表記を問題ごとに登録すると、意図がわかりやすくなります。

string[] acceptedAnswers = { "ねこ", "猫" };

「ネコ」も認めるなら、配列に加えます。文字そのものを使う謎では、ひらがなとカタカナの違いが仕掛けになることもあるので、全問題に同じ変換をかけるより、問題の意図に合わせて決めます。

比較前に整えること、残すこと

入力の違いこの例の扱い
前後の空白取り除く
途中の空白残す
全角・半角など互換文字の違い問題ごとに正規化を選ぶ
英字の大文字・小文字問題ごとに無視するか選ぶ
読みや意味が同じ別表記許容する答えとして登録する

Trim()は、文字列の前後の空白を取り除きます。途中の空白は消えません。「NEW YORK」のように空白を含む答えを扱うなら、この違いが役立ちます。MicrosoftのTrimドキュメント

Normalize(NormalizationForm.FormKC)は、Unicodeの互換性に基づく正規化です。「ABC」と「ABC」をそろえられますが、丸数字の「①」が「1」になるなど、全角・半角以外にも変化します。文字の形や記号の違いが答えになる問題では、この正規化を無効にします。 MicrosoftのNormalizationFormドキュメント

C#で書く、答え判定の例

次の例は、前後の空白を取り除き、空の入力を不正解として扱います。正規化と、大文字・小文字を無視する比較は、初期状態では無効です。

using System;
using System.Collections.Generic;
using System.Text;

public static class RiddleAnswer
{
    private static string Prepare(string value, bool unifyWidth)
    {
        string text = (value ?? "").Trim();
        return unifyWidth ? text.Normalize(NormalizationForm.FormKC) : text;
    }

    public static bool IsCorrect(string input, IEnumerable<string> accepted,
        bool unifyWidth = false, bool ignoreCase = false)
    {
        string value = Prepare(input, unifyWidth);
        if (value.Length == 0 || accepted == null) return false;
        var comparison = ignoreCase
            ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
        foreach (string answer in accepted)
        {
            if (string.IsNullOrWhiteSpace(answer)) continue;
            if (string.Equals(value, Prepare(answer, unifyWidth), comparison))
                return true;
        }
        return false;
    }
}

たとえば、猫が答えの問題は次のように呼び出せます。

bool correct = RiddleAnswer.IsCorrect(
    inputText, new[] { "ねこ", "猫" });

英字の答えで全角入力と大文字・小文字を許容する場合は、明示的に有効にします。

bool correct = RiddleAnswer.IsCorrect(
    inputText, new[] { "ABC" },
    unifyWidth: true, ignoreCase: true);

比較方法には、文字列を明確なルールで比較するStringComparison.OrdinalとOrdinalIgnoreCaseを指定しています。意味の近さを判断する処理ではなく、登録した答えと一致するかを見る処理です。Microsoftの文字列比較ガイド

「これを正解にする?」を表で確認する

コードを読むだけより、入力例を並べると判定の意図が見えます。たとえば、次のケースを用意してみます。

入力許容する答え・設定結果
猫ねこ、猫正解
前後に空白のある「猫」ねこ、猫正解
ネコねこ、猫不正解
ね こねこ、猫不正解
ABCABC、正規化あり正解
abcABC、大文字・小文字を無視正解
空欄どの答えでも不正解

「ネコ」が不正解なのは、今回の配列に登録していないからです。認めたい表記があるなら、コードを複雑にする前に、答えのデータへ加えます。

画面から呼ぶときは、判定と表示を分ける

入力欄が空なら、判定する前に「答えを入力してください」と伝える。正解なら次へ進むボタンを表示する。不正解なら、入力欄を消さずに考え直せるようにする。文字の判定とは別に、画面の反応を設計します。

正解処理が終わるまで回答ボタンを押せないようにすると、連打で結果画面を何度も開くことも防げます。判定関数は文字列の比較に集中させ、画面遷移やクリア記録は、問題画面側で扱うと追いやすくなります。

答えのデータから、遊びやすさを作る

入力式の謎解きでは、解けたときの喜びを、表記の違いで取りこぼさないことが大切です。その一方で、謎の仕掛けに必要な違いまで消してしまわないよう、問題ごとに許容範囲を持たせます。

画面と問題データの全体像はUnityで謎解きアプリを作るための5画面設計へ。解く途中の助け方は謎解きのヒントを3段階で作る方法でも紹介しています。

作った作品を遊んでみたい方は、謎解き千本ノックの紹介とアプリコレクションをご覧ください。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です