MTGの能力でインターフェースを学習
今回やること。
あるクリーチャーが、攻撃しているクリーチャーをブロックできるかを判定するプログラムを書く。使用言語はTypeScriptとする。
今回の記事はある程度MTGのルールに詳しいことを前提として書くことにする。
設定する能力とクリーチャー
設定する能力は以下の通りである。
| 能力名 | 効果 | 備考 |
|---|---|---|
| 警戒 | 攻撃してもタップしない。 | 今回のプログラミングでは特に意味のない能力。 |
| 飛行 | 飛行や到達を持つクリーチャーにブロックされない。 | |
| 到達 | (飛行を持つクリーチャーをブロックできる。) | 飛行に参照されるだけの能力*1。 |
| 威嚇 | アーティファクト・クリーチャーや自身の色を含まないクリーチャーにブロックされない。 | |
| Unblockable | このクリーチャーはブロックできない。 | 「ブロックされない」ではない。 |
| HighFlying | このクリーチャーは飛行を持たないクリーチャーをブロックできない。 | 俗語。 到達は無関係。 |
以上の能力を持たせたクリーチャーとして以下を設定する。
| クリーチャー | 能力 | 色 | その他重要な特性 |
|---|---|---|---|
| 人間 | 警戒 | 白 | |
| 天使 | 飛行、警戒 | 白 | |
| ドレイク | 飛行、HighFlying | 青 | |
| ゾンビ | Unblockable | 黒 | |
| ホラー | 威嚇 | 黒 | |
| デーモン | 飛行、威嚇 | 黒 | |
| エレメンタル | (なし) | 赤 | |
| ゴブリン | 威嚇、Unblockable | 赤 | |
| ドラゴン | 飛行 | 赤 | |
| 蜘蛛 | 到達 | 緑 | |
| スフィンクス | 飛行 | 白青黒 | |
| 恐竜 | 警戒、到達 | 赤緑白 | |
| 多相の戦士 | 威嚇、到達 | 白青黒赤緑 | |
| 構築物 | 到達 | (無色) | アーティファクトでもある |
レッツプログラミング
今回のプログラミングでは継承を使わず、すべてインターフェースの実装のみで済ませる。
各クリーチャーと各能力をクラスとして定義する。パーマネントとして持つ特性はパーマネントの実装PermanentImplに委譲する。
interface Permanent { permanent : PermanentImpl; creature : CreatureImpl; abilities : Ability[]; } class PermanentImpl { get name() { return this._name; } get cardTypes() { return this._cardTypes; } get colors() { return this._colors; } constructor( private _name : string, private _cardTypes : CardType[], private _colors : Color[], ) { } } interface CardType { get name() : string; } class Artifact implements CardType { get name() { return "アーティファクト"; } } class Creature implements CardType { get name() { return "クリーチャー"; } } interface Color { get name() : string; } class White implements Color { get name() { return "白"; } } class Blue implements Color { get name() { return "青"; } } class Black implements Color { get name() { return "黒"; } } class Red implements Color { get name() { return "赤"; } } class Green implements Color { get name() { return "緑"; } }
能力は、攻撃クリーチャーがブロックできるか否かに関係するCanBeBlockedと、防御クリーチャーがブロックできるか否かに関係するCanBlockを実装することができる。警戒のようにまったく関係ない能力ならば、何も実装しないこともできる。到達も何もしない能力だが、一応CanBlockを実装しておく。
また、インターフェースの判別のためにisObject isCanBeBlocked isCanBlockを用意する*2。
const isObject = (arg : unknown) : boolean => { return typeof arg === typeof Object() && arg !== null; } interface Ability { ability : AbilityImpl; } class AbilityImpl { get text() { return this._text; } constructor( private _text : string, ) { } } interface CanBlock { canBlock(blocker : Permanent, attacker : Permanent) : boolean; } const isCanBlock = (arg : unknown) : arg is CanBlock => { return isObject(arg) && typeof (arg as CanBlock).canBlock === typeof function(){}; } interface CanBeBlocked { canBeBlocked(attacker : Permanent, blocker : Permanent) : boolean; } const isCanBeBlocked = (arg : unknown) : arg is CanBeBlocked => { return isObject(arg) && typeof (arg as CanBeBlocked).canBeBlocked === typeof function(){}; } class Vigilance implements Ability { ability = new AbilityImpl("警戒"); } class Flying implements Ability, CanBeBlocked { ability = new AbilityImpl("飛行"); canBeBlocked(attacker: Permanent, blocker: Permanent): boolean { return blocker.abilities.some(a => { return a instanceof Flying || a instanceof Reach; }); } } class Reach implements Ability, CanBlock { ability = new AbilityImpl("到達"); canBlock(blocker: Permanent, attacker : Permanent): boolean { return true; // 飛行を参照 } } class Intimidate implements Ability, CanBeBlocked { ability = new AbilityImpl("威嚇"); canBeBlocked(attacker: Permanent, blocker: Permanent): boolean { return blocker.permanent.cardTypes.some(e => { return e instanceof Artifact; }) || attacker.permanent.colors.some(a => { return blocker.permanent.colors.some(b => { return a.name == b.name; }) }) } } class Unblockable implements Ability, CanBlock { ability = new AbilityImpl("このクリーチャーはブロックできない。"); canBlock(blocker: Permanent, attacker : Permanent): boolean { return false; } } class HighFlying implements Ability, CanBlock { ability = new AbilityImpl("このクリーチャーは飛行を持たないクリーチャーをブロックできない。"); canBlock(blocker: Permanent, attacker : Permanent): boolean { return attacker.abilities.some(a => { return a instanceof Flying; }); } }
クリーチャーの実装CreatureImplに、攻撃クリーチャーattackerが防御クリーチャーblockerをブロックできるかを判定する関数canBeBlockedを実装する。その逆であるcanBlockについてはcanBeBlockedを使いまわす。
canBeBlocked関数については、攻撃クリーチャーが持っている能力attacker.abilitiesの中で、CanBeBlockedを実装しているものと、防御クリーチャーが持っている能力blocker.abilitiesの中で、CanBlockを実装しているものを参照する。それをTypeScriptで実現するために関数filterCastInterface isCanBeBlocked isCanBlockを定義した。
マジックの黄金律『「できない」は「できる」に勝つ』のため、能力を適用した結果1つでもブロックできなくなったらそれはブロックできないこととする。それを達成するためにevery関数を用いる。
const filterCastInterface = <From, To>(list : From[], judge : (e : From) => boolean) : (From & To)[] => { return list.filter(e => { return judge(e); }).map(e => { return ((e as unknown) as From & To) }); } class CreatureImpl { canBeBlocked(attacker : Permanent, blocker : Permanent) : boolean { const attackerAbilityList = filterCastInterface<Ability, CanBeBlocked>(attacker.abilities, isCanBeBlocked); const blockerAbilityList = filterCastInterface<Ability, CanBlock>(blocker.abilities, isCanBlock); return blocker.permanent.cardTypes.includes(e => { return e instanceof Creature }) && attackerAbilityList.every(a => { return a.canBeBlocked(attacker, blocker); }) && blockerAbilityList.every(a => { return a.canBlock(blocker, attacker); }); } canBlock(blocker : Permanent, attacker : Permanent) : boolean { return this.canBeBlocked(attacker, blocker); } }
クリーチャーを実装し、実際にどのクリーチャーがどのクリーチャーにブロックされるかをコンソール上に表示してみる。
class Human implements Permanent { permanent = new PermanentImpl("人間", [ new Creature() ], [ new White() ]); creature = new CreatureImpl(); abilities = [ new Vigilance() ]; } class Angel implements Permanent { permanent = new PermanentImpl("天使", [ new Creature() ], [ new White() ]); creature = new CreatureImpl(); abilities = [ new Flying(), new Vigilance() ]; } class Drake implements Permanent { permanent = new PermanentImpl("ドレイク", [ new Creature() ], [ new Blue() ]); creature = new CreatureImpl(); abilities = [ new Flying(), new HighFlying() ]; } class Zombie implements Permanent { permanent = new PermanentImpl("ゾンビ", [ new Creature() ], [ new Black() ]); creature = new CreatureImpl(); abilities = [ new Unblockable() ]; } class Horror implements Permanent { permanent = new PermanentImpl("ホラー", [ new Creature() ], [ new Black() ]); creature = new CreatureImpl(); abilities = [ new Intimidate() ]; } class Demon implements Permanent { permanent = new PermanentImpl("デーモン", [ new Creature() ], [ new Black() ]); creature = new CreatureImpl(); abilities = [ new Flying(), new Intimidate() ]; } class Elemental implements Permanent { permanent = new PermanentImpl("エレメンタル", [ new Creature() ], [ new Red() ]); creature = new CreatureImpl(); abilities = [ ]; } class Goblin implements Permanent { permanent = new PermanentImpl("ゴブリン", [ new Creature() ], [ new Red() ]); creature = new CreatureImpl(); abilities = [ new Intimidate(), new Unblockable() ]; } class Dragon implements Permanent { permanent = new PermanentImpl("ドラゴン", [ new Creature() ], [ new Red() ]); creature = new CreatureImpl(); abilities = [ new Flying() ]; } class Spider implements Permanent { permanent = new PermanentImpl("蜘蛛", [ new Creature() ], [ new Green() ]); creature = new CreatureImpl(); abilities = [ new Reach() ]; } class Sphinx implements Permanent { permanent = new PermanentImpl("スフィンクス", [ new Creature() ], [ new White(), new Blue(), new Black() ]); creature = new CreatureImpl(); abilities = [ new Flying() ]; } class Dinosaur implements Permanent { permanent = new PermanentImpl("恐竜", [ new Creature() ], [ new Red(), new Green(), new White() ]); creature = new CreatureImpl(); abilities = [ new Vigilance(), new Reach() ]; } class Shapeshifter implements Permanent { permanent = new PermanentImpl("多相の戦士", [ new Creature() ], [ new White(), new Blue(), new Black(), new Red(), new Green() ]); creature = new CreatureImpl(); abilities = [ new Intimidate(), new Reach() ]; } class Construct implements Permanent { permanent = new PermanentImpl("構築物", [ new Artifact(), new Creature() ], [ ]); creature = new CreatureImpl(); abilities = [ new Reach() ]; } const getCreatures = () => { return [ new Human(), new Angel(), new Drake(), new Zombie(), new Horror(), new Demon(), new Elemental(), new Goblin(), new Dragon(), new Spider(), new Sphinx(), new Dinosaur(), new Shapeshifter(), new Construct() ]; }; getCreatures().forEach(attacker => { let list = getCreatures().filter(blocker => { return !attacker.creature.canBeBlocked(attacker, blocker); } ) .map(e => e.permanent.name).join(", "); if (list == "") { list = "全員にブロックされる"; } else { list += " にブロックされない"; } console.log(attacker.permanent.name + " は " + list); }); getCreatures().forEach(blocker => { let list = getCreatures().filter(attacker => { return blocker.creature.canBlock(blocker, attacker); } ) .map(e => e.permanent.name).join(", "); if (list == "") { list = "誰もブロックできない"; } else { list += " をブロックできる"; } console.log(blocker.permanent.name + " は " + list); });
出力結果
> 人間 は ドレイク, ゾンビ, ゴブリン にブロックされない > 天使 は 人間, ゾンビ, ホラー, エレメンタル, ゴブリン にブロックされない > ドレイク は 人間, ゾンビ, ホラー, エレメンタル, ゴブリン にブロックされない > ゾンビ は ドレイク, ゾンビ, ゴブリン にブロックされない > ホラー は 人間, 天使, ドレイク, ゾンビ, エレメンタル, ゴブリン, ドラゴン, 蜘蛛, 恐竜 にブロックされない > デーモン は 人間, 天使, ドレイク, ゾンビ, ホラー, エレメンタル, ゴブリン, ドラゴン, 蜘蛛, 恐竜 にブロックされない > エレメンタル は ドレイク, ゾンビ, ゴブリン にブロックされない > ゴブリン は 人間, 天使, ドレイク, ゾンビ, ホラー, デーモン, ゴブリン, 蜘蛛, スフィンクス にブロックされない > ドラゴン は 人間, ゾンビ, ホラー, エレメンタル, ゴブリン にブロックされない > 蜘蛛 は ドレイク, ゾンビ, ゴブリン にブロックされない > スフィンクス は 人間, ゾンビ, ホラー, エレメンタル, ゴブリン にブロックされない > 恐竜 は ドレイク, ゾンビ, ゴブリン にブロックされない > 多相の戦士 は ドレイク, ゾンビ, ゴブリン にブロックされない > 構築物 は ドレイク, ゾンビ, ゴブリン にブロックされない > 人間 は 人間, ゾンビ, エレメンタル, 蜘蛛, 恐竜, 多相の戦士, 構築物 をブロックできる > 天使 は 人間, 天使, ドレイク, ゾンビ, エレメンタル, ドラゴン, 蜘蛛, スフィンクス, 恐竜, 多相の戦士, 構築物 をブロックできる > ドレイク は 天使, ドレイク, ドラゴン, スフィンクス をブロックできる > ゾンビ は 誰もブロックできない > ホラー は 人間, ゾンビ, ホラー, エレメンタル, 蜘蛛, 恐竜, 多相の戦士, 構築物 をブロックできる > デーモン は 人間, 天使, ドレイク, ゾンビ, ホラー, デーモン, エレメンタル, ドラゴン, 蜘蛛, スフィンクス, 恐竜, 多相の戦士, 構築物 をブロックできる > エレメンタル は 人間, ゾンビ, エレメンタル, ゴブリン, 蜘蛛, 恐竜, 多相の戦士, 構築物 をブロックできる > ゴブリン は 誰もブロックできない > ドラゴン は 人間, 天使, ドレイク, ゾンビ, エレメンタル, ゴブリン, ドラゴン, 蜘蛛, スフィンクス, 恐竜, 多相の戦士, 構築物 をブロックできる > 蜘蛛 は 人間, 天使, ドレイク, ゾンビ, エレメンタル, ドラゴン, 蜘蛛, スフィンクス, 恐竜, 多相の戦士, 構築物 をブロックできる > スフィンクス は 人間, 天使, ドレイク, ゾンビ, ホラー, デーモン, エレメンタル, ドラゴン, 蜘蛛, スフィンクス, 恐竜, 多相の戦士, 構築物 をブロックできる > 恐竜 は 人間, 天使, ドレイク, ゾンビ, エレメンタル, ゴブリン, ドラゴン, 蜘蛛, スフィンクス, 恐竜, 多相の戦士, 構築物 をブロックできる > 多相の戦士 は 人間, 天使, ドレイク, ゾンビ, ホラー, デーモン, エレメンタル, ゴブリン, ドラゴン, 蜘蛛, スフィンクス, 恐竜, 多相の戦士, 構築物 をブロックできる > 構築物 は 人間, 天使, ドレイク, ゾンビ, ホラー, デーモン, エレメンタル, ゴブリン, ドラゴン, 蜘蛛, スフィンクス, 恐竜, 多相の戦士, 構築物 をブロックできる
感想
継承を使わないって結構面倒だと思った。でもやってできないこともないので、慣れていきたいと思う。
ソースコード再掲
const isObject = (arg : unknown) : boolean => { return typeof arg === typeof Object() && arg !== null; } const filterCastInterface = <From, To>(list : From[], judge : (e : From) => boolean) : (From & To)[] => { return list.filter(e => { return judge(e); }).map(e => { return ((e as unknown) as From & To) }); } interface Permanent { permanent : PermanentImpl; creature : CreatureImpl; abilities : Ability[]; } interface CardType { get name() : string; } class Artifact implements CardType { get name() { return "アーティファクト"; } } class Creature implements CardType { get name() { return "クリーチャー"; } } interface Color { get name() : string; } class White implements Color { get name() { return "白"; } } class Blue implements Color { get name() { return "青"; } } class Black implements Color { get name() { return "黒"; } } class Red implements Color { get name() { return "赤"; } } class Green implements Color { get name() { return "緑"; } } class PermanentImpl { get name() { return this._name; } get cardTypes() { return this._cardTypes; } get colors() { return this._colors; } constructor( private _name : string, private _cardTypes : CardType[], private _colors : Color[], ) { } } class CreatureImpl { canBeBlocked(attacker : Permanent, blocker : Permanent) : boolean { const attackerAbilityList = filterCastInterface<Ability, CanBeBlocked>(attacker.abilities, isCanBeBlocked); const blockerAbilityList = filterCastInterface<Ability, CanBlock>(blocker.abilities, isCanBlock); return blocker.permanent.cardTypes.includes(e => { return e instanceof Creature }) && attackerAbilityList.every(a => { return a.canBeBlocked(attacker, blocker); }) && blockerAbilityList.every(a => { return a.canBlock(blocker, attacker); }); } canBlock(blocker : Permanent, attacker : Permanent) : boolean { return this.canBeBlocked(attacker, blocker); } } interface Ability { ability : AbilityImpl; } class AbilityImpl { get text() { return this._text; } constructor( private _text : string, ) { } } interface CanBlock { canBlock(blocker : Permanent, attacker : Permanent) : boolean; } const isCanBlock = (arg : unknown) : arg is CanBlock => { return isObject(arg) && typeof (arg as CanBlock).canBlock === typeof function(){}; } interface CanBeBlocked { canBeBlocked(attacker : Permanent, blocker : Permanent) : boolean; } const isCanBeBlocked = (arg : unknown) : arg is CanBeBlocked => { return isObject(arg) && typeof (arg as CanBeBlocked).canBeBlocked === typeof function(){}; } class Vigilance implements Ability { ability = new AbilityImpl("警戒"); } class Flying implements Ability, CanBeBlocked { ability = new AbilityImpl("飛行"); canBeBlocked(attacker: Permanent, blocker: Permanent): boolean { return blocker.abilities.some(a => { return a instanceof Flying || a instanceof Reach; }); } } class Reach implements Ability, CanBlock { ability = new AbilityImpl("到達"); canBlock(blocker: Permanent, attacker : Permanent): boolean { return true; // 飛行を参照 } } class Intimidate implements Ability, CanBeBlocked { ability = new AbilityImpl("威嚇"); canBeBlocked(attacker: Permanent, blocker: Permanent): boolean { return blocker.permanent.cardTypes.some(e => { return e instanceof Artifact; }) || attacker.permanent.colors.some(a => { return blocker.permanent.colors.some(b => { return a.name == b.name; }) }) } } class Unblockable implements Ability, CanBlock { ability = new AbilityImpl("このクリーチャーはブロックできない。"); canBlock(blocker: Permanent, attacker : Permanent): boolean { return false; } } class HighFlying implements Ability, CanBlock { ability = new AbilityImpl("このクリーチャーは飛行を持たないクリーチャーをブロックできない。"); canBlock(blocker: Permanent, attacker : Permanent): boolean { return attacker.abilities.some(a => { return a instanceof Flying; }); } } class Human implements Permanent { permanent = new PermanentImpl("人間", [ new Creature() ], [ new White() ]); creature = new CreatureImpl(); abilities = [ new Vigilance() ]; } class Angel implements Permanent { permanent = new PermanentImpl("天使", [ new Creature() ], [ new White() ]); creature = new CreatureImpl(); abilities = [ new Flying(), new Vigilance() ]; } class Drake implements Permanent { permanent = new PermanentImpl("ドレイク", [ new Creature() ], [ new Blue() ]); creature = new CreatureImpl(); abilities = [ new Flying(), new HighFlying() ]; } class Zombie implements Permanent { permanent = new PermanentImpl("ゾンビ", [ new Creature() ], [ new Black() ]); creature = new CreatureImpl(); abilities = [ new Unblockable() ]; } class Horror implements Permanent { permanent = new PermanentImpl("ホラー", [ new Creature() ], [ new Black() ]); creature = new CreatureImpl(); abilities = [ new Intimidate() ]; } class Demon implements Permanent { permanent = new PermanentImpl("デーモン", [ new Creature() ], [ new Black() ]); creature = new CreatureImpl(); abilities = [ new Flying(), new Intimidate() ]; } class Elemental implements Permanent { permanent = new PermanentImpl("エレメンタル", [ new Creature() ], [ new Red() ]); creature = new CreatureImpl(); abilities = [ ]; } class Goblin implements Permanent { permanent = new PermanentImpl("ゴブリン", [ new Creature() ], [ new Red() ]); creature = new CreatureImpl(); abilities = [ new Intimidate(), new Unblockable() ]; } class Dragon implements Permanent { permanent = new PermanentImpl("ドラゴン", [ new Creature() ], [ new Red() ]); creature = new CreatureImpl(); abilities = [ new Flying() ]; } class Spider implements Permanent { permanent = new PermanentImpl("蜘蛛", [ new Creature() ], [ new Green() ]); creature = new CreatureImpl(); abilities = [ new Reach() ]; } class Sphinx implements Permanent { permanent = new PermanentImpl("スフィンクス", [ new Creature() ], [ new White(), new Blue(), new Black() ]); creature = new CreatureImpl(); abilities = [ new Flying() ]; } class Dinosaur implements Permanent { permanent = new PermanentImpl("恐竜", [ new Creature() ], [ new Red(), new Green(), new White() ]); creature = new CreatureImpl(); abilities = [ new Vigilance(), new Reach() ]; } class Shapeshifter implements Permanent { permanent = new PermanentImpl("多相の戦士", [ new Creature() ], [ new White(), new Blue(), new Black(), new Red(), new Green() ]); creature = new CreatureImpl(); abilities = [ new Intimidate(), new Reach() ]; } class Construct implements Permanent { permanent = new PermanentImpl("構築物", [ new Artifact(), new Creature() ], [ ]); creature = new CreatureImpl(); abilities = [ new Reach() ]; } const getCreatures = () => { return [ new Human(), new Angel(), new Drake(), new Zombie(), new Horror(), new Demon(), new Elemental(), new Goblin(), new Dragon(), new Spider(), new Sphinx(), new Dinosaur(), new Shapeshifter(), new Construct() ]; }; getCreatures().forEach(attacker => { let list = getCreatures().filter(blocker => { return !attacker.creature.canBeBlocked(attacker, blocker); } ) .map(e => e.permanent.name).join(", "); if (list == "") { list = "全員にブロックされる"; } else { list += " にブロックされない"; } console.log(attacker.permanent.name + " は " + list); }); getCreatures().forEach(blocker => { let list = getCreatures().filter(attacker => { return blocker.creature.canBlock(blocker, attacker); } ) .map(e => e.permanent.name).join(", "); if (list == "") { list = "誰もブロックできない"; } else { list += " をブロックできる"; } console.log(blocker.permanent.name + " は " + list); });
【ゆゆ式アドベントカレンダー2022 9日目】ゆゆ式がゆゆ式になるまでの奇跡
この記事はゆゆ式アドベントカレンダー2022の9日目の記事です。ほかの人の記事も見よう。見てください。ご覧ください。拝聴しろ。
きららファンタジアの話
青天の霹靂。
5年はソーシャルゲームとして長寿の部類ではある。それでもサービス当時から始めていた自分にとって、それが終わると言われたら動揺を禁じ得ない。サービス終了の知らせには普段遊んでいないユーザーからも反応があった。こういったコンテンツが一番盛り上がるのはサービス開始直後であり、その次に盛り上がる瞬間はサービス終了直前なのではないか。
サービスが終わるということは、誰もそのコンテンツが遊べなくなるということに他ならない。コンシューマーゲームであったならば自分が遊んでいなくとも、誰かが遊んでいるだろうという希望を持てた。もうエトワリアへのアクセスはできなくなる。誰もきららファンタジアをできなくなる。悲しみ。
いろいろ言っても覆らないことは分かっているのです。ありがとうきららファンタジア。ありがとう芳文社。ありがとうアニプレックス。ありがとうドリコム。ゆゆ式セラピーで気分をネガティブからポジティブに持っていくとしよう*1。きららファンタジアで遊んだことを忘れるまで忘れない。
さて、なんできららファンタジアの話をしたかというと、このアプリがゆゆ式に与えた影響がそれなりに大きいからである。野々原ゆずこはスクミズメガネネコミミせんしとなった。櫟井唯は絶対に自分で選ばない水着を着た。日向縁はモラトリアムと噛まずに言えるようになった。松本頼子は千矢*2のおかーさんになった。相川千穂はアヴァンギャルドになるとアホ唯ちゃんみたいになると分かった。岡野佳はボラのピザが好きだった。長谷川ふみはマフラーを手のように動かせた*3。そして、きららファンタジアカレンダーから松本頼子・相川千穂・岡野佳・長谷川ふみの誕生日が分かるようになり、主要な登場人物の誕生日を祝えるようになった。
このようにきららファンタジアからわかるゆゆ式のエピソードは多岐にわたる。その中でも一番大きい出来事を問われたら全ゆゆ式民のうち2京人は「ゆずこが唯と縁と初めて会ったときのエピソード」を挙げるだろうことは想像に難くない。

というわけで今回はゆゆ式登場人物の馴れ初めについて語ってみる。長い前置きだった。
唯と縁
すべてはここから始まったと言ってもいい。世界は唯と縁が出会ってできたということは

唯が縁のことを気になったのは、縁の家がお金持ちだったからである。みんなから一目置かれていたかは分からないが、少なくとも唯には一目置かれていた。そもそも一目置かないやついる?
alu.jp縁の倍率が高杉晋作*5であることは想像に難くない。しかし最終的に縁は唯を選ぶ。そのきっかけは小学校2年生*6のある日の遠足であった。縁がクラスメイトに外国のお菓子を配っていたのを唯が見ていたところ、縁は唯の方に走ってきた。しかしそこで縁はこけてしまう。

それを間近で見ていた唯は手に持っていたお菓子を渡す*7。

縁はいつも与える方だったが、唯からはもらう方だった。だから好きになった気がする。のちに縁はそう語った。
唯と縁 とゆずこ
唯と縁がゆずこに出会ったエピソードは10巻になるまで、もっと言うのであればきららファンタジアで明かされるまでは一切分かっていなかった*8。

原作のエピソードとしてもこれだけである*9。しかしこの少ない情報量からも分かることがある。意外なのはゆずこからではなく、さらに縁ではなく唯から話しかけてきたという部分だろうか*10。最初に興味を持ったのは縁で、それを唯と話して、最終的に唯がゆずこに話しかけたという流れだろうか。
忘れがちであるが、ゆずこは3人の中で一番頭がいい。縁が一目置くのも当然であろう。しかし、なぜゆずこだったのだろうか。
ゆずこは確かに頭はいいのだが、常識的な成績優秀者である。おそらく他にもっと頭のいい生徒もいただろう。だけど縁が興味を持ったのはゆずこだった。何か感じるものでもあったのだろうか。ゆずこに近付きたいとは前々から考えていて、話しかけるための最後の一押しが単にテストの点数だったというだけかもしれない。これらは憶測あるいは願望にすぎないが、今後語られる日が来るかもしれない。備えよう。
唯と千穂
千穂が唯を知ったのは高校の入学式である。

少なくとも入学式では五十音順に並んでいたため、

さらに千穂が感じた唯の第一印象は「横顔が少し怖そう」というものであった。千穂が唯を気にかけたとしても、千穂の方から話しかけることは考えにくい。
千穂とゆずこ
人を第一印象で判断する千穂の癖*12は、ゆずこや縁に対しても発揮されている。

少なくとも初期の千穂からは唯とだけ仲良くしたい、ゆずこや縁とはできれば距離を置きたいという意図が見て取れた。一人でいた唯に対して「一人?」と聞くのはその最たる例であろう*13。さらに言うなら後ろからゆずこと縁が現れたとき、千穂は逃げるように去っていった*14。

千穂はゆずこを偶然本屋で見かけたとき、つい隠れてしまっていた。さらにゆずこが千穂が好きな作家の本を見ていたとき、「ショックなような嬉しいような」という感想を抱いていた。そしてその直後にショックを受けた自身にショックを受けたのだった。
ゆずこの方も、千穂に気付いていながら話しかけるべきか悩んでいた。行動を起こした結果が実らなかったらと考えると確かに二の足を踏んでしまうもの無理はないだろう。相手が自分を苦手としていると知っているのであればなおさらその足はすくむ。
意外にも、その後二人は仲良くなっている。この後にどんなやり取りがあったかは語られていないが、何事もなく仲良くできたのであれば一安心である。かつて唯の方から話しかけた2人がこうして仲良くなるのは感慨深い。
佳と唯
佳は唯に対して明らかに敵意を向けていた。

良くも悪くも素直な性格である佳は、お気に入りである千穂が気にかけている唯を気に入らなかったのだ。一言で言えば嫉妬である。

ところがその敵意はあっさりと*15払拭されることになる。きっかけはコンビニでの会計中で佳の所持金が会計額を下回ったときだった。その佳に対し唯はあろうことか*16お札を渡してきた。遠慮しながらも受け取った佳はクラスに戻った後に唯がお金にうるさい人物*17と知り、唯が悪いやつでないと知ったのであった*18。
これに関しては、ゆゆ式作中で仲良くなるまでのエピソードが詳しく描写されている。敵意を向ける人物と成長を感じるイベント。ゆゆ式にとっては両方とも珍しいものであったため、読者の印象に強く残っていることだろう。
千穂と佳とふみ

まず最初に佳が千穂に話しかけた。その後、佳とふみが仲良くなっていった。

何故佳が千穂に話しかけたかというと、「一番かわいかったから」とのことである。素直でかわいい。自分の方が真っ先に話しかけたはずの千穂がほかの人物になびくのは佳にとっても面白くなかったことであろう。しかし、千穂は佳から話しかけられる前である入学式のときから唯と話していたのだからアレである。

そんな佳をふみは「かわいそう」と表現した。当時の佳を形容する最適な言葉だろう。そんなふみだが、佳に付き添うようにいつの間に千穂とも仲良くなっていったらしい。もしふみがいなかったら千穂と佳が仲良くなっていたかは分からない*19。
奇跡
ゆゆ式の登場人物の出会いについては、生まれつきの地位や才能が大きくかかわっている。
もし仮に縁がお金持ちの家に生まれてこなければ、唯が縁に対して一目置くこともなかっただろう。もしゆずこの頭がよくなかったら、唯と縁がゆずこと話すきっかけがなくなってただろう。もし唯が縁と友人になっていなかったら、ゆずことも会っていなかっただろう*20。そうなればゆゆ式という物語は生まれなかったかもしれない。ゆゆ式という物語の裏側にはもはや何者か*21の存在を疑わざるを得ないほどの多くの奇跡があった。それらは奇跡と呼ぶにはあまりにも当然であり、当人たちはそれがどれだけ恵まれているかを知るよしもない。そしてそれは人の手でどうにかできるものでない、ある意味で残酷なものである。
しかし奇跡はそれだけではない。人が起こせない奇跡があるように、人が起こせる奇跡も
遠足で転んだ縁に。
テストでいい点を取ったゆずこに。
入学式で不安だった千穂に。
財布を忘れて愉快な佳*22に。
唯の行動とそれが起こした奇跡は

願わくばすべてがゆゆ式にならんことを。
*1:意味が分からない人は https://www.youtube.com/watch?v=TDxkTJ87VC0 を参照。
*3:そのせいでゆゆ式を知らない人から「この子手がないんだよね……」と誤解されることがあったらしい。まあゆゆ式を知らない人はいないので嘘なのですが。
*5:高杉 晋作(たかすぎ しんさく、天保10年8月20日〈1839年9月27日〉- 慶應3年4月14日〈1867年5月17日〉)は、日本の政治家。幕末長州藩の尊王攘夷志士として活躍。奇兵隊など諸隊を創設し、長州藩を倒幕運動に方向付けた。
*6:このころの唯は当時と髪の色が違うのでもしかしたら今の髪は染めているのかもしれない。ゆずこもピンク色だし。
*7:は? 尊い…😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭
*8:2018年10月10日にきららファンタジアのメインシナリオ第一部第7章が公開され、そのエピソード中で語られた。まんがタイムきらら本誌で明かされたのはその直後のことである。
*9:きららファンタジアの方でも似たようなセリフ1つだけで特に深掘りとかはされていなかったため割愛。
*10:あくまでゆずこが言ってるだけであるが、この発言の誤りは縁に対する無礼を意味するため信憑性は高い。
*11:アニメではその後に宇田奈七、岡野佳と続く。
*12:千穂に限らない気はする。
*13:関係ないけどこのころの千穂は髪に取っ手がなかったらしい。
*14:もっとも、ゆずこと縁がちょっかいをかけようとして駆け足気味に迫ってきたのだから逃げるのも無理はない。
*15:1年のころは払拭されなかったため、案外あっさりではないかも。
*16:唯は佳からの敵意に気付いていないというのもあるが。
*17:ゆずこと縁はこの「お金にうるさい」というキャラを気に入っているため、唯が積極的にそういう振る舞いをしているだけであって、決してケチなわけではない。たぶん。
*18:その後唯に対して、千穂の胸を1回だけ触っていいと約束する。その後本当に触ったかについては描写がないため不明。わっふるわっふる。
*19:千穂が消極的、佳が積極的であったため、もしかしたらそりが合わずに仲良くなれなかった可能性が否定しきれない。
*20:そうなったら唯と千穂はもっと早く仲良くなってたかもしれない。
*22:財布を忘れてはいない。
*23:人が起こせない奇跡がそもそも起きていない気もするが。
MTGでわかる確率の話
Magic: the Gathering(MTG)というカードゲームがある。楽しいので一度やってみよう。基本無料で遊べるMTGアリーナというPCゲームもあるぞ。
ところで、MTGを遊んでいるとこういうことがよく起きる。
- 土地*1が引けない/土地しか引かない。
- 相手の引くカードが強すぎて負ける。
- これさえ引かれなければ勝てるというときに引かれて勝てなくなる。
カードゲームをやる上で確率の話は切り離すことができない。実際、これらの確率というのがどのくらいかを知らずにカードゲームをしている人も多いだろう。実際の確率を知らずによく起こる事象を何度も目の当たりにすると、理不尽に感じてしまうのも無理はない。
さて、これらが起こる確率は実際はどれほどのものだろうか。
この記事は、MTGの初期手札を介して確率についての話を行うものである。以降、特に何も言われない場合、前提として以下を設定する。
- ライブラリー*2の枚数は60枚、初期手札は7枚。
- マリガン*3はしない。
- ライブラリーのシャッフルは完璧に行われ、各カードが特定の位置に存在する確率はすべて同様に確からしいものとする。
- 引きたいカードの名前を《七人の小人》とし、そうでないカードを《山》とする。

1枚差しのカードを引く
分かりやすさのために以下のデッキを想定する。
1 七人の小人 59 山
「初期手札で《七人の小人》を引く」というのは、言い換えれば「《七人の小人》がライブラリーの上から7枚のうちいずれかにある」ということである。つまり以下のパターンの内いずれかが達成できればいい。
《七人の小人》がライブラリーの上から1枚目にある確率。
《七人の小人》がライブラリーの上から2枚目にある確率。
《七人の小人》がライブラリーの上から3枚目にある確率。
《七人の小人》がライブラリーの上から4枚目にある確率。
《七人の小人》がライブラリーの上から5枚目にある確率。
《七人の小人》がライブラリーの上から6枚目にある確率。
《七人の小人》がライブラリーの上から7枚目にある確率。
表で表すと以下のようになる。○が書かれてあるところに《七人の小人》がいる。以下同様。
| 上から |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8~ |
|---|---|---|---|---|---|---|---|---|
| ○ | ||||||||
| ○ | ||||||||
| ○ | ||||||||
| ○ | ||||||||
| ○ | ||||||||
| ○ | ||||||||
| ○ |
確率はそれぞれ である。そのまま確率を合計して
が導ける。
1枚差しのカードを初手に引ける確率は と
より少し高い。これを高いと見るか低いと見るかはその人次第だろう。
7枚入れたカードをすべて引く
7 七人の小人 53 山
わざわざ《七人の小人》を指定したので7枚に増やしてみる。初期手札が7枚全部《七人の小人》になるパターンは以下の1通りである。
- 《七人の小人》がライブラリーの上から1枚目から7枚目すべてにある。
| 上から |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8~ |
|---|---|---|---|---|---|---|---|---|
| ○ | ○ | ○ | ○ | ○ | ○ | ○ |
ライブラリーの上から順番に計算していこう。
ライブラリーの上から1枚目が《七人の小人》である確率は、60枚のカードのうち7枚が《七人の小人》であるため である。
次にライブラリーの上から2枚目が《七人の小人》である確率を同様に考えてみる。しかしその確率は ではない。ライブラリーの上から1枚目がすでに《七人の小人》と確定しているため、残りの59枚から考えなければいけない。つまり、「ライブラリーの上から2枚目が《七人の小人》である確率」は、「ライブラリーの残り59枚の上から1枚目が《七人の小人》である確率」と読み替えることができる。考えるべきカードの束は以下である。
6 七人の小人 53 山
よって「《七人の小人》がライブラリーの上から2枚目にある」確率は となる。
同様に3枚目は以下の58枚で考えるので である。
5 七人の小人 53 山
上から 枚目までがすべて《七人の小人》である場合、上から
枚目が《七人の小人》である確率は
である。以下の表のようになる。
| 上から |
《七人の小人》の数 | 全体の数 | 確率 |
|---|---|---|---|
| 7 | 60 | ||
| 6 | 59 | ||
| 5 | 58 | ||
| 4 | 57 | ||
| 3 | 56 | ||
| 2 | 55 | ||
| 1 | 54 |
求めたい確率はこれらが上から7枚目まで同時に起こる確率であるため、それらの積で求めることができる。
麻雀における天和の確率がだいたい であるため、確率は非常に低い*4。もし初期手札7枚がすべて《七人の小人》になったのだとしたら、マリガンする前にTwitterとかにあげてほしい。
6枚積みのカードをすべて引く
6 七人の小人 54 山
7枚積みのカードをすべて引くパターンが1通りしかなかったのに対し、このパターンは以下の7通り存在する。
ライブラリーの上から7枚の内、1枚目以外がすべて《七人の小人》である。
ライブラリーの上から7枚の内、2枚目以外がすべて《七人の小人》である。
ライブラリーの上から7枚の内、3枚目以外がすべて《七人の小人》である。
ライブラリーの上から7枚の内、4枚目以外がすべて《七人の小人》である。
ライブラリーの上から7枚の内、5枚目以外がすべて《七人の小人》である。
ライブラリーの上から7枚の内、6枚目以外がすべて《七人の小人》である。
ライブラリーの上から7枚の内、7枚目以外がすべて《七人の小人》である。
表にすると以下のようになる。
| 上から |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8~ |
|---|---|---|---|---|---|---|---|---|
| ○ | ○ | ○ | ○ | ○ | ○ | |||
| ○ | ○ | ○ | ○ | ○ | ○ | |||
| ○ | ○ | ○ | ○ | ○ | ○ | |||
| ○ | ○ | ○ | ○ | ○ | ○ | |||
| ○ | ○ | ○ | ○ | ○ | ○ | |||
| ○ | ○ | ○ | ○ | ○ | ○ | |||
| ○ | ○ | ○ | ○ | ○ | ○ |
順番に考えていこう。
まずはのパターンを考える。ライブラリーの上からパターンに当てはまる確率を列挙する。「7枚入れたカードをすべて引く」パターンと同様に、
枚目のカードは
枚目までの結果の影響を受けることに注意。
- ライブラリーの上から1枚目が《七人の小人》でない確率は
である。
- ライブラリーの上から2枚目が《七人の小人》である確率は
である。
- ライブラリーの上から3枚目が《七人の小人》である確率は
である。
- ライブラリーの上から4枚目が《七人の小人》である確率は
である。
- ライブラリーの上から5枚目が《七人の小人》である確率は
である。
- ライブラリーの上から6枚目が《七人の小人》である確率は
である。
- ライブラリーの上から7枚目が《七人の小人》である確率は
である。
それらが同時に起こるので、確率はこれらの積となる。
次に、のパターンを考える。同様に条件を列挙する。
- ライブラリーの上から1枚目が《七人の小人》である確率は
である。
- ライブラリーの上から2枚目が《七人の小人》でない確率は
である。
- ライブラリーの上から3枚目が《七人の小人》である確率は
である。
- ライブラリーの上から4枚目が《七人の小人》である確率は
である。
- ライブラリーの上から5枚目が《七人の小人》である確率は
である。
- ライブラリーの上から6枚目が《七人の小人》である確率は
である。
- ライブラリーの上から7枚目が《七人の小人》である確率は
である。
確率はそれらの積であるため、以下の式で求められる。
これは、で現れた式と同様である。
以降のパターンについても、順番が違うだけで分子と分母に現れる数が同じであるため、同じ結果が導ける。
「ライブラリーの上から7枚の内、 枚目以外がすべて《七人の小人》である」パターンになる確率はすべて同じ式で表せることが分かる。いずれかが達成すればいいため、最終的に6枚入れたカードをすべて引く確率は以下で表せる。
2枚積みのカードをすべて引く
2 七人の小人 58 山
2枚積みの場合、1枚差し・6枚積み・7枚積みと同じようにやろうとするとうまくいかない。2枚積みのパターンがちょっと多いためである。考えるべきパターンを以下に列挙する。
ライブラリーの上から1枚目と2枚目が《七人の小人》である。
ライブラリーの上から1枚目と3枚目が《七人の小人》である。
ライブラリーの上から1枚目と4枚目が《七人の小人》である。
ライブラリーの上から1枚目と5枚目が《七人の小人》である。
ライブラリーの上から1枚目と6枚目が《七人の小人》である。
ライブラリーの上から1枚目と7枚目が《七人の小人》である。
ライブラリーの上から2枚目と3枚目が《七人の小人》である。
ライブラリーの上から2枚目と4枚目が《七人の小人》である。
ライブラリーの上から2枚目と5枚目が《七人の小人》である。
ライブラリーの上から2枚目と6枚目が《七人の小人》である。
ライブラリーの上から2枚目と7枚目が《七人の小人》である。
ライブラリーの上から3枚目と4枚目が《七人の小人》である。
ライブラリーの上から3枚目と5枚目が《七人の小人》である。
ライブラリーの上から3枚目と6枚目が《七人の小人》である。
ライブラリーの上から3枚目と7枚目が《七人の小人》である。
ライブラリーの上から4枚目と5枚目が《七人の小人》である。
ライブラリーの上から4枚目と6枚目が《七人の小人》である。
ライブラリーの上から4枚目と7枚目が《七人の小人》である。
ライブラリーの上から5枚目と6枚目が《七人の小人》である。
ライブラリーの上から5枚目と7枚目が《七人の小人》である。
ライブラリーの上から6枚目と7枚目が《七人の小人》である。
全部で21通りの場合分けができる。表にすると以下のようになる。
| 上から |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8~ |
|---|---|---|---|---|---|---|---|---|
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ | |||||||
| ○ | ○ |
基本的には「6枚積みのカードをすべて引く」と同様に、出現順のみ異なるだけで各種パターンに出てくる数字は同じであるため、各種パターンになる確率は同じになる。
21通りすべて同じ確率であるため、最終的な確率はこれを21倍したものになる。
3枚積みのカードをすべて引く
3 七人の小人 57 山
3枚積みのカードをすべて引くパターンも、今までのようにやっていけばできるが、パターンの列挙が手間である。そこで、順列というものを考える。
順列は階乗 を用いて表すこととする。
であり、
である。
個の区別されているものを一列に並べるとき、組み合わせは
通りある。その中で同じグループ内のもの
が区別されない場合、組み合わせは
を
で割った
通りに減る。特に、残りの
個のグループも区別されない場合、組み合わせの数は以下の式で表すことができる。
3枚積みのカードをすべて引くパターンの場合、初期手札7枚の内3枚の引きたいカードが同じグループ、そのほかの残り4枚が同じグループであるため、と考えることができる。
全部で35通りという結果が出た。今回は確認のため、列挙もしてみることとする。
| 上から |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8~ |
|---|---|---|---|---|---|---|---|---|
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ | ||||||
| ○ | ○ | ○ |
確かに35通り確認できた。
また、「2枚積みのカードをすべて引く」パターンは21通りであったが、それも同様に計算できるかどうかを確認する。である。
確かに21を導くことができた。
各パターンの確率は同じであるため、代表パターンの確率を計算し、出現パターンの総数である35倍すれば求めたい確率を求めることができる。代表パターンの確率は、総数と、区別されないものの総数
と
を用いて以下で表すことができる。
これは、先ほど現れた総数の逆数*5*6である。代表パターンの確率が同じような形になることを確認する。
確かに階乗を用いて表すことができた。あとはこれを35倍すればいいので、結果は以下のようになる。
枚積みのカードをすべて引く
ここまで来たら一般化できるだろう。以下の条件を満たす確率を求める。ただし、第1変数
と第2変数
は一致している。
- ライブラリー総数
- 初期手札の枚数
- 引きたいカードの枚数
- 初期手札の中に含まれてる枚数
c 七人の小人 60-c 山
まずは代表パターンを計算し一通りあたりの確率を計算する。引きたいカードの枚数とライブラリーの総数
を用いて以下のように表せる。
次に初期手札のパターンを列挙する。引きたいカードの枚数と初期手札の枚数
を用いて以下のように表せる。
最後にこれらをかける。
ライブラリー総数、初期手札枚数
、引きたいカード枚数
を用いて、引きたいカードが初期手札にすべて集まる確率を求めることができた。
ライブラリー総数、初期手札枚数
のとき、引きたいカードを初手にすべて引ける確率の表を置いておく。
| 引きたいカード枚数 |
確率 |
|---|---|
閑話:封印されしエクゾディアをそろえる
遊戯王OCGの話になるが、これを応用することで《封印されしエクゾディア》《封印されし者の右腕》《封印されし者の左腕》《封印されし者の右足》《封印されし者の左足》が初手にそろう確率を計算することもできる。遊戯王はデッキ*740枚で初期手札が5枚であり、引きたいカードの総数は5枚であるため式は以下になる。
これまでは積んだカードがすべて初手に引くケースを考えてきた。しかし、7枚積んだカードが初手に7枚来るパターンの確率を知ったところで実際に役立つことは少ないだろう。以降、複数積んだカードが1枚以上来るケースを考えることにする。《七人の小人》である必要はなくなったかもしれないが、以降も同じく《七人の小人》で考えることにする。
2枚積みのカードを1枚以上引く
2 七人の小人 58 山
2枚積みのカードを初手に1枚以上引く確率は以下の合算で求められる。
初手に1枚ちょうど引く確率。
初手に2枚ちょうど引く確率。
そのうち、はすでに求めたので、
を考えればいい。
の確率は以下の合計で求めることができる。
ライブラリーの上から1枚目~7枚目の内、1枚目にのみ《七人の小人》がある。
ライブラリーの上から1枚目~7枚目の内、2枚目にのみ《七人の小人》がある。
ライブラリーの上から1枚目~7枚目の内、3枚目にのみ《七人の小人》がある。
ライブラリーの上から1枚目~7枚目の内、4枚目にのみ《七人の小人》がある。
ライブラリーの上から1枚目~7枚目の内、5枚目にのみ《七人の小人》がある。
ライブラリーの上から1枚目~7枚目の内、6枚目にのみ《七人の小人》がある。
ライブラリーの上から1枚目~7枚目の内、7枚目にのみ《七人の小人》がある。
例えばのパターンであるならば上から2枚目から7枚目に《七人の小人》を引いてはいけないことに注意してほしい。このパターンは
で計算されているため、二重に集計されてしまうことになる*8。いずれのパターンも確率は同じであるため、代表して
のパターンの確率を求める。
のパターンも、引けなかった方の《七人の小人》の位置で場合分けを行う。
ライブラリーの上から1枚目と8枚目に《七人の小人》がある。
ライブラリーの上から1枚目と9枚目に《七人の小人》がある。
ライブラリーの上から1枚目と10枚目に《七人の小人》がある。
ライブラリーの上から1枚目と11枚目に《七人の小人》がある。
ライブラリーの上から1枚目と58枚目に《七人の小人》がある。
ライブラリーの上から1枚目と59枚目に《七人の小人》がある。
ライブラリーの上から1枚目と60枚目に《七人の小人》がある。
全部で53通りの場合分けができた。まずは代表してと
と
を考える。当たり前だが、1枚目と8枚目以外は《七人の小人》ではない。
いずれの場合も出てくる数字の順番のみが異なるだけで、出てくる数字は同じである。そのため各パターンの確率はすべて同じである。
53通りのパターンの確率がすべて同じであるため、単純に掛け合わせることでの確率を求めることができる。
以降も同様に求めることができる。確率は
と同じであるため、7倍することで
の確率を導ける。
あとはと
を足し合わせる。
だいたい24%程度である。4回初期手札が来れば1回は引けたり引けなかったりする確率だろうか。
3枚積みのカードを1枚以上引く
基本的には「2枚積みのカードを1枚以上引く」と同様に考える。
3 七人の小人 57 山
初手に1枚ちょうど引く確率。
初手に2枚ちょうど引く確率。
初手に3枚すべて引く確率。
については
とすでに分かっているため、
と
を求めることとする。まずは
になるパターンを考えてみる。
ライブラリーの上から1枚目と8枚目と9枚目に《七人の小人》がある。
ライブラリーの上から1枚目と8枚目と10枚目に《七人の小人》がある。
ライブラリーの上から1枚目と8枚目と11枚目に《七人の小人》がある。
このまま列挙してもいいが、順列の考え方でこれが全部で何通り現れるかを計算することもできる。初期手札となる7枚目までに引きたいカードが1枚、8枚目以降の53枚の内のいずれかに引きたいカードの残りの2枚が現れるパターンと考えた場合、組み合わせの数は以下の式で表すことができる。
あとは一通りあたりの確率とかけ合わせればを求めることができる。その確率も階乗を用いて
と表すことができる。
も同様に求めることができる。繰り返しになるが、以下をかけ合わせることで求められる。
一通りあたりの確率
初期手札に引きたいカードが2枚あるパターンの総数
ライブラリーの残りに引きたいカードが1枚あるパターンの総数
それぞれの確率は以下となる。
最終的には以下となる。
求めたいのはであるため、あとは足し合わせる。
3枚積みのカードはだいたい3割強の確率で初手に来ることが分かる。
枚入れたカードが初手に
枚来る確率
4枚積みのカードについても気になるが、先に一般化する。以下の条件で確率を設定する。
- ライブラリー総数
- 初期手札の枚数
- 引きたいカードの枚数
- 初期手札の中に含まれてる枚数
一通りあたりの確率を求める。ライブラリーの総数と引きたいカードの枚数
を用いて以下で表せる。
初期手札のパターンの総数を求める。これは枚引くのでそれに合わせる。
初期手札以外の残りのライブラリーのパターンの総数を求める。引きたいカードが初期手札に枚引けているので、残りのライブラリー
の中には残りの引きたいカード
が残っている。
はこれらをかけ合わせることで求めることができる。
は
枚ちょうど引く確率であるため、1枚以上引く確率
は総和
で求める必要がある。
これですべてのパターンを網羅することができた。
ちなみにこれはと入れたとき、0枚ちょうど引けた確率
1枚も引けない確率を求めることができる。1枚も引けない確率
と1枚以上引く確率
の合計は当然ながら1になるため、以下の式から1枚以上引く確率
を求めることもできる。
計算自体は面倒なので、Excel等の計算機に任せるといい。
ライブラリー総数、初期手札枚数
のとき、引きたいカードを初手に1枚以上引ける確率の表を置いておく。
| 引きたいカードの枚数 |
確率 |
|---|---|
練習問題
ライブラリー60枚の内に引きたいカードが4枚ある場合、それが初手に来る確率が
であることを確かめよ。
ライブラリー60枚の内土地が24枚ある場合、ライブラリーの上から9枚に土地が3枚以上ある確率が
であることを確かめよ。
*1:カードのコストを出すカード。引かなすぎると高コストのカードが使えず負ける。引きすぎると今度は使えるカードが相対的に減って負ける。
*2:山札のこと。
*3:簡単に言えば引き直しのこと。MTGでは初期手札の枚数分だけ(普通は7回)マリガンができるが、マリガンを行った回数だけ初期手札が減ってしまう。
*4:ただし、0ではないため起こりえる。
*5:ある数Aとその逆数Bの積は1になる。特に分数の逆数は分子と分母を入れ替えたものである。
*6:各パターンの確率が同様に確からしいため、それらの確率は総数の逆数で求めることができる。また、確率の総和は1になる。
*7:山札のこと。特にメインデッキのこと。
*8:2枚目を引くことを許容し直接1枚以上引く確率を直接求めることもできるが、考えるべきパターンが膨れ上がってしまうため割愛。
拠点
Twitterプロフィール用です。
ファンアート #わひろちゃん pic.twitter.com/yJHIj6WA72
— 大和🍅2期はよ (@Ohwa_Hirokazu) 2021年7月24日
数の拡張

前回、線形代数の言葉の意味について学習しなおした。その結果、線形性について代数を用いて調査する学問だということが分かった。何も分かってないようなものでは?
今回はいよいよ線形代数について学びたいと思う。これまで3つも記事を挟んだがその甲斐は多分あったりなかったりしたりしなかったりしたりしなかったりすると思ったり思わなかったりしたりしなかったりしたりしなかったりしたりしなかったりしたりしなかったりすると思われると思われる。多分しない。
このシリーズでは、セガ様より公開されている線形代数の資料『基礎線形代数講座 線形代数・回転の表現』に準じて学習していく。
また、この記事では同資料の「【第1講】イントロダクション」を読み解いていく。
数の拡張
最初、数字は自然数 しか存在しなかった。
引き算のために と負の数が導入され、整数
になった。
割り算のために分数が導入され、有理数 になった。
の計算により無理数が発見され、実数
になった。
の解のために虚数単位
を定義し虚数が導入され、複素数
になった。
「こんなに必要なのか?」という問いを思い浮かべたことがある人も少なくないと思う*1。しかし、因果関係が逆である。これらの数については「必要だから導入された」のだ。数は道具であり、便利になっていくべきだろう。その便利さを追求していくにつれて、数が表現できるものはそれこそ無限以上に増えてしまった*2。
数で表せるものは多ければ多いほどいいのだ。
代数学の基本定理
参考書には、こう書かれている。
今までの拡張は、 次関数の解をすべて見つけるために役立った。そして、
次関数の解を見つけるためにはこれ以上の拡張を必要としない。
その気になればどんどん拡張はできそうではあるが、それはまた別の話。
※余談だが、 _ で囲まれた部分が強調表示されてしまうため、下付き文字を2つ以上使用する場合 \ でエスケープする必要がある。
そもそも線形代数とは

今回から本格的に線形代数の学習に取り掛かりたいと思う。
線形代数が分からない
早速勉強しようと思ったが、その前にまず解決すべき疑問があった。
そもそも線形代数ってなんだ?
授業/講義とかで必修だったからとりあえず「線形代数」を取ってはいたのだが、そもそもこの「線形代数」が何を取り扱っているのかをいまいち理解していない。その講義ではベクトルや行列を取り扱っていたが、「線形代数」のもともとの意味を知らないのであれば理解度が残念になるのは当然と言える*1。
まずは言葉の意味から改めて調べたいと思う。
線形
まずは「線形」で調べてみると、こんなページにたどり着いた。
そこにはこう書かれていた。
関数などの演算
が,任意の
に対して、
を満たすとき,のそのような性質を線形性と呼ぶ。
を満たす性質を線形性/linearityと呼ぶらしい。
線形性,(あるいは線型性とも書きます)は大雑把に言うと 直線っぽいということを表しています。
直線っぽければ線形/linearらしい。
要は を満たせば線形性を持つということらしい。同じページに高校数学における例も載っているので抜粋する。
切片が
の一次関数
総和
極限
微分と積分
線形性を満たすものは結構多い。
代数
「代数」 の名の通り数の代わりに文字を用いて方程式の解法を研究する学問として始まった。
文字を用いて方程式を解く学問を「代数学」というらしい。 や
などの文字を代数と呼ぶらしい。
例えば、 という式は代数学に含まれる。
ちょっと調べたら ガロア理論 とか 次関数の解の公式は存在しない とか気になる記述を見つけたのだが、これをがっつりやるとなると 『線形代数学を学びなおす』ことができなくなる ので割愛する。
線形代数学とは
調べた結果、「線形性を持つものについて特徴を突き詰めた学問」という結論に至った。線形性というのは、直線っぽいものであり、原点を通るものである。
違ったら教えてください😔。
はてブロで数式を書く

なぜブログ形式で記事を書こうかと思ったかというと、その形式の方が分かりやすくまとめられると思ったからである。つまり数式が書きたいのだ。
まず線形代数を書く前に、はてブロで数式を記述する練習をしたいと思う。
記法を反映させる
数式を書く基本的な操作は、 [tex: と ] で囲むことである。さらにいうと、これらで囲むことによって数式に限らず 記法が有効になる。
[tex:\LaTeX]
ここに 記法についてまとめたサイトがある。これを参考にいろいろ書いてみる。
一次関数
まずは一次関数 f(x) = ax + b を書いてみる。
[tex: f(x) = ax + b ]
変数が斜体(イタリック)になった。数式っぽくなっている。
もしセンタリングしたいのであれば、htmlタグを使う。
<div align="center"> [tex: f(x) = ax + b ] </div>
この例からも分かるとおり、 [tex: と ] の間には改行があっても 記法はできる。
三角関数
次に三角関数 f(x) = sin(2πx) を書いてみる。
<div align="center"> [tex: f(x) = sin(2 \pi x) ] </div>
は
記法だと
\pi で表せる。しかし、 sin が斜体になってしまっている。普通、三角関数は斜体ではなく立体(通常の字体)で表すので、違和感がある。さらに言えば、変数は斜体で書くという慣例があるため、これだと と受け取られてしまう可能性がある。
なので斜体にならないようにする。幸い 記法では
\sin が用意されているのでそれを用いる。
<div align="center"> [tex: f(x) = \sin (2 \pi x) ] </div>
が立体になった。
斜体と立体とギリシャ文字
斜体と立体でどう違うのかを確かめてみる。
<div align="center">
[tex: a \; \rm{a} \; \it{a} ]
<div>
左から、規定、立体指定(\rm{ })、斜体指定(\it{ })である。 については、規定で斜体になっていること、立体と斜体で見え方が異なっていることが分かる。また、
\; で文字と文字の間にスペースを挿入することができる。
ついでに を斜体にしてみたりしてみなかったりしてみる。
<div align="center">
[tex: \pi \; \rm{\pi} \; \it{\pi} ]
</div>
だと違いがない。
<div align="center">
[tex: \Pi \; \rm{\Pi} \; \it{\Pi} ]
</div>
[tex; \pi ] を に、つまり小文字から大文字にしてみた。変わった。
texでの細かい話4。ギリシャ文字について - D_PLIUS’s blog
どうやら規定では、ギリシャ文字の小文字は斜体、大文字は通常の字体になるらしい。はてブロの数式で立体のギリシャ文字の小文字を使う方法は多分ないっぽい? μ*1とかSI単位系の を表すためにほしいと思うのですが。
多分「線形代数を学びなおす」シリーズでは使わないだろうし一旦ほっとこう。
オイラーの公式
オイラーの公式を書いてみる。
<div align="center">
[tex:
\begin{eqnarray}
\sin \theta & = & \sum^{\infty}_{n=0} & \frac{(-1)^n}{(2n+1)!} & \theta ^{2n+1} \\
\cos \theta & = & \sum^{\infty}_{n=0} & \frac{(-1)^n}{(2n)!} & \theta^{2n} \\
e^{\theta} & = & \sum^{\infty}_{n=0} & \frac 1 {n!} & \theta ^n
\end{eqnarray}
]
</div>
学ぶことが多いので箇条書きで。
- 数式郡を
\begin{eqnarray}\end{eqnarray}で囲むことで複数の数式を整理して記述することができる。&で位置をそろえ、\\で改行する。- 【20210621追記】
eqnarrayを使用する場合、htmlタグ<div></div>で囲まないと&がに化ける。 必ず併用すること。ちなみに
<span></span>では駄目だった。
- 【20210621追記】
\thetaでを出力する。
\sum^{《上部添え字》}_{《下部添え字》}で総和記号のを出力する。
\inftyで無限を表す記号を出力する。
\frac{《分子》}{《分母》}で分数を出力する。
^{《上付き文字》}で上付き文字を出力する。通常は指数を書く。- 自然対数の底
はそのまま
eと書く。 {}については1文字だけなら省略できる。
ここからオイラーの公式を導く式を記述してみる。ここで、 である。
<div align="center">
[tex:
\begin{eqnarray}
e^{ix} & =& \sum^{\infty}_{n=0} \frac{i^n}{n!}x^n && \\
&=&\sum^{\infty}_{n=0} \frac{i^{2n}}{(2n)!} x^{2n} &+& \sum^{\infty}_{n=0} \frac{i^{2n+1}}{(2n+1)!} x^{2n+1} \\
&=&\sum^{\infty}_{n=0} \frac{(-1)^n}{(2n)!} x^{2n} &+i& \sum^{\infty}_{n=0} \frac{(-1)^{n}}{(2n+1)!} x^{2n+1} \\
&=& \cos x &+i& \sin x
\end{eqnarray}
]
</div>
オイラーの公式を導き出せた。
ベクトルと行列を記述する。
ベクトルは色々な記述方法がある。好きなものを使おう。
<div align="center">
[tex: \vec{a} \; \vec{AB} \; \overrightarrow{a} \; \overrightarrow{AB} \; \boldsymbol{a} ]
</div>
このシリーズでは、 \overrightarrow を用いる*2。
行列を記述してみる。
\mathbf{ } で太字にできる。 \begin{bmatrix} \end{bmatrix} で囲むことで大かっこの行列が、 begin{pmatrix} \end{pmatrix} なら小かっこの行列が出力される。このシリーズでは行列を斜体で表し、 \begin{bmatrix} \end{bmatrix} の方を主に使用する。要素は & で並べ、 \\ で改行する。
<div align="center">
[tex:
E=I=
\begin{bmatrix}
1 & 0 & \dots & 0 \\
0 & 1 & \dots & 0 \\
\vdots & \vdots & \ddots & \vdots \\
0 & 0 & \dots & 1
\end{bmatrix}
]
</div>
- 単位行列については、
を用いる。
\cdotsで、
\vdotsで、
\ddotsでを出力する。
疲れた
ひとまずはこんなところでいいだろう。もし他に表記したい記述があった場合、その都度調べることにする。