見出し画像

MSXPen で新年までのカウントダウンを試みた

新年明けたばかりで気が早いかもしれないが、次の年明けまでのカウントダウンをするプログラムを作ってみた。

今回は、Webブラウザ上で実行でき、GET DATE ステートメントGET TIME ステートメントで現在日時が取得できそうである、MSXPen を対象プラットフォームに選んだ。


方針

まず、GET DATE および GET TIME で現在日時を取得する。
GET DATE による日付の取得は GET TIME による時刻の取得の前後の2回行い、一致を確認することで、矛盾した日時を処理に使用しないようにする。
また、現在時刻が変わったときのみ計算および描画を行うようにする。(時刻が変わらずに日付だけ変わらないことはないと仮定する)

次に、現在の日付に基づいて、次の年明けまでの残り日数 (現在の日付は含まない) を計算する。
これは、現在の月 (含む) から12月までの日数を合計し、現在の日を引くことで求まる。
それぞれの月の日数は、以下の法則で求めることができる。

  • 2月は、閏年なら29日、そうでなければ28日

  • 1月~7月 (2月を除く) は、偶数月なら30日、そうでなければ31日

  • 8月~12月は、偶数月なら31日、そうでなければ30日

GET DATE で取得できる年は2桁しかないため、閏年の判定において、年が100の倍数かどうかがわからない。
よって、今回の対応範囲は1901年~2099年とする。(この範囲であれば、4の倍数であって閏年でない年は無い)

ただし、日付が変わっていない場合は、この計算をスキップし、前回計算した日数を用いる。

残りの日数の情報が得られたら、これを86400 (=60×60×24) 倍し、今日の残り秒数を足すことで、次の年明けまでの残り秒数を得る。
この残り秒数は、小数点以下切り上げである。
なお、1月1日 0時0分0秒においては、本来は年明けの直後であるため「次の年明け」は365~366日後であるが、カウントダウンとしての見た目を重視して「残り0秒」とみなす。

最後に、取得した日時 (確認のため) および、計算した次の年明けまでの残り秒数を描画する。

ハマりポイント

ここでは、今回の実装に当たり発見した罠を記す。

SCREEN 1 の横幅が29文字しかない

SCREEN ステートメントの解説には、以下のように書かれている。

文 例

SCREEN 1,2

解 説

使用する画面を32×24文字のテキストモードに設定し、スプライト模様の大きさを16×16に設定します。

その下の画面モード1の説明にも、「32×24文字のテキストモード」と書かれている。

さらに、「2部 3.3 BASICの文法」内の画面モードの紹介においても、「テキストモード(縦32×横24)」が紹介されている。

これらから、SCREEN 1 では、横に32文字入りそうだという印象を受ける。
しかし、実際に試してみると、横には29文字しか入らない。
そのため、ドキュメントを鵜呑みにして横幅32文字のつもりでレイアウトしてしまうと、想定より右に寄ってしまうことになる。

その証拠に、以下のプログラムを実行してみた。

10 SCREEN 1
20 PRINT "12345678901234567890123456789012"

すると、以下の表示になった。

SCREEN 1 の横幅が29文字しかないことの確認

20行目で出力した32文字の文字列のうち最後の3文字が次の行にはみ出しており、画面の横幅が29文字しかないことがわかる。

PRINT USING の書式記号 @ はデフォルトでは使えない

PRINT USING ステートメントの解説では、文字列を編集する書式の一つとして以下が紹介されている。

@ @を指定した文字列で置き換える。

さらに、文例では以下のように紹介されている。(抜粋)

10 A$="ABCDE":B$="ZYXWV"
60 PRINT USING "@ MNL @";A$,B$

しかし、これを実行すると、@ がそのまま出力され、エラー「Illegal function call」が出てしまった。

PRINT USING の書式記号 @ がうまく動かない

詳しく見ると、Japan 系の Machine では @ が期待通り動作するが、America 系の Machine では動作しなそうであることがわかった。
使える環境もあるとはいえ、使えない環境もあり、なおかつ使えない環境がデフォルトであるため、@ は使わない方が良さそうである。

数値を出力したり文字列に変換したりすると余計な空白が入る

以下のプログラムは、1桁の数値「9」に「0」を足して2桁で表示しようとするプログラムである。
PRINT ステートメントでの並べての出力と、STR$ 関数により文字列に変換し、文字列の結合を行っての出力を試みている。
また、PRINT USING ステートメントの書式記号 # も試している。

10 N%=9
20 PRINT USING "##";N%
30 PRINT "0";N%
40 NS$="0"+STR$(N%)
50 PRINT NS$

これを実行すると、以下の実行結果が得られた。

数値の前に余計な空白が入る

書式記号 # では余計な空白は入らないものの、余った部分が「0」ではなく空白で埋められてしまう。
データを並べたり STR$ 関数で変換したりすると、余計な空白が入ってしまい、きれいに2桁に見えない。

今回は、PRINT USING で空白埋め2桁で出力した後、出力した数値が10未満の場合は十の位に「0」を出力することで、ゼロ埋め2桁での出力を実現した。
しかし、これは出力する位置が決まっているからできることであり、出力する位置が決まっていない (テキストの流れで、今のカーソル位置に出力する、などの) 場合は難しいだろう。

取得できる時刻がどんどん遅れる

これは今回のプログラムにとって致命的な罠である。
時刻の取得機能があるからということでMSXPenを選んだものの、肝心の時刻取得機能が使い物にならないため、あまり役に立たないものになってしまった。

GET DATE ステートメントおよび GET TIME ステートメントにより現在日時のようなデータが取得できるものの、その精度はひどいものである。
ページを読み込んだ直後は本当の現在時刻 (Webブラウザを実行しているパソコンの時計の時刻) からの遅れは5秒程度である。(それでも5秒も遅れている)
そして、ページを開いたままにしていると、取得できる時刻がどんどん遅れていくのである。
取得できる時刻が1秒進むのに1秒しかかからないときもあれば、2~5秒程度かかるときもあった。
いくら取得した時刻に基づく計算を頑張ったところで、取得できる時刻自体がデタラメであれば、結果もデタラメになってしまう。

1/60秒ごとに値が増えるとされる TIME を用いた、以下のプログラムを実行してみた。

10 TIME=0
20 NT%=60
30 FOR I%=1 TO 16
40 IF TIME<NT% THEN 40
50 GET TIME T$
60 PRINT TIME;" ";T$
70 NT%=NT%+60
80 NEXT I%

結果は、TIME の値と取得できる時刻には一貫性がありそう (TIME が 60 増えるごとに取得できる時刻が1秒進む) であるものの、取得できる時刻と現実の時刻には一貫性がない (取得できる時刻が14秒進むのに、23秒かかった) というものだった。

ついでに、この動画を YouTube に上げる際、プログラムを貼り付けたら角かっこは使っていない (かっこ自体使っていない) にもかかわらず「角かっこは使用できません」と出て投稿を拒否された。なんでやねん。

YouTube が変なメッセージとともにプログラムの投稿を拒否しやがる件

成果物

デタラメな時刻しか取得できないため実用的ではないが、取得した時刻から年明けまでの秒数を表示することができた。

取得した時刻から年明けまでの秒数を表示できている

なお、サマータイムは考慮しておらず、サマータイムがある場合は取得した時刻との関係も不正確になる可能性がある。

10 SCREEN 1:KEY OFF
20 TC$=""
30 DC$=""
40 GET DATE DS$
50 GET TIME TS$
60 GET DATE D2$
70 IF DS$<>D2$ OR TS$=TC$ THEN 40
80 TC$=TS$
90 H%=VAL(MID$(TS$,1,2))
100 MI%=VAL(MID$(TS$,4,2))
110 S%=VAL(MID$(TS$,7,2))
120 IF DS$=DC$ THEN 290
130 DC$=DS$
140 Y%=VAL(MID$(DS$,1,2))
150 MO%=VAL(MID$(DS$,4,2))
160 D%=VAL(MID$(DS$,7,2))
170 LD%=0
180 FOR I%=MO% TO 12
190 IF I%<>2 THEN 220
200 IF Y% MOD 4 = 0 THEN MD%=29 ELSE MD%=28
210 GOTO 260
220 IF I%>=8 THEN 250
230 IF I% MOD 2 = 0 THEN MD%=30 ELSE MD%=31
240 GOTO 260
250 IF I% MOD 2 = 0 THEN MD%=31 ELSE MD%=30
260 LD%=LD%+MD%
270 IF I%=MO% THEN LD%=LD%-D%
280 NEXT I%
290 DS#=(H%*60.0+MI%)*60.0+S%
300 IF MO%=1 AND D%=1 AND DS#=0 THEN LS#=0 ELSE LS#=86400.0*LD%+86400.0-DS#
310 CLS
320 LOCATE 5,10
330 PRINT USING "##/##/## ##:##:##";Y%,MO%,D%,H%,MI%,S%
340 IF Y%<10 THEN LOCATE 5,10:PRINT "0"
350 IF MO%<10 THEN LOCATE 8,10:PRINT "0"
360 IF D%<10 THEN LOCATE 11,10:PRINT "0"
370 IF H%<10 THEN LOCATE 14,10:PRINT "0"
380 IF MI%<10 THEN LOCATE 17,10:PRINT "0"
390 IF S%<10 THEN LOCATE 20,10:PRINT "0"
400 LOCATE 5,13
410 PRINT USING "######## sec. left";LS#
420 GOTO 40

MSXPen - MSX Developer Playground & Code Editor in the Browser

ROMイメージにもしてみた。

このプログラムは、CC BY 4.0 でライセンスする。

反省と今後

今回は時刻取得機能があり、ブラウザ上で実行できるからということで MSXPen を選んだが、時刻取得機能の品質がひどいものであったため、使い物にならないものが出来上がってしまった。

このような使い物にならないものを作る羽目になった一因として、自分が開発中の OneFiveCrowd にRTCを含む仮想I2Cデバイスを実装したいと思っていたものの、思うだけで実装を怠っていたため、使用できなかったことがある。
自身の怠慢がこのような結果を招いてしまい、誠に遺憾である。

今回、GET DATE ステートメントは「年/月/日」形式、GET TIME ステートメントは「時:分:秒」形式でデータを返してくれることを仮定しているが、もしかしたらそのような形式で返してくれる保証は無いかもしれない。
時刻を正しく取得できる可能性を上げるため、リアルタイムクロックのデータを直接読み取る方式に切り替えたほうがいいかもしれない。
その際には、以下が参考になりそうだ。

Real Time Clock Programming - MSX Wiki


この記事が気に入ったらサポートをしてみませんか?