質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
CSS3

CSS(Cascading Style Sheet)の第3版です。CSS3と略されることが多いです。色やデザインを柔軟に変更することが可能になります。

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

4回答

2542閲覧

再帰呼び出しを行うと、処理が終わってからでないと画面が表示されない

nekora

総合スコア501

CSS3

CSS(Cascading Style Sheet)の第3版です。CSS3と略されることが多いです。色やデザインを柔軟に変更することが可能になります。

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

5グッド

2クリップ

投稿2022/12/08 04:45

編集2022/12/09 11:06

追記2:int32_t様のコードで、やりたいことが出来ましたので、記録として残しておきます。

JavaScript

1async function sleep(sec) { 2 const startTime = performance.now(); 3 return new Promise(resolve => { 4 const sleepInternal = tick => { 5 if (startTime + sec * 1000 <= tick) { 6 resolve(); 7 } 8 requestAnimationFrame(sleepInternal); 9 }; 10 requestAnimationFrame(sleepInternal); 11 }); 12}

変更点
1. functionの記載がなかったので、最新のJSはこういう構文なのかな?と思い試してみましたがやっぱり必要だったようなので追加
2.繰り返しの再帰呼び出しメイン処理の再帰関数recursive_show_marked()asyncにして、await sleep(2)するようにした
3.再帰関数を最初に呼び出すイベントハンドラもasyncにして、await recursive_show_marked()するようにした
(イベントハンドラがasyncでいいのかと一瞬悩みましたが、これでいいはずと修正)

CODEPENも、修正済みなので、ご興味があればご確認ください。

追記2:ここまで

追記:皆様のアドバイスを参考にsleep関数を作り変えてみました

JavaScript

1 var start; 2 var sleep_flag = false; 3 var id_rAF; 4 function sleep(sec) { 5 // 指定秒数スリープさせる 6 if (sleep_flag == false) { 7 console.log("Sleeping"); 8 start = new Date().getTime(); 9 sleep_flag = true; 10 } 11 12 let progress = new Date().getTime() - start; 13 let wait_time = sec * 1000; 14 if (progress < wait_time) { 15 id_rAF = window.requestAnimationFrame(sleep.bind(null, sec)); 16 } 17 18 id_rAF = window.requestAnimationFrame(sleep.bind(null, sec)); 19 cancelAnimationFrame(id_rAF); 20 console.log("Sleeping Break!!"); 21 sleep_flag = false; 22 return; 23 }

以上の様に、作り変えた所、結果は表示されるにはされるのですが、
別の問題が発生してしまいました

  1. 内部で指定秒数まっているつもりなのですが、wait_time分待ってくれない
  2. 直前のrequestAnimationFrameのIDでcancelAnimationFrameを行っているはずなのに、

 requestAnimationFrameが終了せず、コンソールにsleepのログが出続ける

という、新たな問題に直面しています。CODEPENのコードも直してあります。
ご覧いただけると幸いです。

申し訳ありませんが、再度アドバイスを頂ければ幸いです。

追記ここまで

再帰呼び出しのたびに画面も更新していきたい

現在OJTで、初心者のJavaScriptでの課題を指導していますが、思った動作にならず、又、当方の再帰呼び出しへの理解が浅かったため
有効なアドバイスが出来ず、立場上困ったことになっています。
当方の理解レベルは下記のリンクにあったコード程度の理解で、今までは特に困ったことはありませんでしたが、
今回はより込み入ったコードになっており、画面更新が適宜行われません。
参考サイト:【初心者】JavaScriptでストップウォッチの作り方

申し訳ありませんが、諸先輩方のお知恵をお借りしたく質問させていただきます。

OJTの課題は5×5マスのゲーム盤に上から落としていき、ぷよぷよもどきのゲームを作ってもらっています。
マニュアルモードは完成したのですが、最適解を求めるために再帰呼び出しを使ってコードを書いた所、
途中経過が表示されません、デバッガーでブレイクポイントをはって実行するとちゃんと画面更新されます。

何がいけないのか、どこがダメなのか、どうすれば良いのかのアドバイスをいただけたら幸いですm(_ _)m

実現したいこと

「自動進行機能」をONにしてから最初の駒を落とすと途中経過が出ません。
ブレイクポイントをはると途中経過を確認できます。
ブレイクポイントをはらなくても適宜画面更新されるようにしたい

発生している問題

そのまま実行すると、終了時のみ画面表示される。
途中で、ブレイクポイントをはって実行していくと適宜画面表示される。

再起呼び出しを行っている関数はrecursive_show_marked()です。
sleep関数に入ってすぐにブレイクポイントをはって、止めながら進めると、適宜画面更新されます。

コードが長くて入らなかったので、CODEPENに記載しました
CODEPEN

申し訳ありませんが、諸先輩方のお知恵を拝借出来たら幸いです。

uky👍を押しています
tanat, uky, miyabi-sun, holly❤️を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miyabi_takatsuk

2022/12/08 04:53

検証にて、各箇所でコンソールなどで値を出力してみたりは行っていますか??
yambejp

2022/12/08 04:54

やりたいことは立体五目並べですよね? なにをどうする部分で想定と違うのでしょうか? 何をもって再帰と言っているのかもよくわかりません
nekora

2022/12/08 05:03 編集

1つ駒を置くと、次に駒を置ける候補を取得し、今現在は候補の1番目に駒を置く部分で、駒を置く処理を再帰呼び出ししています。 そして、ブレイクポイントをはって止めながら進めると途中経過が画面に反映されますが、 ブレイクポイントをはらないと、途中経過が出ず、終了時にやっと纏めて表示されるのを何とかしたいと悩んでいます。
yambejp

2022/12/08 05:03 編集

現状5つある「ここに置く」ボタンのどれかを押すと順番に、☓、○順番にその段の下に置かれて、5つ並べば勝ちとなるように見受けられます。この処理の内どこが問題になっているかさっぱり伝わってきません。
miyabi_takatsuk

2022/12/08 05:03

質問者さん、再帰しているメソッド名を提示したほうがいいかと。 また、質問本文にも、ソースコードを記載いただけるので、 本文にも載せた方がいいかと。 (動作も見てもらった方がいいと思うので、CODEPENのリンクはそのままで)
nekora

2022/12/08 05:07

質問が不適切でしたm(_ _)m、「自動進行機能」をONにしてから最初の駒を落とすと途中経過が出ません。 ブレイクポイントをはると途中経過を確認できます。 質問をご指摘通り直しておきます。
nekora

2022/12/08 05:10

質問文にはコードが長すぎて、記載できませんでした。申し訳ありません。
guest

回答4

0

ベストアンサー

ブラウザは原則イベントループに戻らないと画面更新を行いません。重い処理はワーカーでやって適宜メインスレッドに進捗を報告するか、メインスレッドでやる場合は処理を分割しましょう。

このコードの場合は、recursive_show_marked()async にして、sleep(1);await new Promise(resolve => requestAnimationFrame(resolve)); にするといいかもしれません(未確認)。

diff

1 //自動化用サブルーチン 2- function recursive_show_marked(x, y) { 3+ async function recursive_show_marked(x, y) {

diff

1- sleep(1); 2+ await new Promise(resolve => requestAnimationFrame(resolve));

追記:

sleep(秒数) のようなものがどうしても欲しいなら、以下のような実装にして、await sleep(1); のように使います。

js

1// setTimeout() 版 2async sleep(sec) { 3 return new Promise(resolve => setTimeout(resolve, sec * 1000)); 4} 5// rAF 版 6async sleep(sec) { 7 const startTime = performance.now(); 8 return new Promise(resolve => { 9 const sleepInternal = tick => { 10 if (startTime + sec * 1000 <= tick) 11 resolve(); 12 requestAnimationFrame(sleepInternal); 13 }; 14 requestAnimationFrame(sleepInternal); 15 }); 16}

投稿2022/12/08 06:31

編集2022/12/09 01:56
int32_t

総合スコア20884

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nekora

2022/12/09 01:17

返信遅れて申し訳ありません。貴重なご意見ありがとうございます。参考にさせていただきます。
nekora

2022/12/09 10:48

int32_t様、追記いただいたコードで思ってた動きが出来ました。 心より感謝申し上げます。 皆様それぞれに、有意義なご指摘だったのですが、今回はint32_t様の回答をベストアンサーとさせていただきます。 (動いたけど中身を完全に理解してると胸を張って言えないので死ぬ気で勉強して理解したいと思います。ありがとうございました。) 質問文にさらに追記してどうやって動くようにさせていただいたかを残しておきます。
guest

0

なるほど、C言語等でよくあるsleep関数を再現しているんですね。
JavaScriptにはsleep関数はあってはならないから作ってないだけです。
勝手に作らないようにしましょう。

JavaScriptはシングルスレッドなので、
さっさと全ての処理を片付けてイベントループに入らなければなりません。
でないとトリガーが発火して、登録された次の処理の塊(関数)を実行する事が出来ません。

なのでJavaScriptには処理をせき止めて待たせるsleep関数は用意されていません。
代わりにsetTimeoutという○○秒が経過したら、この関数実行してね!というお願いが用意されているのです。
これは処理が全て完了して暇になったらイベントループに入り、指定時間経過していたらトリガーが発火されて関数実行という仕組みを利用しています。

つまり、setTimeoutを使う場合、その場では「はい承知しました」と受け流して下のコードを実行し始めます。

js

1// 最新世代のJSはfunctionではなく、constでアロー関数を定義するのが一般的 2const sleep = seq => new Promise(resolve => 3 setTimeout(resolve, seq * 1000) 4); 5 6// sleepの返り値はPromiseのインスタンスなのでawait構文を使う前提となる 7// await構文を使うにはasync関数を定義する必要がある。 8const main = async () => { 9 console.log("start"); 10 await sleep(1); 11 for (let i = 0; i < 5; i++) { 12 console.log(i); 13 await sleep(i); 14 } 15 console.log("finish"); 16} 17main();

async/awaitはPromiseというJavaScriptの非同期処理の中身の話にはいってくるので
これ覚えるだけで普通に数週間かけるほどには複雑です。
ですが、JavaScriptで大規模なプログラムを作るには必要不可欠なので頑張ってください。


まぁ、直近の問題はsleepの在り方が悪いのが原因ですが、
ゲームを作るならばsleep関数の改修程度では実用には耐えられないでしょう。
だって数秒間動かなくなるとかアホかって話なので

C++等でのリアルタイムのゲームを作る際の手段の一つとして、
1フレーム毎(1/30秒や1/60秒)に場を全て停止させ、
それぞれの値がどうなっているべきか調整を行います。

JavaScript流儀でそれを再現する場合、
Window.requestAnimationFrameを呼ぶという事になります。
これの一番最後の行で再度Window.requestAnimationFrameを呼び、
無限ループっぽいループを生み出しながらも、イベント登録の向こう側なのでブラウザや各種操作の邪魔をしないという仕組みを実現出来ます。

なので、真に生徒さんの事を考えた場合
私だったらWindow.requestAnimationFrameを覚えて使いこなしてみろとアドバイスをすると思います。


追記箇所

JavaScriptに同期処理で待つという機能が存在しない以上、
sleep関数はsetTimeoutをラッピングしてPromiseインスタンスを返すくらいのものしか出来ません。

そしてイベント・非同期処理の機能一度使ってしまったが最後
「二度と」同期処理の世界には戻って来れません。
なのでPromiseを使いまくる必要があります。

そのPromiseのthenメソッドで一生繋ぐのは嫌だから
JavaScriptのES2017というバージョンでasync/awaitというPromiseを使って
コードだけでも同期処理っぽく簡素に書けるようになったわけです。

質問者さん自身は今のうちにPromiseの猛勉強をしてください。
JavaScriptを教える人間がPromiseやイベント駆動教えられないなら、
存在意義が分からなくなるくらい大事な箇所ですからね。

Window.requestAnimationFrame

これ同軸の話じゃねえから。

ゆくゆくのゲームの在り方を考えれば
一生Window.requestAnimationFrameを叩き続ける無限ループを作って
今画面はどうなっているかを逐一書き換えさせるのが最善です。
そういう意味で他の回答者さんも「これじゃゲームの体をなしてないから全部書き直すしかない」って言ってるわけです。

そこをごっちゃにしてsleep関数をWindow.requestAnimationFrameで包んでやろうってのはトンチンカンな事をやっているという事になります。

自作のsleep関数を作るとすれば、
私の回答上部にあるsetTimeoutをラッピングして、
もしかするとPromiseインスタンスで包んで返す関数にしても良いかもね程度のものしかなりえません。

私であっても質問者さんであっても
片手間でこの思想が詰まったコードをぷよぷよのゲームの完成形に差し替えるというのは土台無理な話です。
生徒には「Window.requestAnimationFrameの大きな無限ループの檻を作って、グローバル変数を確認しながら、秒間30回画面を最新の状態に更新し直せ」と伝えた上で、
その間にPromiseを死ぬ気で勉強するのを推奨します。

投稿2022/12/08 07:47

編集2022/12/09 07:50
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dameo

2022/12/08 08:26

質問者さんがゲーム業界と考えると、(ゲーム業界は未経験なので)イメージだけだと新人でもバリバリ独自エンジン書いてそうなイメージですね(しかもJavaScriptでなく…)。
nekora

2022/12/09 02:07 編集

miyabi-sun様、返信遅れて申し訳ありません。今回、色々な方からアドバイスをいただけて大変光栄です、その中でも 既存のコードへの影響が少なそうな、miyabi-sunさんの案で進めてみましたが、別の問題が発生してしまいました。 大変申し訳ありませんが、再びアドバイスを頂けると光栄です。
nekora

2022/12/09 10:42

miyabi-sun様、追記と、ご忠告、大変ありがとうございます。死ぬ気で勉強します><
guest

0

最終的に全面的な書き直しが必要です

一番の問題はsleep(3)です。ビジーループで待っているので、この間に描画もUI操作もできません。これをclickイベントの中でやるのは初心者でもダメです。
ビジーループの代わりにsetTimeout()を使ってください。sleep()setTimeout()Promiseで書き直せばOKです。
Promiseにした後は必然的に全面的な書き直しになると思います。イベントの処理は皆さん言われるように重たい処理は非同期(async/await)で行いましょう。

あと自動実行時のUI制御もちゃんとしてくださいね。


(追記)
具体例がないと分からないかもしれないので、sleep()を例えばこうすべきという例だけ書いておきます。

JavaScript

1const delay = msec => new Promise(resolve => setTimeout(resolve, msec)); 2 3async function sleep(sec) { 4 console.log("Sleeping"); 5 await delay(sec * 1000); 6 console.log("Sleeping Break!!"); 7} 8... 9 await sleep(3); 10...

普通に順に直していったら動くことは動きました。

投稿2022/12/08 07:06

編集2022/12/08 07:44
dameo

総合スコア943

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nekora

2022/12/09 01:21

返信遅れてすみません。ズバリ的中で、Promise、resolve、async/awaitが理解できていません、昨日一晩かけてコードを弄り回してみましたが、かえって悪化させてしまいました。もう少し修行したいと思います。 貴重なアドバイスありがとうございました。
guest

0

ブラウザ上で実行されるJavaScriptは、(ワーカーなど特別な書き方をしたものを除いて)シングルスレッドなので、同期的にJavaScriptを実行し続ける限り画面描画は変化しません。

途中経過を表示したいのであれば、setTimeoutPromiseを使って、処理が非同期となるよう部分ごとに切り分ける必要があります。

投稿2022/12/08 06:03

maisumakun

総合スコア145184

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nekora

2022/12/09 01:15

返信遅くなり申し訳ありません。Promiseは当方古い時代のプログラマーで今は現場をはなれ指導の立場にいますが、Promiseのような新機能については不勉強で、ずばりご慧眼の通り避けて通ってきました。 今回を機に勉強をと、ひたすらググって調べまくってて返信が遅れました。 貴重なご意見ありがとうございます。参考にさせていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問