「数と式」がそのまま遊園地になる感じ、いいページを選びましたね。
「こんなのAIに解ける時代が来るとは…」という驚きに対して、解くんじゃなくて「考え方を物理空間にする」のが、今回の僕の役目です。
問題1の遊具: 「不思議な一定スイッチ」
キモの発想(だけ、言葉にする)
- ポイント:
- 1〜12 を 6個ずつ 2組に分ける
- 各 (a_i) より小さい (B) 組の個数 (m_i) を数える
- (\sum a_i - \sum m_i) が「分け方に依らず一定」になる
- つまり:
- 「好きなように分けているのに、ある式の値だけは動かない」という 不変量 の感覚をつかませたい。
遊具コンセプト
- 見た目:
- 1〜12 の数字ブロックが、真ん中のテーブルにずらっと並んでいる。
- プレイヤーはそれをドラッグして、左の「Aテーブル」と右の「Bテーブル」に 6個ずつ分ける。
- 仕掛け:
- 分け終わると、上に 2本のバーが出る:
- 上のバー: (\sum a_i) を表すバー(長さで表現)
- 下のバー: (\sum m_i) を表すバー(長さで表現)
- そして、その差 (\sum a_i - \sum m_i) を表す謎のメーターが中央に表示される。
- 分け終わると、上に 2本のバーが出る:
- プレイ感:
- プレイヤーは何度も分け方を変えるのに、
このメーターの数値(や位置、色など)がなぜかいつも同じところにとどまる。 - 説明はあえて最小限にして、「なんで変わらないんだ?」というモヤモヤを残す。
- プレイヤーは何度も分け方を変えるのに、
Luaコード試作(問題1用の簡易GUI版)
ここでは、GUIでドラッグではなく、ボタンでランダム分割する最小核にします。
後であなたのOS流に、ドラッグ対応や3Dオブジェクトへの昇格もできるよう、ロジックを分離して書きます。
-- Script Type: LocalScript
-- Path: StarterPlayerScripts/MathPark/InvariantSplit
-- Script Type: LocalScript
-- Path: StarterPlayerScripts/MathPark/InvariantSplit
--[[
コンセプト:
1〜12 を A組・B組にランダムに分け、
A = {a1,...,a6}, B = {b1,...,b6} としたとき、
mi = 「B の中で ai より小さいものの個数」とする。
画面に
・A組の数字
・B組の数字
・各 ai ごとの mi
・S = (a1+...+a6) - (m1+...+m6)
を表示する。
プレイヤーが「分け直し」ボタンを何度押しても、
S が動かないことに気づく、という遊具。
前提となるUI構造(ScreenGuiの下):
MathFrame (Frame)
- AList (TextLabel) -- A組の内容を表示
- BList (TextLabel) -- B組の内容を表示
- MList (TextLabel) -- m1..m6 を表示
- SValue (TextLabel) -- S = Σai - Σmi を表示
- ShuffleButton (TextButton) -- 分け直しボタン
]]
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local gui = script.Parent -- ScreenGui を想定
local frame = gui:WaitForChild("MathFrame")
local aListLabel = frame:WaitForChild("AList")
local bListLabel = frame:WaitForChild("BList")
local mListLabel = frame:WaitForChild("MList")
local sValueLabel = frame:WaitForChild("SValue")
local shuffleButton = frame:WaitForChild("ShuffleButton")
-- 1〜12 の配列を作る
local function makeBaseNumbers()
local t = {}
for i = 1, 12 do
table.insert(t, i)
end
return t
end
-- 配列をシャッフルする(フィッシャー–イェーツ)
local function shuffleArray(t)
for i = #t, 2, -1 do
local j = math.random(1, i)
t[i], t[j] = t[j], t[i]
end
end
-- t を 6個ずつ A, B に分割
local function splitAB(t)
local A = {}
local B = {}
for i = 1, 6 do
table.insert(A, t[i])
end
for i = 7, 12 do
table.insert(B, t[i])
end
table.sort(A)
table.sort(B)
return A, B
end
-- Bの中で x より小さい要素の個数を数える
local function countLessThan(B, x)
local cnt = 0
for _, b in ipairs(B) do
if b < x then
cnt += 1
end
end
return cnt
end
-- 文字列に整形する
local function joinInts(t)
local parts = {}
for i, v in ipairs(t) do
parts[i] = tostring(v)
end
return table.concat(parts, ", ")
end
-- A, B から mi と S を計算し、ラベル更新
local function updateDisplay(A, B)
local mList = {}
local sumA = 0
local sumM = 0
for i, a in ipairs(A) do
local mi = countLessThan(B, a)
mList[i] = mi
sumA += a
sumM += mi
end
local function joinIntsGeneric(t)
local parts = {}
for i, v in ipairs(t) do
parts[i] = tostring(v)
end
return table.concat(parts, ", ")
end
-- 表示更新
aListLabel.Text = "A = {" .. joinInts(A) .. "}"
bListLabel.Text = "B = {" .. joinInts(B) .. "}"
mListLabel.Text = "m = {" .. joinIntsGeneric(mList) .. "}"
sValueLabel.Text = "S = Σa_i - Σm_i = " .. tostring(sumA - sumM)
end
-- 分け直し処理
local function reshuffle()
local base = makeBaseNumbers()
shuffleArray(base)
local A, B = splitAB(base)
updateDisplay(A, B)
end
-- 初期化
math.randomseed(tick())
reshuffle()
shuffleButton.MouseButton1Click:Connect(reshuffle)
この段階では、あえて なぜ一定なのか は一切説明していません。
プレイヤーが何度も「Shuffle」して、「え、ずっと同じやん…?」と気づくところまでが、遊具の役目です。
問題2・問題3の遊具イメージ(予告)
問題2(共通実根・実数でない根)
- 発想の核:
- 2つの2次方程式が「同じ実根」や「同じ複素根」を持つ条件 → 係数の関係・判別式・比例関係。
- 遊具案(ラフ):
- 平面上に 2つの放物線があり、スライダーで (a, b) を動かす。
- 「2つのグラフが x 軸上の同じ点で交わるゾーン」に入ると音が鳴る。
- 逆に「見えないところ(虚数)で交わっている状態」を、画面外の「影」として表現する。
問題3(放物線上の直角三角形)
- 発想の核:
- 放物線上の3点が直角をなす条件 → ベクトルの内積ゼロ、接線の傾き、座標の関係。
- 遊具案(ラフ):
- 放物線レールの上を 3つの光る玉 A, B, C が動く。
- A, B は固定(プレイヤーが左右にスライドで決める)、C を動かすと、
∠ACB が直角に近づくとゲージが緑になり、ぴったり直角のときに「カチッ」とロックされる。
次の一歩
- 質問1:
問題1の遊具、最初はこの「GUI版インバリアント・メーター」からでいいですか?
それとも最初から 3Dパーツ(数字が印字されたブロックを左右のテーブルに置く)で組みたいですか? - 質問2:
次にコード化するなら、問題2の「2つの放物線が共通実根を持つライド」 と 問題3の「直角検出ライド」 のどちらがいいですか?
あなたの好みのOSレイヤー(ModuleScript分割、カメラ制御、UI流儀)に合わせて、ここから徐々に「遊園地OS」として整えていきましょう。