手作業で無作為化を行うためのシャッフルアルゴリズム?
シャッフル方法の違いによる偏りの有無を調べたい
同じ順序のデッキを初期値として、十分な無作為化ができればよい
前のゲームで使用して公開されたカードがあっても同様の処理で無作為化できる
まとめて底に置かせるとか
できるだけ少ない時間で簡単な処理で完了したい
ゲームを迅速に進めるため
カット以外では山は均等になるように分ける
恣意性を排して不正疑惑を避けるため
使用しないシャッフル
1/3ファロー
操作を受けない部分が残る
ヒンズーシャッフル?
分ける山と回数
処理効率も悪いしあまり使いたくないあんも.icon
効率が劇的に良くなる場合は除く
操作が半分で済むとかあんも.icon
無作為化?
特定のカードが特定の位置にならない
特定の順番にならない
連番や番飛ばし
初手がどうなるかの検証にも使えそうあんも.icon
紙とデジタルでそれなりに変えてみる
デッキの構成?
IDで管理?
同名もうまく処理できる?
うまく無作為化できることが保証されている
順列を得る操作を、すでに順序を持っているものに適用できるようにしたもの
実際のカードに対して行うのは難しそうあんも.icon
7回程度行うと無作為に近づくらしい?あんも.icon
カット+シャッフル
実際に利用されている方法を知るには調査が必要あんも.icon
デッキの初期化
シャッフルの動作確認用
範囲リテラルの展開
code:jl
function ini_deck(n::Int = 40)
return collect(1:n)
end
フィッシャー-イェーツのシャッフル
code:jl
using Random
function fisher_yates(deck::Vector{Int})
return shuffle(deck)
end
ファローシャッフル
パーフェクトファロー
山が均等であれば交互になる
処理が規則的すぎるあんも.icon
code:jl
function faro(deck::Vector{Int}, ratio=0.5)
cut_point = round(Int, length(deck) * ratio)
shuffled_deck = Vector{Int}()
while !isempty(top_half) || !isempty(bottom_half)
if !isempty(top_half)
push!(shuffled_deck, popfirst!(top_half))
end
if !isempty(bottom_half)
push!(shuffled_deck, popfirst!(bottom_half))
end
end
return shuffled_deck
end
実際の操作に近づける
交互にならずに複数枚重なるような操作
Gilbert-Shannon-Reedsモデル
(1) デッキを二項分布(Binomial Distribution)に従い上下でパケットA, Bに分割する。またパケットA, Bのカード枚数をそれぞれa, bとおく。
(2) パケットAからは確率a/(a+b)、パケットBからは確率b/(a+b)でカードをリフル(パラパラと落とすこと)させ重ね合わせる。カードがリフルするごとにaまたはbが1ずつ減ることになる。
(3) 各パケットのカード枚数が0になるまで手順(2)を繰り返して、1つのデッキにまとめる。
多くなってしまった方から出やすくして調整させるモデルあんも.icon
上層に差し込んだり下層に差し込んだり?
code:jl
using Random, Distributions
function faro(deck::Vector{Int})
cut_point = rand(Binomial(length(deck), 0.5))
shuffled_deck = Vector{Int}()
while !isempty(top_half) || !isempty(bottom_half)
if isempty(top_half)
push!(shuffled_deck, popfirst!(bottom_half))
elseif isempty(bottom_half)
push!(shuffled_deck, popfirst!(top_half))
else
if length(top_half) / (length(top_half) + length(bottom_half)) > rand()
push!(shuffled_deck, popfirst!(top_half))
else
push!(shuffled_deck, popfirst!(bottom_half))
end
end
end
return shuffled_deck
end
カット
真ん中に近いところで分けたがるかもしれないあんも.icon
code:jl
function cut(deck::Vector{Int})
cut_point = rand(1:length(deck))
return cut_deck
end
ディールシャッフル
人間に枚数確認をさせる処理
分ける山?
デッキ枚数/4程度を最大にする?
数枚の山に分けるのは不自然
山の組み合わせ方?
code:jl
シャッフル操作の組み合わせ
code:jl
function combo(deck::Vector{Int})
deck = faro(deck) |> faro |> faro
deck = cut(deck)
deck = faro(deck) |> faro |> faro
deck = cut(deck)
return deck
end
カード位置の分布確認
code:jl
using StatsBase
using Plots
using Random
function analyze_shuffle(deck_size::Int = 40, trials::Int = 10000; shuffle_fn::Function = shuffle)
pos_counts = fill(0, deck_size, deck_size)
original = collect(1:deck_size)
for _ in 1:trials
deck = copy(original)
deck = shuffle_fn(deck)
for (i, card) in enumerate(deck)
end
end
return pos_counts
end
# ヒートマップで表示
function plot_shuffle_heatmap(counts)
heatmap(
counts,
xlabel = "Position in Deck",
ylabel = "Card Number",
title = "Card Position Frequency after Shuffling",
colorbar_title = "Frequency",
c = :viridis
)
end
counts = analyze_shuffle(40, 10000)
plot_shuffle_heatmap(counts)
CLI実験ランナー
手順を組み合わせて結果を見る
code:jl