回答編集履歴

6

テキスト追加

2021/12/05 16:02

投稿

退会済みユーザー
test CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  3. ジェネレータを使う案
10
10
 
11
-    3.1 ジェネレータを使ったコード例 (???? [サンプル](https://codepen.io/kilesa/pen/OJxyrPv?editors=1010) )
11
+    3.1 ジェネレータを使ったコード例 (???? [サンプル](https://codepen.io/kilesa/pen/OJxyrPv/right/) )
12
12
 
13
13
 
14
14
 
@@ -436,7 +436,7 @@
436
436
 
437
437
 
438
438
 
439
- - **サンプル** ???? [tera: 372199 - A Game with generator function](https://codepen.io/kilesa/pen/OJxyrPv?editors=1010)
439
+ - **サンプル** ???? [tera: 372199 - A Game with generator function](https://codepen.io/kilesa/pen/OJxyrPv/right/)
440
440
 
441
441
 
442
442
 

5

テキスト追加

2021/12/05 16:02

投稿

退会済みユーザー
test CHANGED
@@ -1,3 +1,21 @@
1
+ 以下の順に回答します。
2
+
3
+
4
+
5
+ 1. 配列のシャッフルにより、COMの出す数字の配列を作る案
6
+
7
+ 2. ご質問にあるコードと同様のものを実装してみる案
8
+
9
+ 3. ジェネレータを使う案
10
+
11
+    3.1 ジェネレータを使ったコード例 (???? [サンプル](https://codepen.io/kilesa/pen/OJxyrPv?editors=1010) )
12
+
13
+
14
+
15
+ # 1. 配列のシャッフルでCOMの出す数字の配列を作る案
16
+
17
+
18
+
1
19
  COMが1回目から13回目までの各セットに選択する数字の配列を、対戦開始時にあらかじめ作っておく、という方法が考えられます。この配列は1から13までの整数を1個ずつ含み、かつ並び順をランダムにしたもので、たとえば以下のようなものです。
2
20
 
3
21
 
@@ -98,9 +116,7 @@
98
116
 
99
117
 
100
118
 
101
- ### 追記
119
+ # 2. ご質問にあるコードと同様のものを実装してみる案
102
-
103
-
104
120
 
105
121
 
106
122
 
@@ -199,3 +215,247 @@
199
215
  selected_com_numの取り得る値の場合の数が高々13だから
200
216
 
201
217
  というやや曖昧というかざっくりな理由によるものなので、私見としては正直あまりスッキリしないモヤモヤが残るコードという感じがします。
218
+
219
+
220
+
221
+ # 3. ジェネレータを使う案
222
+
223
+
224
+
225
+ 最後に、ジェネレータを使うコードを紹介します。
226
+
227
+
228
+
229
+ ユーザーの対戦相手であるCOM が備えるべき仕組みとして、以下のようなものを考えます。対戦の開始時点で、COM は以下のような配列を内部に持っています。
230
+
231
+
232
+
233
+
234
+
235
+
236
+
237
+ - COM の内部にある配列の初期状態:
238
+
239
+
240
+
241
+ ```
242
+
243
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
244
+
245
+ ```
246
+
247
+
248
+
249
+
250
+
251
+
252
+
253
+ 第1セットに臨むにあたって、COMは上記の配列からランダムにひとつを選びます。例えば、6 が選ばれたとしましょう。これは、COMが内部にもつ配列から 6 が取り出されたことを意味します。
254
+
255
+
256
+
257
+ - 第1セットで選ばれた数: 6
258
+
259
+
260
+
261
+ このとき、COMの内部にある配列から、選ばれた6が削除されるような仕組みをCOMは持っているとします。そうすると、第1セット終了後のCOMの内部にある配列は以下になります。
262
+
263
+
264
+
265
+ - 第1セット終了後のCOM内部の配列:
266
+
267
+
268
+
269
+ ```
270
+
271
+ [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13]
272
+
273
+ ```
274
+
275
+
276
+
277
+
278
+
279
+
280
+
281
+ 第2セットでCOMは上記の第1セット終了時の配列からランダムにひとつを選びます。例えば、11 が選ばれたとしましょう。以下のようになります。
282
+
283
+
284
+
285
+ - 第2セットで選ばれた数: 11
286
+
287
+ - 第2セット終了後のCOM内部の配列:
288
+
289
+
290
+
291
+ ```
292
+
293
+ [1, 2, 3, 4, 5, 7, 8, 9, 10, 12, 13]
294
+
295
+ ```
296
+
297
+
298
+
299
+
300
+
301
+
302
+
303
+ 以降のセットも、COMは内部に持っている配列からひとつの要素をランダムに取り出して、それをユーザーとの対戦に使用します。取り出された要素は配列から削除されるようにすれば、13セット目の終了時には、以下のように空の配列になるはずです。
304
+
305
+
306
+
307
+ - 第13セット終了後のCOM内部の配列:
308
+
309
+
310
+
311
+ ```
312
+
313
+ []
314
+
315
+ ```
316
+
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+
326
+
327
+ このような仕組みを持つCOMが実装されたオブジェクトが、`com` という変数に入るとしましょう。`com `が備えるべき要件は以下です。
328
+
329
+ - `com` は初期状態で、`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]`という配列を内部に持っている。
330
+
331
+ - `com` には、次のセットで出す数字(またはその数字を持つオブジェクト)を得るためのメソッドがある。このメソッド名を仮に `com.next()` とする。
332
+
333
+
334
+
335
+ - `com.next()` が返す数(あるいは、返すオブジェクトがあるプロパティとして持つ数)はCOMが次に出す数とする。これはそれまでに出した数のどれとも重複してはならない。
336
+
337
+ - 上記の重複排除を実現するために `com.next()` するたびに内部の配列から、次に出す数に選ばれたものを削除する。
338
+
339
+ - 初期状態では`com`は1から13までの13個の数を持っているので、`com.next()` で、COMの出す数として有効な数値が返されるのは13回目までで、14回目以降は(対戦に使える有効な)数を返さないように構成されている必要がある。
340
+
341
+
342
+
343
+ このような`next`メソッドを持つ`com` を実装する方法として、何らかのクラスを自作してそれのインスタンスとして`com` を得ることもできるでしょう。けれども、もっと簡単な方法があります。それは[ジェネレータ関数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Iterators_and_Generators#generators)を使って、これの返す値として`com`を得ることです。
344
+
345
+
346
+
347
+ `com` を得るためのジェネレータ関数は以下のようなものになります。
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+ ```javascript
356
+
357
+ // 1以上n以下の整数をランダムな順序で各1回ずつ生成するジェネレータを返す。
358
+
359
+ function* randomSequenceFromOneTo(n) {
360
+
361
+ const numbers = [...Array(n)].map((_, i) => i+1);
362
+
363
+
364
+
365
+ while (numbers.length > 0) {
366
+
367
+ const index = Math.floor(Math.random() * numbers.length);
368
+
369
+ const value = numbers[index];
370
+
371
+ numbers.splice(index, 1);
372
+
373
+ yield value;
374
+
375
+ }
376
+
377
+ }
378
+
379
+ ```
380
+
381
+
382
+
383
+
384
+
385
+
386
+
387
+ 上記のジェネレータ関数`randomSequenceFromOneTo(n)`を使って、一対戦が13セットマッチに対応する`com`を得るには以下のようにします。
388
+
389
+
390
+
391
+
392
+
393
+
394
+
395
+ ```javascript
396
+
397
+ const com = randomSequenceFromOneTo(13);
398
+
399
+ ```
400
+
401
+
402
+
403
+
404
+
405
+
406
+
407
+ こうして得られた `com` を使って、COMが次に出す数を得るには以下のようにします。
408
+
409
+
410
+
411
+ ```javascript
412
+
413
+ const { value, done } = com.next();
414
+
415
+ ```
416
+
417
+
418
+
419
+
420
+
421
+
422
+
423
+ こうすると、変数`value` には次にCOMが出す数が、変数`done`にはtrueかfalseのブール値が入ってきます。`done` が true になっているときは、すでに`com`が内部にもつ配列`numbers`が空になっており、次に出せる数が無いことを意味しています。そのときの`value` はundefined になります。
424
+
425
+
426
+
427
+ `randomSequenceFromOneTo(13)`で作った `com` が内部に持つ数字の配列は、初期状態で長さ13ですが、それが `com.next()`が呼ばれるたびに1ずつ減っていき、第13セットが終わったときには配列は空になります。その後にさらに`com.next()`を呼んでも、返されるオブジェクトとしては`done`がtrueで、`value`の無いものが返ってきます。
428
+
429
+
430
+
431
+ ### 3.1 ジェネレータを使ったコード例
432
+
433
+
434
+
435
+ 以下のリンク先は、先のジェネレータ関数`randomSequenceFromOneTo(n)` を使ってゲームを実装してみた例です。
436
+
437
+
438
+
439
+ - **サンプル** ???? [tera: 372199 - A Game with generator function](https://codepen.io/kilesa/pen/OJxyrPv?editors=1010)
440
+
441
+
442
+
443
+
444
+
445
+ このサンプルでは、
446
+
447
+
448
+
449
+ - 初期表示では、全5セット対戦の第1セットを開始するところから始まります。
450
+
451
+ - 5セット対戦なので、ユーザーもCOMも出せる数は1以上5以下の整数です。
452
+
453
+ - 5セット目が終わると、ボタンが「総合成績を表示」になります。これをクリックすると全5セットの戦績が表示されます。
454
+
455
+ - 総合成績が表示されると、画面最上部は再度の対戦を受けつける状態になります。
456
+
457
+ - 再度の対戦では、対戦セット数を 5, 8, 13 の三通りから選べます。
458
+
459
+
460
+
461
+ このサンプルの主目的はジェネレータ関数の使用例を示すことですので、ゲームを構成するデータ構造だったりロジックだったりは、リファクタリングの余地が多々あると思われます。そのような観点で参考にしていただければと思います。

4

テキスト追加

2021/12/05 10:56

投稿

退会済みユーザー
test CHANGED
@@ -186,7 +186,7 @@
186
186
 
187
187
 
188
188
 
189
- ですが、これはあまりいいやり方ではありません。たとえば、history_com_numが`[2, 8, 4]`だったとして、万が一、 `selected_com_num` が 2 か 8 か 4 のどれかであり続けると、while からbreakしません。それでも実用上問題ないのは、
189
+ ですが、これはあまりいいやり方ではありません。たとえば、history_com_numが`[2, 8, 4]`だったとして、万が一、 `selected_com_num` が 2 か 8 か 4 のどれかであり続けると、while からbreakしません。それでも実用上問題ないのは、
190
190
 
191
191
 
192
192
 
@@ -194,4 +194,8 @@
194
194
 
195
195
 
196
196
 
197
+ という楽観的な見通しがほぼ間違いなく成り立つからです。しかし、実用上問題ないと言える根拠としては
198
+
199
+ selected_com_numの取り得る値の場合の数が高々13だから
200
+
197
- という楽観的な見通しがほぼ間違立つからです。しかしそれはselected_com_numの取り得場合数が高々13だから言える話ありますから、私見としては正直あまりスッキリしないモヤモヤが残るコードという感じがします。
201
+ というやや曖昧とうかざっくりな理由によので、私見としては正直あまりスッキリしないモヤモヤが残るコードという感じがします。

3

テキスト追加

2021/12/04 12:05

投稿

退会済みユーザー
test CHANGED
@@ -116,7 +116,7 @@
116
116
 
117
117
 
118
118
 
119
- と同じ趣旨のコード例を挙げると以下です。
119
+ と同じ趣旨のコード例を挙げると以下です。(`selected_com_num` が `history_com_num`に含まれていない値になるまで、`selected_com_num = getRandomNum(13) + 1` を繰り返す部分のみです。`history_com_num`にpushで追加する部分は割愛しています。)
120
120
 
121
121
 
122
122
 

2

テキスト追加

2021/12/04 11:49

投稿

退会済みユーザー
test CHANGED
@@ -194,4 +194,4 @@
194
194
 
195
195
 
196
196
 
197
- という楽観的な見通しがほぼ間違いなく成り立つからです。しかしそれはselected_com_numの取り得る値の場合の数が高々13だから言える話でありますから、私見としては正直あまりスッキリしないモヤモヤが残るコードという感じがします。
197
+ という楽観的な見通しがほぼ間違いなく成り立つからです。しかしそれはselected_com_numの取り得る値の場合の数が高々13だから言える話でありますから、私見としては正直あまりスッキリしないモヤモヤが残るコードという感じがします。

1

テキスト追加

2021/12/04 05:48

投稿

退会済みユーザー
test CHANGED
@@ -93,3 +93,105 @@
93
93
  console.log(comNumbers); // => .e.g. [6,13,10,2,4,11,7,8,3,9,12,5,1]
94
94
 
95
95
  ```
96
+
97
+
98
+
99
+
100
+
101
+ ### 追記
102
+
103
+
104
+
105
+
106
+
107
+ 質問にある、
108
+
109
+
110
+
111
+ > history_com_numの配列に選択したカードをpushで入れていき、
112
+
113
+ for文で回してrandom関数で選択した数字と配列内の値が一致しない
114
+
115
+ 場合は抜ける、同じ場合は再取得というコード
116
+
117
+
118
+
119
+ と同じ趣旨のコード例を挙げると以下です。
120
+
121
+
122
+
123
+ ```javascript
124
+
125
+ let selected_com_num = getRandomNum(13) + 1;
126
+
127
+
128
+
129
+ while (true) {
130
+
131
+ let index = -1;
132
+
133
+ for (let i=0; i < history_com_num.length; i ++) {
134
+
135
+ if (history_com_num[i] === selected_com_num) {
136
+
137
+ index = i;
138
+
139
+ break;
140
+
141
+ }
142
+
143
+ }
144
+
145
+ if (index === -1) {
146
+
147
+ break;
148
+
149
+ }
150
+
151
+ selected_com_num = getRandomNum(13) + 1;
152
+
153
+ }
154
+
155
+
156
+
157
+ console.log(selected_com_num);
158
+
159
+ ```
160
+
161
+
162
+
163
+ 全体の構造は、`while(true)` という無限ループです。ここから抜けるのは
164
+
165
+ ```javascript
166
+
167
+ if (index === -1) {
168
+
169
+ break;
170
+
171
+ }
172
+
173
+ ```
174
+
175
+ という `break`です。ここで`break`されるのは、`index`が-1のときですが、`index`が-1のときというのは、selected_com_num が、配列history_com_numに含まれていないときです。含まれているときは、forループによって、一致したhistory_com_numの要素のインデクス`i`が `index` に代入されますが、それは0以上の整数です。
176
+
177
+ index が 0以上だった場合は、whileループ本体の最後で、再び
178
+
179
+ ```javascript
180
+
181
+ selected_com_num = getRandomNum(13) + 1;
182
+
183
+ ```
184
+
185
+ を実行して、selected_com_num を取り直します。
186
+
187
+
188
+
189
+ ですが、これはあまりいいやり方ではありません。たとえば、history_com_numが`[2, 8, 4]`だったとして、万が一、 `selected_com_num` が 2 か 8 か 4 のどれかであり続けると、while からbreakしません。それでも実用上は問題ないのは、
190
+
191
+
192
+
193
+ そう多くない試行回数で、selected_com_num は、history_com_num に含まれない数になるはず
194
+
195
+
196
+
197
+ という楽観的な見通しがほぼ間違いなく成り立つからです。しかしそれはselected_com_numの取り得る値の場合の数が高々13だから言える話でありますから、度私見としては正直あまりスッキリしないモヤモヤが残るコードという感じがします。