非専門的シンギュラリティー研究所

無限に動き続けるシステムを表す方法を AI なども使って考えていきます。

数学ゲーム(1)

ここではもう少しちゃんと仕様を書くことができそうな問題について考えていきます。

まずは「エラトステネスのふるい」をゲーム的にしたものを考えていきます。今後は「ビジュアルプログラミング(11) - 非専門的シンギュラリティー研究所」で書いたものについても取り上げていきたいと思います。

「エラトステネスのふるい」については

などでも書いています。

エラトステネスのふるいゲーム

以下のように ChatGPT に入力しました。

以下のような「エラトステネスのふるいゲーム」を行う C# のプログラムを作ってください:
以下の用語を使います。n = 2, 3, 4, 5,... とします。
n のふるい: n の2倍・3倍・4倍・5倍…の全体
n 以下のふるい: 2のふるい・3のふるい・4のふるい…nのふるいの全体
n 以上のふるい: nのふるい・n+1のふるい・n+2のふるい…の全体
全ふるい: 2のふるい・3のふるい・4のふるい…の全体
全ふるいに含まれないものを素数と呼びます。
n は n 以上のふるいに含まれないので
(*) n が素数であることと n が n-1 以下のふるいに含まれないことは同値です。

k (k = 2, 3, 4, 5,...) 以下のすべての素数を求める手順は以下のようになります。
(1) X の初期値を 2, 3, 4, 5,..., k からなる集合とします。
(2) P の初期値を空集合とします。
(3) X が空であるか、指定した回数繰り返したら終了です。
X が空のとき P が k 以下のすべての素数です。
(4) P に X の最小の元 p を追加します。
(*)によって p は素数です。
(5) X から p と p のふるいを取り除きます。
(6) (3) に戻って繰り返します。
(X と P を一つの配列で行うものが通常の「エラトステネスのふるい」ですがここでは別々とします)

「エラトステネスのふるいゲーム」は部品を組み合わせて k 以下のすべての素数を得るプログラムを作るゲームです。
以下の部品があります:
(a) 2 から k までの集合を作る
(b) 空集合を作る
(c) 空集合かどうかを調べる
(d) 集合から最小の元を取り出す
(e) 集合に元を追加する
(f) 集合から元を取り除く
(g) 集合から n のふるいを取り除く
(h) 指定した回数以内で、指定した条件が成り立つ間繰り返す

以下のような答えになりました。

面白い「証明を組み立てる系ゲーム」ですね。
指定された部品 (a)〜(h) を“プログラム部品”として組み合わせる形で、プレイヤーがエラトステネスのふるいを完成させる C# コンソールゲームを作ってみます。

ポイントは:

  • 各操作を「コマンド」として実装
  • ユーザーがコマンド列を入力
  • 実行して正しく素数が得られるか判定

🎮 エラトステネスのふるいゲーム(C#)

using System;
using System.Collections.Generic;
using System.Linq;

class SieveGame
{
    static List<int> X = new List<int>();
    static List<int> P = new List<int>();
    static int k;

    static void Main()
    {
        Console.Write("k を入力してください: ");
        k = int.Parse(Console.ReadLine());

        Console.WriteLine("=== エラトステネスのふるいゲーム ===");
        Console.WriteLine("使えるコマンド:");
        Console.WriteLine("a : 2からkまでの集合Xを作る");
        Console.WriteLine("b : 空集合Pを作る");
        Console.WriteLine("c : Xが空かチェック");
        Console.WriteLine("d : Xの最小値を取り出す");
        Console.WriteLine("e : Pに値を追加");
        Console.WriteLine("f : Xから値を削除");
        Console.WriteLine("g n : Xからnのふるいを削除");
        Console.WriteLine("h n : n回ループ開始");
        Console.WriteLine("end : 終了");

        while (true)
        {
            Console.Write("\nコマンド入力: ");
            string input = Console.ReadLine();
            if (input == "end") break;

            ExecuteCommand(input);
            ShowState();
        }

        Console.WriteLine("\n=== 結果 ===");
        Console.WriteLine("P = " + string.Join(", ", P));

        if (IsCorrect())
            Console.WriteLine("正解!すべての素数が求まりました!");
        else
            Console.WriteLine("まだ正しくありません。");
    }

    static void ExecuteCommand(string input)
    {
        var parts = input.Split(' ');

        switch (parts[0])
        {
            case "a": // (a)
                X = Enumerable.Range(2, k - 1).ToList();
                Console.WriteLine("X を初期化しました");
                break;

            case "b": // (b)
                P = new List<int>();
                Console.WriteLine("P を空集合にしました");
                break;

            case "c": // (c)
                Console.WriteLine(X.Count == 0 ? "Xは空です" : "Xは空ではありません");
                break;

            case "d": // (d)
                if (X.Count > 0)
                {
                    int min = X.Min();
                    Console.WriteLine($"最小値: {min}");
                }
                break;

            case "e": // (e)
                if (X.Count > 0)
                {
                    int min = X.Min();
                    P.Add(min);
                    Console.WriteLine($"{min} を P に追加");
                }
                break;

            case "f": // (f)
                if (X.Count > 0)
                {
                    int min = X.Min();
                    X.Remove(min);
                    Console.WriteLine($"{min} を X から削除");
                }
                break;

            case "g": // (g)
                if (parts.Length < 2) break;
                int n = int.Parse(parts[1]);
                X.RemoveAll(x => x % n == 0);
                Console.WriteLine($"{n} のふるいを削除");
                break;

            case "h": // (h)
                if (parts.Length < 2) break;
                int count = int.Parse(parts[1]);
                Loop(count);
                break;
        }
    }

    static void Loop(int count)
    {
        for (int i = 0; i < count; i++)
        {
            if (X.Count == 0) break;

            int p = X.Min();
            P.Add(p);
            X.RemoveAll(x => x % p == 0);

            Console.WriteLine($"ループ{i + 1}: p={p} を追加し、そのふるいを削除");
        }
    }

    static void ShowState()
    {
        Console.WriteLine("X = " + string.Join(", ", X));
        Console.WriteLine("P = " + string.Join(", ", P));
    }

    static bool IsCorrect()
    {
        var primes = GetPrimes(k);
        return P.OrderBy(x => x).SequenceEqual(primes);
    }

    static List<int> GetPrimes(int n)
    {
        var sieve = new bool[n + 1];
        for (int i = 2; i <= n; i++) sieve[i] = true;

        for (int i = 2; i * i <= n; i++)
            if (sieve[i])
                for (int j = i * i; j <= n; j += i)
                    sieve[j] = false;

        return Enumerable.Range(2, n - 1).Where(i => sieve[i]).ToList();
    }
}

🧠 ゲームの本質(重要ポイント)

このゲームの面白いところはここです:

★ プレイヤーは「アルゴリズムを発明する」

正しい流れは:

a  (X 初期化)
b  (P 初期化)
h 10(適当な回数)

または手動で:

a
b
e
g 2
f
e
g 3
f
...

🎯 このゲームで学べること

  • 素数の定義(「ふるいに残るもの」)
  • 命題 (*) の意味
  • アルゴリズムと数学の対応
  • 「最小元を選ぶ」ことの重要性

🚀 発展アイデア(かなり面白い)

もしさらに発展させるなら:

  • ❗ 間違った操作を検出(証明チェック)
  • 🧩 ステージ制(小さい k → 大きい k)
  • 🤖 自動証明モード(最短手順探索)
  • 🌳 操作を「構文木」として表示(あなたの興味に直結)

必要なら、「完全にパズル形式(コマンドを並べるUI)」や
「C++版」「GUI版」も作れます。