はじめに
ライフゲームで有名なジョン・ホートン・コンウェイ氏が考案したアルゴリズムの中に "Doomsday Rule" というものがあります。
これは計算により任意の日付の曜日を調べるもので、慣れると暗算も可能です。これをゲームにしてみたいと思います。
実装
まずはアルゴリズムの説明から。
アルゴリズム
曜日とインデックスの関係は次のようになっています。0 ベースで日曜から始まります。
値 | 曜日 |
---|---|
0 | 日曜日 |
1 | 月曜日 |
2 | 火曜日 |
3 | 水曜日 |
4 | 木曜日 |
5 | 金曜日 |
6 | 土曜日 |
これをまず覚えておいてください。
Doomsday Rule のアルゴリズムは次の通りです。2020/04/11
を例に曜日を計算してみましょう。
アンカーデイ (Anchor day) の算出
- 4 桁の年の上 2 桁を取り出します (X=20)。
- 4 で割った剰余を求めます (X=0)。
- 5 を掛けます (X=0)
- 2 を足します (X=2)
- 7 で割った剰余を求めます (X=2)。
アンカーデイ (のインデックス) は 2 となります。
Anchor day = (5 * (C mod 4) + 2) mod 7
アンカーデイ (Anchor day) の算出 (2)
4 つの数字を暗記すれば、アンカーデイはもっと簡単に算出できます。
- 4 桁の年の上 2 桁を取り出します (X=20)。
- 4 で割った剰余を求めます (X=0)。
- [2, 0, 5, 3] の配列でアンカーデイを求めます (X=2)。
アンカーデイ (のインデックス) は 2 となります。
Anchor day = [2, 0, 5, 3][C mod 4]
ドゥームズデイ (Doomsday) の算出
- 4 桁の年の下 2 桁を取り出します (X=20)。
- 奇数ならば 11 を加えます (X=20)。
- 2 で割ります (X=10)。
- 奇数ならば 11 を加えます (X=10)。
- 7 で割った剰余を求めます (X=3)。
- 7 から引きます (X=4)。
- アンカーデイ (のインデックス) を足します (X=6)。
- 7 で割った剰余を求めます (X=6)。
ドゥームズデイは 6...つまり土曜日 となります。
思い出深い日 (Memorable date)
ここから任意の日の曜日を求めるにはいくつかの日を覚えておく必要があります。
月 | 日付 |
---|---|
4 | 4/4 |
6 | 6/6 |
8 | 8/8 |
10 | 10/10 |
12 | 12/12 |
4 月以降の偶数月で月と日が同じ日はドゥームズデイとなります。2020/4/4 は土曜日です。
月 | 日付 |
---|---|
5 | 5/9 |
7 | 7/11 |
9 | 9/5 |
11 | 11/7 |
5 月以降の奇数月の上記の日付はドゥームズデイとなります。月と日を入れ替えた日 (5/9 <-> 9/5, 7/11 <-> 11/7) もドゥームズデイです。
月 | 日付 |
---|---|
1 | 1/3 |
2 | 2/28 |
1 月は 1/3 が、2 月は 2/28 がドゥームズデイですが、
月 | 日付 |
---|---|
1 | 1/4 |
2 | 2/29 |
うるう年だと一日ずれます。
月 | 日付 |
---|---|
3 | 3/7 |
3 月は 3/7 がドゥームズデイです。ホワイトデー (3/14) など、別の覚えやすい日でも構いません。
月 | 日付 | 月 | 日付 |
---|---|---|---|
1 | 1/3 (うるう年は 1/4) |
2 | 2 月の末日 |
3 | 3/7 | 4 | 4/4 |
5 | 5/9 | 6 | 6/6 |
7 | 7/11 | 8 | 8/8 |
9 | 9/5 | 10 | 10/10 |
11 | 11/7 | 12 | 12/12 |
まとめるとこんな感じになります。
先ほどの例だと 2020年
のドゥームズデイは土曜日でした。4/11 に最も近い日付は 4/4 で、この日も土曜日です。4/4 と 4/11 はちょうど 7 日離れているので 2020/4/11 は土曜日という事になります。
例題
ではもう一問。1992/7/19
は何曜日でしょう?
- 19 mod 4 = 3
- [2, 0, 5, 3][3] = 3 (Anchor)
- 92 / 2 = 46
- 46 mod 7 = 4
- 7 - 4 = 3
- Anchor + 3 = 6 (Doomsday = 土曜日)
- 7/11 とは +8 日離れているので (6 + 8) mod 7 = 0 (日曜日)
夏希先輩の誕生日は日曜日です。
See also:
ソースコード
先述のアルゴリズムを Delphi で書いてゲームにしてみました。
program DoomsdayRule;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.DateUtils, System.Diagnostics;
begin
// 初期化
var DoWarr := FormatSettings.LongDayNames; // [1..7]
Randomize;
// タイトル
Writeln('=== The Doomsday Rule ===');
Writeln;
// 出題 (グレゴリオ暦: 1582/10/15~2199/12/31)
var TargetDate := EncodeDate(1582, 10, 15);
TargetDate := TargetDate + Random(Succ(Trunc(EncodeDate(2199, 12, 31) - TargetDate)));
Writeln(FormatDateTime('Q. YYYY" 年 "M" 月 "D" 日 は何曜日?"', TargetDate));
var LineStr := '';
for var i in [0..6] do
LineStr := LineStr + Format('%d:%s ', [i, DoWarr[Succ(i)]]);
Writeln(' (', Trim(LineStr), ')');
Writeln;
// 測定開始
var sw := TStopWatch.StartNew;
// 解答
var Ch: Char;
while True do
begin
Readln(Ch);
if CharInSet(Ch, ['0'..'6']) then
Break;
Writeln('不正な入力です。');
end;
var DoW := Ord(Ch) - Ord('0');
Writeln('A. ', DoWarr[Succ(DoW)]);
Writeln;
// 正解の場合
if DayOfWeek(TargetDate) = Succ(DoW) then
begin
Writeln('正解です!');
Writeln('Time: ', sw.Elapsed.ToString); // 経過時間
Exit;
end;
// 不正解の場合
Writeln('残念。正解は "', DoWarr[DayOfWeek(TargetDate)], '" です。');
Writeln;
// Doomsday Century
var Year: WORD := YearOf(TargetDate);
var C := Year div 100;
var AnchorDay := (5 * (C mod 4) + 2) mod 7;
//var AnchorDay := TArray<Integer>.Create(2, 0, 5, 3)[C mod 4]; // [2, 0, 5, 3]
Writeln('Anchor day (A): ', AnchorDay, ':', DoWarr[Succ(AnchorDay)]);
// 2-digit year
var X := Year mod 100;
Writeln('2-digit year: ', X);
// Leap year?
Writeln('Leap year?: ', TArray<string>.Create('No', 'Yes')[Ord(IsLeapYear(Year))]);
// Odd or Even?
Write('Odd or Even?: ');
if Odd(X) then
begin
Writeln('Odd');
Inc(X, 11);
Writeln('Add 11: ', X);
end
else
Writeln('Even');
// Divide by 2
X := X div 2;
Writeln('Divide by 2: ', X);
// Odd or Even?
Write('Odd or Even?: ');
if Odd(X) then
begin
Writeln('Odd');
Inc(X, 11);
Writeln('Add 11: ', X);
end
else
Writeln('Even');
// Modulo 7
X := X mod 7;
Writeln('Modulo 7: ', X);
// Subtract from 7
X := 7 - X;
Writeln('Subtract from 7 (B): ', X);
// Doomsday
var Doomsday := (AnchorDay + X) mod 7;
Writeln('Doomsday = (A + B) MOD 7: ', Doomsday, ':', DoWarr[Succ(Doomsday)]);
// Memorable Date
Writeln('Memorable Date (= Doomsday):');
if IsLeapYear(Year) then
Writeln('・01/04 (非閏年は 01/03)・02/29 (2 月の最終日)')
else
Writeln('・01/03 (閏年は 01/04) ・02/28 (2 月の最終日)');
Writeln('・03/07 (固定) ・04/04 (月と日が同じ)');
Writeln('・05/09 (09/05 も) ・06/06 (月と日が同じ)');
Writeln('・07/11 (11/07 も) ・08/08 (月と日が同じ)');
Writeln('・09/05 (05/09 も) ・10/10 (月と日が同じ)');
Writeln('・11/07 (07/11 も) ・12/12 (月と日が同じ)');
end.
Delphi 10.3 Rio で開いてコンパイルするとコンソールアプリケーションが得られます。
See also:
遊び方
DoomsdayRule.exe (Windows の場合) を実行します。
答えの番号 (0~6) を入力して〔Enter〕キーを押します。
正解すると解答に掛かった時間を表示します。
答えを間違うと正解と解法が表示されます。
Conway could usually give the correct answer in under two seconds. To improve his speed, he practiced his calendrical calculations on his computer, which was programmed to quiz him with random dates every time he logged on.
(Wikipedia 「Doomsday rule」より引用)
コンウェイ氏は問題を 2 秒で解答できたそうで、今回のようなアプリをトレーニングのために書いたようです。
おわりに
2020/04/11、ジョン・ホートン・コンウェイ氏は新型コロナウィルス感染症 (COVID-19) のため永眠されました。謹んで哀悼の意を表します。
See also: