したらばTOP ■掲示板に戻る■ 全部 1-100 最新50 | |
レス数が1スレッドの最大レス数(1000件)を超えています。残念ながら投稿することができません。

おちゃめくらぶ掲示板

1824御茶目菜子:2013/12/14(土) 23:59:32
プチコンでフレーム単位でスリープ時間を取得する方法
プチコンでスリープしている時間(画面を閉じている時間)を1フレーム単位で取得できるルーチンを
作ったにょ。
といっても、これは1年以上前に作っていて放置していたものだけどね(笑)

@SLEEP
T$=TIME$:FOR A=0TO 1:A=MAINCNTL*(T$!=TIME$):NEXT:TMREAD(T$),H,M,S:X=H*3600+M*60+S:?"READY"
FOR U=0TO 1:FOR T=0TO 1:B=MAINCNTL:TMREAD(TIME$),H,M,S:Y=H*3600+M*60+S:T=(Y-X)*60-60-B+A
WAIT 1:NEXT:A=B:X=Y-1:U=T>2:NEXT:RETURN
http://ww5.tiki.ne.jp/~ochame/petitcom/tips/routine.htm#sleep

プチコンにはスリープしたかどうかを判断する命令やシステム変数はないにょ。
しかし、1秒単位の時間ならば簡単に取得が可能にょ。
それはTIME$の変化で判断すればいいからにょ。
TIME$は1秒単位で変化しているため「秒数が1大きくなったか」という判定ではスリープが
行われたかどうかは判断できないため「秒数が2大きくなったか」という判定が必要になるにょ。
つまり、1秒単位で2秒以上スリープした状態であればTIME$だけで簡単に判断が可能ということ
になるにょ。

上記の私が作ったプログラムでは1フレーム単位で1フレーム以上スリープしていたら判断が
可能になっているにょ。
この原理は実は至って簡単で1フレームごとにインクリメントしているMAINCNTLの値とTIME$を
併用するだけにょ。
TIME$はスリープ中でも変化するけどMAINCNTLはプチコンが起動していいないと変化しないため
その変化の度合いを見ていけば何フレームほどスリープしたかは判断が可能になるわけにょ。
そのためには厳密な基準点が必要になるにょ。
このプログラムでは1行目で秒数が変わる瞬間のMAINCNTLの値を求めているにょ。
そして、その時の秒数も変数に入れておけばその両方の変化を見ることで何フレームほど
スリープしたかが分かるようになるにょ。(ここではTIME$の下2桁だけではなく0時0分0秒を
基準とした秒数をTMREAD関数を使って計算することで1分以上のスリープ時間を測定できると
ともにリスト短縮を可能にしている)

では、問題はどうやってここからスリープ時間をフレーム単位で求めるかということにょ。
そのためには本体を開いて次に秒が変わる瞬間のMAINCNTLの値から判断する必要があるにょ。
例えば秒数が100の時にMAINCNTLの値が1000だったとするにょ。

 秒数    100  101  102  103  104
 MAINCNTL 1000  1060  1120  1180  1205

101〜103のように秒数が変わるタイミングでMAINCNTLの値が60ずつ増えていけばスリープに
なってないことが分かるし、103から104に変わった時に60増えてない(25しか増えてない)
ということから103と104の間でスリープになったということが分かるにょ。
ここで本来ならば60増えてないとおかしいのに25しか増えてないため差分の35フレームが
スリープ時間となるにょ。
これをIF文で判断(秒数が変わったかどうか、その瞬間に前回と比べて60増えているかどうか
という2つのIF文を使用)してやればスリープ時間のフレーム数を求めることは簡単にょ。

しかし、そんなことをする必要はないにょ。
秒数を基準に判断するのではなくMAINCNTLの値を基準に判断すればいいにょ。
1秒ごとに60加算していきそれとMAINCNTLの値とで合算すればいいだけのことにょ。
それで差分をとってやって0よりも大きな値になっていればスリープしていると判断が
可能になるにょ。
その式が「T=(Y-X)*60-60-B+A」というものにょ。(Tがスリープ時間のフレーム数だけど
「-60」が付いているのはXというのは基準点より1秒前の秒数であるため)
秒数は1フレームごとに変化しているわけではないけど秒数が変わるタイミングが最大値と
なるためそれ以外は負数となるためTの値が0より大きくなっているかどうかだけで
スリープが行われたかどうかの判断が可能になるにょ。
これもすべてまとめてFOR文で行っているためIF文で判断する必要はないにょ。

ここで問題となるのはプチコンの演算範囲にょ。
秒数×60+MAINCNTLの値なんてことをしたら簡単にOverflowになってしまうにょ。
そのためできるだけOverflowになりにくい演算順序で計算する必要があるにょ。
といっても特に難しいことをしているわけではなくあらかじめ秒数の差分をとってそれを
60倍しているだけにょ。
あとは現在のMAINCNTLの値の減算を先に行うことでスリープ時間のフレーム数がプチコンの
演算範囲内であれば計算が可能になるにょ。(約145分のスリープに対応)
それを越える場合には32bit整数演算変換ルーチンを使えばいいにょ。
http://ww5.tiki.ne.jp/~ochame/petitcom/tips/routine.htm#int
これを使うことで2147483647フレーム(約14ヶ月)までのスリープに対応が可能になるにょ。
このプログラムではTIME$しか使用していないため日付を跨いだスリープは判定ができないため
DATE$を使って日数計算(スリープ開始の日付から何日経ったか)を行う必要があるにょ。
普通に考えたらそこまで連続してスリープのフレーム数を求めたいと思うことはないだろう
から現行の最大145分でそれほど問題はないと思われるにょ。(それ以前に本体内蔵の時計の
精度を考えると1ヶ月で秒単位の大きなずれが発生すると思うのでフレーム単位で求めても
意味のあることではない)

実はこれだけではフレーム単位でのスリープ時間を求めることはできないにょ。
確かに机上ではこれで合っているけれど実は1秒がMAINCNTL60カウント分と厳密に等しいと
いうわけではなく若干の誤差があるためにょ。
といっても1秒≒59.8フレームなので誤差といっても0.3%程度に過ぎないのだけど1フレーム
単位で求めるならばその誤差はかなり大きいにょ。
累積誤差が1フレーム分発生した時点でスリープと判断されてしまうためにょ。
この許容誤差を大きくするというのも1つの手だけどこのプログラムではTの値が2になった時
(IF文の代わりにFOR文で判断しているためNEXTで1加算されるので1フレームのずれの場合
にはT=2となる)「スリープは行われていない」と判断してそこを秒数やフレーム数の基準点に
変更しているにょ。
つまり、1フレームから判断可能だけど事実上2フレーム以上のスリープのみ対応させることで
MAINCNTLのずれによる誤作動を無くしているにょ。


ということで、このプログラムの仕組みは概ね理解できたのではないかと思われるにょ。
このプログラムは冒頭に書いたように1年以上前に作ったものにょ。
それを過去に作ったプログラムを適当に見ていたら思い出したので今回発表したわけだけど
本来ならばこれを使った面白いゲームを作ってそれと同時に発表しようと思っていたにょ。
しかし、最大1秒の初期設定は無くすことができるけどスリープが行われたかどうかは秒数が
変わるまでは判断ができないため最大1秒の待ちが発生し、リアルタイム性に欠けるため
これを使ったゲーム(ボタン操作では実現ができないようなゲーム)はなかなか良いものが
思いつかなかったにょ。(下記バージョンアップ履歴のように今回発表した際にMAINCNTLの
誤差問題を簡単に改善する方法を思いついたのが大きい)
しかし、第2回大喜利でスリープ時間を元にしたプログラムが投稿されたし、プチコンまとめ
wikiにもスリープ時間を取得するルーチンやそれを使ったゲームが投稿されたので今更感が
するようなものだけどフレーム単位で取得できるルーチンとしては恐らくこれが一番短いの
ではないかと思われるにょ。(単純にリストの短さだけならば省略できる部分はたくさん
あるけどこれよりも短くなるフレーム単位でスリープ時間を取得可能なアルゴリズムは無いと
思う)


《 バージョンアップ履歴 》

ver.1.0 2012年作成(発表は2013/12/12)
初出版。1年以上前に作ったものそのまんま。
当初はMAINCNTLの誤差問題のため60フレーム以上スリープした時のみ取得するようにしていた。

ver.1.1 2013/12/12
初出版を見直してみたら2フレーム以上であれば取得できるのでそれに対応。(1年前の時点で
分かっていたけど誤差問題を改善する良策が思いつかなかっただけ)
その代わり、誤算問題が大きく露呈することになったのでその改善策を提案。

ver.1.2 2013/12/14
ver.1.1の改善策を最初からリストに盛り込んだもの。これでようやく使えるレベルになった。




掲示板管理者へ連絡 無料レンタル掲示板