回答編集履歴
6
テキスト追加
answer
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
1. 配列のシャッフルにより、COMの出す数字の配列を作る案
|
4
4
|
2. ご質問にあるコードと同様のものを実装してみる案
|
5
5
|
3. ジェネレータを使う案
|
6
|
-
3.1 ジェネレータを使ったコード例 (???? [サンプル](https://codepen.io/kilesa/pen/OJxyrPv
|
6
|
+
3.1 ジェネレータを使ったコード例 (???? [サンプル](https://codepen.io/kilesa/pen/OJxyrPv/right/) )
|
7
7
|
|
8
8
|
# 1. 配列のシャッフルでCOMの出す数字の配列を作る案
|
9
9
|
|
@@ -217,7 +217,7 @@
|
|
217
217
|
|
218
218
|
以下のリンク先は、先のジェネレータ関数`randomSequenceFromOneTo(n)` を使ってゲームを実装してみた例です。
|
219
219
|
|
220
|
-
- **サンプル** ???? [tera: 372199 - A Game with generator function](https://codepen.io/kilesa/pen/OJxyrPv
|
220
|
+
- **サンプル** ???? [tera: 372199 - A Game with generator function](https://codepen.io/kilesa/pen/OJxyrPv/right/)
|
221
221
|
|
222
222
|
|
223
223
|
このサンプルでは、
|
5
テキスト追加
answer
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
以下の順に回答します。
|
2
|
+
|
3
|
+
1. 配列のシャッフルにより、COMの出す数字の配列を作る案
|
4
|
+
2. ご質問にあるコードと同様のものを実装してみる案
|
5
|
+
3. ジェネレータを使う案
|
6
|
+
3.1 ジェネレータを使ったコード例 (???? [サンプル](https://codepen.io/kilesa/pen/OJxyrPv?editors=1010) )
|
7
|
+
|
8
|
+
# 1. 配列のシャッフルでCOMの出す数字の配列を作る案
|
9
|
+
|
1
10
|
COMが1回目から13回目までの各セットに選択する数字の配列を、対戦開始時にあらかじめ作っておく、という方法が考えられます。この配列は1から13までの整数を1個ずつ含み、かつ並び順をランダムにしたもので、たとえば以下のようなものです。
|
2
11
|
|
3
12
|
```
|
@@ -48,9 +57,8 @@
|
|
48
57
|
```
|
49
58
|
|
50
59
|
|
51
|
-
#
|
60
|
+
# 2. ご質問にあるコードと同様のものを実装してみる案
|
52
61
|
|
53
|
-
|
54
62
|
質問にある、
|
55
63
|
|
56
64
|
> history_com_numの配列に選択したカードをpushで入れていき、
|
@@ -98,4 +106,126 @@
|
|
98
106
|
|
99
107
|
という楽観的な見通しがほぼ間違いなく成り立つからです。しかし、実用上問題ないと言える根拠としては
|
100
108
|
selected_com_numの取り得る値の場合の数が高々13だから
|
101
|
-
というやや曖昧というかざっくりな理由によるものなので、私見としては正直あまりスッキリしないモヤモヤが残るコードという感じがします。
|
109
|
+
というやや曖昧というかざっくりな理由によるものなので、私見としては正直あまりスッキリしないモヤモヤが残るコードという感じがします。
|
110
|
+
|
111
|
+
# 3. ジェネレータを使う案
|
112
|
+
|
113
|
+
最後に、ジェネレータを使うコードを紹介します。
|
114
|
+
|
115
|
+
ユーザーの対戦相手であるCOM が備えるべき仕組みとして、以下のようなものを考えます。対戦の開始時点で、COM は以下のような配列を内部に持っています。
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
- COM の内部にある配列の初期状態:
|
120
|
+
|
121
|
+
```
|
122
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
|
123
|
+
```
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
第1セットに臨むにあたって、COMは上記の配列からランダムにひとつを選びます。例えば、6 が選ばれたとしましょう。これは、COMが内部にもつ配列から 6 が取り出されたことを意味します。
|
128
|
+
|
129
|
+
- 第1セットで選ばれた数: 6
|
130
|
+
|
131
|
+
このとき、COMの内部にある配列から、選ばれた6が削除されるような仕組みをCOMは持っているとします。そうすると、第1セット終了後のCOMの内部にある配列は以下になります。
|
132
|
+
|
133
|
+
- 第1セット終了後のCOM内部の配列:
|
134
|
+
|
135
|
+
```
|
136
|
+
[1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13]
|
137
|
+
```
|
138
|
+
|
139
|
+
|
140
|
+
|
141
|
+
第2セットでCOMは上記の第1セット終了時の配列からランダムにひとつを選びます。例えば、11 が選ばれたとしましょう。以下のようになります。
|
142
|
+
|
143
|
+
- 第2セットで選ばれた数: 11
|
144
|
+
- 第2セット終了後のCOM内部の配列:
|
145
|
+
|
146
|
+
```
|
147
|
+
[1, 2, 3, 4, 5, 7, 8, 9, 10, 12, 13]
|
148
|
+
```
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
以降のセットも、COMは内部に持っている配列からひとつの要素をランダムに取り出して、それをユーザーとの対戦に使用します。取り出された要素は配列から削除されるようにすれば、13セット目の終了時には、以下のように空の配列になるはずです。
|
153
|
+
|
154
|
+
- 第13セット終了後のCOM内部の配列:
|
155
|
+
|
156
|
+
```
|
157
|
+
[]
|
158
|
+
```
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
|
163
|
+
|
164
|
+
このような仕組みを持つCOMが実装されたオブジェクトが、`com` という変数に入るとしましょう。`com `が備えるべき要件は以下です。
|
165
|
+
- `com` は初期状態で、`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]`という配列を内部に持っている。
|
166
|
+
- `com` には、次のセットで出す数字(またはその数字を持つオブジェクト)を得るためのメソッドがある。このメソッド名を仮に `com.next()` とする。
|
167
|
+
|
168
|
+
- `com.next()` が返す数(あるいは、返すオブジェクトがあるプロパティとして持つ数)はCOMが次に出す数とする。これはそれまでに出した数のどれとも重複してはならない。
|
169
|
+
- 上記の重複排除を実現するために `com.next()` するたびに内部の配列から、次に出す数に選ばれたものを削除する。
|
170
|
+
- 初期状態では`com`は1から13までの13個の数を持っているので、`com.next()` で、COMの出す数として有効な数値が返されるのは13回目までで、14回目以降は(対戦に使える有効な)数を返さないように構成されている必要がある。
|
171
|
+
|
172
|
+
このような`next`メソッドを持つ`com` を実装する方法として、何らかのクラスを自作してそれのインスタンスとして`com` を得ることもできるでしょう。けれども、もっと簡単な方法があります。それは[ジェネレータ関数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Iterators_and_Generators#generators)を使って、これの返す値として`com`を得ることです。
|
173
|
+
|
174
|
+
`com` を得るためのジェネレータ関数は以下のようなものになります。
|
175
|
+
|
176
|
+
|
177
|
+
|
178
|
+
```javascript
|
179
|
+
// 1以上n以下の整数をランダムな順序で各1回ずつ生成するジェネレータを返す。
|
180
|
+
function* randomSequenceFromOneTo(n) {
|
181
|
+
const numbers = [...Array(n)].map((_, i) => i+1);
|
182
|
+
|
183
|
+
while (numbers.length > 0) {
|
184
|
+
const index = Math.floor(Math.random() * numbers.length);
|
185
|
+
const value = numbers[index];
|
186
|
+
numbers.splice(index, 1);
|
187
|
+
yield value;
|
188
|
+
}
|
189
|
+
}
|
190
|
+
```
|
191
|
+
|
192
|
+
|
193
|
+
|
194
|
+
上記のジェネレータ関数`randomSequenceFromOneTo(n)`を使って、一対戦が13セットマッチに対応する`com`を得るには以下のようにします。
|
195
|
+
|
196
|
+
|
197
|
+
|
198
|
+
```javascript
|
199
|
+
const com = randomSequenceFromOneTo(13);
|
200
|
+
```
|
201
|
+
|
202
|
+
|
203
|
+
|
204
|
+
こうして得られた `com` を使って、COMが次に出す数を得るには以下のようにします。
|
205
|
+
|
206
|
+
```javascript
|
207
|
+
const { value, done } = com.next();
|
208
|
+
```
|
209
|
+
|
210
|
+
|
211
|
+
|
212
|
+
こうすると、変数`value` には次にCOMが出す数が、変数`done`にはtrueかfalseのブール値が入ってきます。`done` が true になっているときは、すでに`com`が内部にもつ配列`numbers`が空になっており、次に出せる数が無いことを意味しています。そのときの`value` はundefined になります。
|
213
|
+
|
214
|
+
`randomSequenceFromOneTo(13)`で作った `com` が内部に持つ数字の配列は、初期状態で長さ13ですが、それが `com.next()`が呼ばれるたびに1ずつ減っていき、第13セットが終わったときには配列は空になります。その後にさらに`com.next()`を呼んでも、返されるオブジェクトとしては`done`がtrueで、`value`の無いものが返ってきます。
|
215
|
+
|
216
|
+
### 3.1 ジェネレータを使ったコード例
|
217
|
+
|
218
|
+
以下のリンク先は、先のジェネレータ関数`randomSequenceFromOneTo(n)` を使ってゲームを実装してみた例です。
|
219
|
+
|
220
|
+
- **サンプル** ???? [tera: 372199 - A Game with generator function](https://codepen.io/kilesa/pen/OJxyrPv?editors=1010)
|
221
|
+
|
222
|
+
|
223
|
+
このサンプルでは、
|
224
|
+
|
225
|
+
- 初期表示では、全5セット対戦の第1セットを開始するところから始まります。
|
226
|
+
- 5セット対戦なので、ユーザーもCOMも出せる数は1以上5以下の整数です。
|
227
|
+
- 5セット目が終わると、ボタンが「総合成績を表示」になります。これをクリックすると全5セットの戦績が表示されます。
|
228
|
+
- 総合成績が表示されると、画面最上部は再度の対戦を受けつける状態になります。
|
229
|
+
- 再度の対戦では、対戦セット数を 5, 8, 13 の三通りから選べます。
|
230
|
+
|
231
|
+
このサンプルの主目的はジェネレータ関数の使用例を示すことですので、ゲームを構成するデータ構造だったりロジックだったりは、リファクタリングの余地が多々あると思われます。そのような観点で参考にしていただければと思います。
|
4
テキスト追加
answer
CHANGED
@@ -92,8 +92,10 @@
|
|
92
92
|
```
|
93
93
|
を実行して、selected_com_num を取り直します。
|
94
94
|
|
95
|
-
ですが、これはあまりいいやり方ではありません。たとえば、history_com_numが`[2, 8, 4]`だったとして、万が一、 `selected_com_num` が 2 か 8 か 4 のどれかであり続けると、while からbreakしません。それでも実用上
|
95
|
+
ですが、これはあまりいいやり方ではありません。たとえば、history_com_numが`[2, 8, 4]`だったとして、万が一、 `selected_com_num` が 2 か 8 か 4 のどれかであり続けると、while からbreakしません。それでも実用上問題ないのは、
|
96
96
|
|
97
97
|
そう多くない試行回数で、selected_com_num は、history_com_num に含まれない数になるはず
|
98
98
|
|
99
|
+
という楽観的な見通しがほぼ間違いなく成り立つからです。しかし、実用上問題ないと言える根拠としては
|
100
|
+
selected_com_numの取り得る値の場合の数が高々13だから
|
99
|
-
という
|
101
|
+
というやや曖昧というかざっくりな理由によるものなので、私見としては正直あまりスッキリしないモヤモヤが残るコードという感じがします。
|
3
テキスト追加
answer
CHANGED
@@ -57,7 +57,7 @@
|
|
57
57
|
for文で回してrandom関数で選択した数字と配列内の値が一致しない
|
58
58
|
場合は抜ける、同じ場合は再取得というコード
|
59
59
|
|
60
|
-
と同じ趣旨のコード例を挙げると以下です。
|
60
|
+
と同じ趣旨のコード例を挙げると以下です。(`selected_com_num` が `history_com_num`に含まれていない値になるまで、`selected_com_num = getRandomNum(13) + 1` を繰り返す部分のみです。`history_com_num`にpushで追加する部分は割愛しています。)
|
61
61
|
|
62
62
|
```javascript
|
63
63
|
let selected_com_num = getRandomNum(13) + 1;
|
2
テキスト追加
answer
CHANGED
@@ -96,4 +96,4 @@
|
|
96
96
|
|
97
97
|
そう多くない試行回数で、selected_com_num は、history_com_num に含まれない数になるはず
|
98
98
|
|
99
|
-
という楽観的な見通しがほぼ間違いなく成り立つからです。しかしそれはselected_com_numの取り得る値の場合の数が高々13だから言える話でありますから、
|
99
|
+
という楽観的な見通しがほぼ間違いなく成り立つからです。しかしそれはselected_com_numの取り得る値の場合の数が高々13だから言える話でありますから、私見としては正直あまりスッキリしないモヤモヤが残るコードという感じがします。
|
1
テキスト追加
answer
CHANGED
@@ -45,4 +45,55 @@
|
|
45
45
|
```javascript
|
46
46
|
const comNumbers = shuffle([...Array(13)].map((_, i) => i+1));
|
47
47
|
console.log(comNumbers); // => .e.g. [6,13,10,2,4,11,7,8,3,9,12,5,1]
|
48
|
-
```
|
48
|
+
```
|
49
|
+
|
50
|
+
|
51
|
+
### 追記
|
52
|
+
|
53
|
+
|
54
|
+
質問にある、
|
55
|
+
|
56
|
+
> history_com_numの配列に選択したカードをpushで入れていき、
|
57
|
+
for文で回してrandom関数で選択した数字と配列内の値が一致しない
|
58
|
+
場合は抜ける、同じ場合は再取得というコード
|
59
|
+
|
60
|
+
と同じ趣旨のコード例を挙げると以下です。
|
61
|
+
|
62
|
+
```javascript
|
63
|
+
let selected_com_num = getRandomNum(13) + 1;
|
64
|
+
|
65
|
+
while (true) {
|
66
|
+
let index = -1;
|
67
|
+
for (let i=0; i < history_com_num.length; i ++) {
|
68
|
+
if (history_com_num[i] === selected_com_num) {
|
69
|
+
index = i;
|
70
|
+
break;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
if (index === -1) {
|
74
|
+
break;
|
75
|
+
}
|
76
|
+
selected_com_num = getRandomNum(13) + 1;
|
77
|
+
}
|
78
|
+
|
79
|
+
console.log(selected_com_num);
|
80
|
+
```
|
81
|
+
|
82
|
+
全体の構造は、`while(true)` という無限ループです。ここから抜けるのは
|
83
|
+
```javascript
|
84
|
+
if (index === -1) {
|
85
|
+
break;
|
86
|
+
}
|
87
|
+
```
|
88
|
+
という `break`です。ここで`break`されるのは、`index`が-1のときですが、`index`が-1のときというのは、selected_com_num が、配列history_com_numに含まれていないときです。含まれているときは、forループによって、一致したhistory_com_numの要素のインデクス`i`が `index` に代入されますが、それは0以上の整数です。
|
89
|
+
index が 0以上だった場合は、whileループ本体の最後で、再び
|
90
|
+
```javascript
|
91
|
+
selected_com_num = getRandomNum(13) + 1;
|
92
|
+
```
|
93
|
+
を実行して、selected_com_num を取り直します。
|
94
|
+
|
95
|
+
ですが、これはあまりいいやり方ではありません。たとえば、history_com_numが`[2, 8, 4]`だったとして、万が一、 `selected_com_num` が 2 か 8 か 4 のどれかであり続けると、while からbreakしません。それでも実用上は問題ないのは、
|
96
|
+
|
97
|
+
そう多くない試行回数で、selected_com_num は、history_com_num に含まれない数になるはず
|
98
|
+
|
99
|
+
という楽観的な見通しがほぼ間違いなく成り立つからです。しかしそれはselected_com_numの取り得る値の場合の数が高々13だから言える話でありますから、度私見としては正直あまりスッキリしないモヤモヤが残るコードという感じがします。
|