jeopardy 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3f2e4ec7a88258a44bd43c227c69e7fea652b1ad
4
+ data.tar.gz: 687ee8d39fcc775e55d3f373ed5fbb1f31fe69d4
5
+ SHA512:
6
+ metadata.gz: e09c4d840e98e26241e9a759c893a50310285115de97b17dc57d7bd07105e2c2bf578684fabaab58838d3a93358d90b8f7b716f6f84645c3e6366e45e501c9f0
7
+ data.tar.gz: d4b0a0d3aa98c4310a3aed295373ac629ba1bf2b4115669af8e6b46336c1c54c15a7898a8863a21180df1b726e40661c37f9c9220f249a4e684212034e4f1736
@@ -0,0 +1,99 @@
1
+ //Clue.c
2
+ #include "Clue.h"
3
+ #include <string.h>
4
+ #include <stdlib.h>
5
+ #include <math.h>
6
+ #include "ruby.h"
7
+ #include "MinMax.h"
8
+
9
+ double sampleNormal()
10
+ {
11
+ double u, v, r;
12
+ do
13
+ {
14
+ u = drand() * 2.0 - 1.0;
15
+ v = drand() * 2.0 - 1.0;
16
+ r = u * u + v * v;
17
+ } while (r == 0.0 || r > 1.0);
18
+ double c = sqrt(-2.0 * log(r) / r);
19
+ return u * c;
20
+ }
21
+
22
+ Clue clueMake(int value, int round, int row, int column, int isDailyDouble)
23
+ {
24
+ Clue clue;
25
+ clue.value = value;
26
+ clue.round = round;
27
+ clue.row = row;
28
+ clue.column = column;
29
+ clue.isDailyDouble = isDailyDouble;
30
+ clueReset(&clue);
31
+ return clue;
32
+ }
33
+
34
+ void clueReset(Clue *clue)
35
+ {
36
+ for (int j = 0; j<3; j++)
37
+ {
38
+ clue->answers[j] = NO_ANSWER;
39
+ clue->wagers[j] = NO_WAGER;
40
+ }
41
+ if (clue->round == 3)
42
+ {
43
+ clue->finalJeopardyStandardDeviations = sampleNormal();
44
+ }
45
+ }
46
+
47
+ int previousAnswerers(Clue *clue)
48
+ {
49
+ return (clue->answers[0] > -1) + (clue->answers[1] > -1) + (clue->answers[2] > -1);
50
+ }
51
+
52
+ double rawOddsOfAnsweringClue(Clue *clue)
53
+ {
54
+ const double odds[2][3][5] = {
55
+ {
56
+ {0.9316826244, 0.9020692974, 0.8808619551, 0.8568982492, 0.8262623452},
57
+ {0.9344023324, 0.9148061105, 0.8989547038, 0.8820861678, 0.8332403793},
58
+ {0.9861111111, 0.9428571429, 0.9482758621, 0.9449541284, 0.9761904762}
59
+ },
60
+ {
61
+ {0.9191489362, 0.8851981352, 0.846296456, 0.8150716176, 0.7819324269},
62
+ {0.9168356998, 0.8936294565, 0.8767288034, 0.8435064935, 0.8086092715},
63
+ {0.972972973, 0.9652173913, 0.9595959596, 0.9368421053, 0.9444444444}
64
+ }
65
+ };
66
+ return odds[clue->round - 1][previousAnswerers(clue)][clue->row];
67
+ }
68
+
69
+ double rawOddsOfAnsweringDailyDouble(Clue *clue)
70
+ {
71
+ const double odds[2][5] = {
72
+ {0.76, 0.7364864865, 0.7255985267, 0.6594800254, 0.6321167883},
73
+ {0.75, 0.7361647362, 0.6603773585, 0.6360884045, 0.5720502901}
74
+ };
75
+ return odds[clue->round - 1][clue->row];
76
+ }
77
+
78
+ double rawOddsOfRingingIn(Clue *clue)
79
+ {
80
+ const double odds[2][3][5] = {
81
+ {
82
+ {0.726542166, 0.6600194005, 0.6006297272, 0.5522971451, 0.4693421163},
83
+ {0.5396228103, 0.4495181174, 0.3649201599, 0.3127371374, 0.2778616104},
84
+ {0.8, 0.724137931, 0.6666666667, 0.5240384615, 0.2809364548}
85
+ },
86
+ {
87
+ {0.6867450161, 0.6042841339, 0.5466591934, 0.4895921923, 0.3971898935},
88
+ {0.4845967439, 0.3838645318, 0.2695752355, 0.2125177663, 0.1979007232},
89
+ {0.6016260163, 0.6318681319, 0.4829268293, 0.3941908714, 0.3114186851}
90
+ }
91
+ };
92
+ return odds[clue->round - 1][previousAnswerers(clue)][clue->row];
93
+ }
94
+
95
+ double rawOddsOfAnsweringFinalJeopardy(Clue *finalJeopardy)
96
+ {
97
+ return finalJeopardy->finalJeopardyStandardDeviations*0.22 + 0.4924242424;
98
+ }
99
+
@@ -0,0 +1,35 @@
1
+ //Clue.h
2
+ #include <limits.h>
3
+
4
+ #ifndef CLUE_H
5
+ #define CLUE_H
6
+
7
+ typedef enum
8
+ {
9
+ NO_WAGER = INT_MIN,
10
+ NO_ANSWER,
11
+ CORRECT_ANSWER,
12
+ INCORRECT_ANSWER
13
+ } ResponseType;
14
+
15
+ typedef struct
16
+ {
17
+ int value;
18
+ int round;
19
+ int row;
20
+ int column;
21
+ int isDailyDouble;
22
+ double finalJeopardyStandardDeviations;
23
+ int answers[3];
24
+ int wagers[3];
25
+ } Clue;
26
+
27
+ Clue clueMake(int value, int round, int row, int column, int isDailyDouble);
28
+
29
+ double rawOddsOfAnsweringClue(Clue *clue);
30
+ double rawOddsOfAnsweringDailyDouble(Clue *clue);
31
+ double rawOddsOfRingingIn(Clue *clue);
32
+ double rawOddsOfAnsweringFinalJeopardy(Clue *finalJeopardy);
33
+ void clueReset(Clue *clue);
34
+ double randomFJPercentage();
35
+ #endif
@@ -0,0 +1,413 @@
1
+ //Game.c
2
+
3
+ #include "Game.h"
4
+ #include <math.h>
5
+ #include <string.h>
6
+ #include <stdlib.h>
7
+ #include "MinMax.h"
8
+ #include <stdio.h>
9
+
10
+ Game gameMake(Player *players, int numJeopardyClues, Clue *jeopardyClues, int numDoubleJeopardyClues, Clue *doubleJeopardyClues, Clue finalJeopardyClue, int playerInControlIndex)
11
+ {
12
+ Game game;
13
+ memcpy(game.players, players, sizeof(game.players));
14
+ game.numJeopardyClues = numJeopardyClues;
15
+ game.jeopardyClues = malloc(sizeof(Clue)*numJeopardyClues);
16
+ memcpy(game.jeopardyClues, jeopardyClues, sizeof(Clue)*numJeopardyClues);
17
+ game.doubleJeopardyClues = malloc(sizeof(Clue)*numDoubleJeopardyClues);
18
+ memcpy(game.doubleJeopardyClues, doubleJeopardyClues, sizeof(Clue)*numDoubleJeopardyClues);
19
+ game.numDoubleJeopardyClues = numDoubleJeopardyClues;
20
+ game.finalJeopardyClue = finalJeopardyClue;
21
+ game.playerInControlIndex = playerInControlIndex;
22
+ return game;
23
+ }
24
+
25
+ void gameFree(Game game)
26
+ {
27
+ free(game.jeopardyClues);
28
+ free(game.doubleJeopardyClues);
29
+ }
30
+
31
+ void resetDailyDoubles(Game *game)
32
+ {
33
+ int numJeopardyDailyDoubles = 0;
34
+ for (int i = 0; i<game->numJeopardyClues; i++)
35
+ {
36
+ numJeopardyDailyDoubles += game->jeopardyClues[i].isDailyDouble;
37
+ }
38
+
39
+ int numDoubleJeopardyDailyDoubles = 0;
40
+ for (int i = 0; i<game->numDoubleJeopardyClues; i++)
41
+ {
42
+ numDoubleJeopardyDailyDoubles += game->doubleJeopardyClues[i].isDailyDouble;
43
+ }
44
+
45
+ for (int i = 0; i<game->numJeopardyClues; i++)
46
+ {
47
+ game->jeopardyClues[i].isDailyDouble = 0;
48
+ }
49
+
50
+ for (int i = 0; i<game->numDoubleJeopardyClues; i++)
51
+ {
52
+ game->doubleJeopardyClues[i].isDailyDouble = 0;
53
+ }
54
+
55
+ if (numJeopardyDailyDoubles)
56
+ {
57
+ addDailyDouble(game, 1);
58
+ }
59
+
60
+ for (int i = 0; i<numDoubleJeopardyDailyDoubles; i++)
61
+ {
62
+ addDailyDouble(game, 2);
63
+ }
64
+ }
65
+
66
+ void gameReset(Game *game, int *scores)
67
+ {
68
+ for (int i = 0; i<3; i++)
69
+ {
70
+ game->players[i].score = scores[i];
71
+ }
72
+
73
+ for (int i = 0; i<game->numJeopardyClues; i++)
74
+ {
75
+ Clue *clue = &game->jeopardyClues[i];
76
+ clueReset(clue);
77
+ }
78
+
79
+ for (int i = 0; i<game->numDoubleJeopardyClues; i++)
80
+ {
81
+ Clue *clue = &game->doubleJeopardyClues[i];
82
+ clueReset(clue);
83
+ }
84
+
85
+ shuffleClues(game->jeopardyClues, game->numJeopardyClues);
86
+ shuffleClues(game->doubleJeopardyClues, game->numDoubleJeopardyClues);
87
+
88
+ clueReset(&game->finalJeopardyClue);
89
+ game->playerInControlIndex = 0;
90
+
91
+ resetDailyDoubles(game);
92
+
93
+ }
94
+
95
+ Game averageGame()
96
+ {
97
+ Player players[3] = {averagePlayer(), averagePlayer(), averagePlayer()};
98
+ int numClues = 30;
99
+ Clue jClues[numClues];
100
+ Clue djClues[numClues];
101
+ for (int i = 0; i<numClues; i++)
102
+ {
103
+ int value = (i%5+1)*200;
104
+ jClues[i] = clueMake(value, 1, i%5, i/5, 0);
105
+ djClues[i] = clueMake(value*2, 2, i%5, i/5, 0);
106
+ }
107
+ Clue fjClue = clueMake(0, 3, 0, 0, 0);
108
+ Game game = gameMake(players, numClues, jClues, numClues, djClues, fjClue, 0);
109
+ game.previousDailyDoubleColumn = -1;
110
+ addDailyDouble(&game, 1);
111
+ addDailyDouble(&game, 2);
112
+ addDailyDouble(&game, 2);
113
+ return game;
114
+ }
115
+
116
+ void addDailyDouble(Game *game, int round)
117
+ {
118
+ int clueCounts[5] = {0, 0, 0, 0, 0};
119
+ double odds[5];
120
+ Clue *clues = NULL;
121
+ int numClues = 0;
122
+ if (round == 1)
123
+ {
124
+ clues = game->jeopardyClues;
125
+ numClues = game->numJeopardyClues;
126
+ double denom = 4300.0;
127
+ odds[0] = 1.0/denom;
128
+ odds[1] = 296.0/denom;
129
+ odds[2] = 1086.0/denom;
130
+ odds[3] = 1577.0/denom;
131
+ odds[4] = 1370.0/denom;
132
+ }
133
+ else if (round == 2)
134
+ {
135
+ clues = game->doubleJeopardyClues;
136
+ numClues = game->numDoubleJeopardyClues;
137
+ double denom = 8598.0;
138
+ odds[0] = 12.0/denom;
139
+ odds[1] = 777.0/denom;
140
+ odds[2] = 2438.0/denom;
141
+ odds[3] = 3303.0/denom;
142
+ odds[4] = 2068.0/denom;
143
+ }
144
+
145
+ for (int i = 0; i<numClues; i++)
146
+ {
147
+ if (round == 1 || clues[i].column != game->previousDailyDoubleColumn)
148
+ {
149
+ clueCounts[clues[i].row]++;
150
+ }
151
+ }
152
+
153
+ double weightedOdds[5] = {0, 0, 0, 0, 0};
154
+ double sumWeightedOdds = 0.0;
155
+ for (int i = 0; i<5; i++)
156
+ {
157
+ weightedOdds[i] = clueCounts[i] * odds[i];
158
+ sumWeightedOdds += weightedOdds[i];
159
+ }
160
+
161
+ for (int i = 0; i<5; i++)
162
+ {
163
+ weightedOdds[i] /= sumWeightedOdds;
164
+ }
165
+
166
+ double dailyDoubleRandomizer = drand();
167
+ double totalOdds = 0.0;
168
+
169
+
170
+
171
+ int rowToInsertDailyDouble = -1;
172
+ for (int i = 0; i<5; i++)
173
+ {
174
+ totalOdds += weightedOdds[i];
175
+ if (totalOdds >= dailyDoubleRandomizer)
176
+ {
177
+ rowToInsertDailyDouble = i;
178
+ break;
179
+ }
180
+ }
181
+
182
+ for (int i = 0; i<numClues; i++)
183
+ {
184
+ Clue *clue = &clues[i];
185
+ if (clue->row == rowToInsertDailyDouble && !clue->isDailyDouble)
186
+ {
187
+ clue->isDailyDouble = 1;
188
+ return;
189
+ }
190
+ }
191
+ }
192
+
193
+ void shuffleClues(Clue *clues, int numClues)
194
+ {
195
+ for (int i = numClues; i>0; i--)
196
+ {
197
+ Clue lastClue = clues[i-1];
198
+ int randomIndex = rand()%i;
199
+ Clue randomClue = clues[randomIndex];
200
+ clues[i-1] = randomClue;
201
+ clues[randomIndex] = lastClue;
202
+ }
203
+ }
204
+
205
+ int winningScore(Game *game)
206
+ {
207
+ return MAX(game->players[0].score, MAX(game->players[1].score, game->players[2].score));
208
+ }
209
+
210
+ int winningPlayerIndex(Game *game)
211
+ {
212
+ double numWinners = 0;
213
+ int topScore = winningScore(game);
214
+
215
+ if (topScore <= 0)
216
+ {
217
+ return -1;
218
+ }
219
+
220
+ for (int i = 0; i<3; i++)
221
+ {
222
+ if (game->players[i].score == topScore)
223
+ {
224
+ numWinners += 1.0;
225
+ }
226
+ }
227
+
228
+ double randomIncrement = 1.0/numWinners;
229
+ double currentValue = 0.0;
230
+ double randomValue = drand();
231
+
232
+ for (int i = 0; i<3; i++)
233
+ {
234
+ if (game->players[i].score == topScore)
235
+ {
236
+ currentValue += randomIncrement;
237
+ }
238
+
239
+ if (currentValue >= randomValue)
240
+ {
241
+ return i;
242
+ }
243
+ }
244
+
245
+ return -1;
246
+ }
247
+
248
+ //returns the players index who rang in, or -1 if no one does
249
+ int indexOfPlayerWhoRangInFirst(Game *game, Clue *clue)
250
+ {
251
+ int attemptsToRingIn[3];
252
+ double sumBuzzerRatings = 0.0;
253
+ for (int i = 0; i<3; i++)
254
+ {
255
+ // Someone has already answered correctly
256
+ if (clue->answers[i] == CORRECT_ANSWER)
257
+ {
258
+ return -1;
259
+ }
260
+
261
+ Player *player = &game->players[i];
262
+ attemptsToRingIn[i] = clue->answers[i] == NO_ANSWER && playerAttemptedToRingIn(player, clue); //only attempt to ring in if you haven't already given an answer
263
+ if (attemptsToRingIn[i])
264
+ {
265
+ sumBuzzerRatings += player->buzzerRating;
266
+ }
267
+ }
268
+
269
+ double ringInRandomizer = drand();
270
+ double oddsOfRingingIn = 0.0;
271
+ for (int i = 0; i<3; i++)
272
+ {
273
+ Player *player = &game->players[i];
274
+ oddsOfRingingIn += (double)attemptsToRingIn[i] * player->buzzerRating / sumBuzzerRatings;
275
+ if (oddsOfRingingIn >= ringInRandomizer || (sumBuzzerRatings == 0.0 && attemptsToRingIn[i])) //check to make sure that a lone player with a rating of 0.0 will ring in
276
+ {
277
+ return i;
278
+ }
279
+ }
280
+
281
+ return -1;
282
+ }
283
+
284
+ int moneyLeft(Clue *clues, int numClues)
285
+ {
286
+ int money = 0;
287
+ for (int i = 0; i<numClues; i++)
288
+ {
289
+ money += clues[i].value;
290
+ }
291
+
292
+ return money;
293
+ }
294
+
295
+ void simulateClue(Game *game, Clue *clue)
296
+ {
297
+ int indexOfPlayerWhoRangIn = indexOfPlayerWhoRangInFirst(game, clue);
298
+ while (indexOfPlayerWhoRangIn != -1)
299
+ {
300
+ Player *player = &game->players[indexOfPlayerWhoRangIn];
301
+ clue->answers[indexOfPlayerWhoRangIn] = playerAnsweredClue(player, clue);
302
+ if (clue->answers[indexOfPlayerWhoRangIn] == CORRECT_ANSWER)
303
+ {
304
+ player->score += clue->value;
305
+ game->playerInControlIndex = indexOfPlayerWhoRangIn;
306
+ }
307
+ else
308
+ {
309
+ player->score -= clue->value;
310
+ }
311
+ indexOfPlayerWhoRangIn = indexOfPlayerWhoRangInFirst(game, clue);
312
+ }
313
+ }
314
+
315
+ void simulateDailyDouble(Game *game, Clue *dailyDouble, int moneyLeft)
316
+ {
317
+ int wager = dailyDoubleWager(game->players, game->playerInControlIndex, moneyLeft);
318
+ Player *player = &game->players[game->playerInControlIndex];
319
+ dailyDouble->wagers[game->playerInControlIndex] = wager;
320
+ dailyDouble->answers[game->playerInControlIndex] = playerAnsweredDailyDouble(player, dailyDouble);
321
+ if (dailyDouble->answers[game->playerInControlIndex] == CORRECT_ANSWER)
322
+ {
323
+ player->score += wager;
324
+ }
325
+ else
326
+ {
327
+ player->score -= wager;
328
+ }
329
+ }
330
+
331
+ void simulateFinalJeopardy(Game *game)
332
+ {
333
+ for (int i = 0; i<3; i++)
334
+ {
335
+ game->finalJeopardyClue.wagers[i] = finalJeopardyWager(game->players, i);
336
+ }
337
+
338
+ for (int i = 0; i<3; i++)
339
+ {
340
+ Player *player = &game->players[i];
341
+ Clue *finalJeopardyClue = &game->finalJeopardyClue;
342
+ finalJeopardyClue->answers[i] = playerAnsweredFinalJeopardy(player, finalJeopardyClue);
343
+ if (finalJeopardyClue->answers[i] == CORRECT_ANSWER)
344
+ {
345
+ player->score += finalJeopardyClue->wagers[i];
346
+ }
347
+ else
348
+ {
349
+ player->score -= finalJeopardyClue->wagers[i];
350
+ }
351
+ }
352
+ }
353
+
354
+ void simulateGame(Game *game)
355
+ {
356
+ for (int i = 0; i<game->numJeopardyClues; i++)
357
+ {
358
+ Clue *clue = &game->jeopardyClues[i];
359
+ if (clue->isDailyDouble)
360
+ {
361
+ int remainingMoney = i + 1 == game->numJeopardyClues ? 36000 : 36000 + moneyLeft(&clue[1], game->numJeopardyClues - i - 1);
362
+ simulateDailyDouble(game, clue, remainingMoney);
363
+ }
364
+ else
365
+ {
366
+ simulateClue(game, clue);
367
+ }
368
+ }
369
+
370
+ //The player in last place starts off in control of the DJ board
371
+ if (game->numDoubleJeopardyClues == 30)
372
+ {
373
+ int minScore = MIN(game->players[0].score, MIN(game->players[1].score, game->players[2].score));
374
+ int minPlayerIndex = game->players[0].score == minScore ? 0 : game->players[1].score == minScore ? 1: 2;
375
+ game->playerInControlIndex = minPlayerIndex;
376
+ }
377
+
378
+ for (int i = 0; i<game->numDoubleJeopardyClues; i++)
379
+ {
380
+ Clue *clue = &game->doubleJeopardyClues[i];
381
+ if (clue->isDailyDouble)
382
+ {
383
+ int remainingMoney = i + 1 == game->numDoubleJeopardyClues ? 0 : moneyLeft(&clue[1], game->numDoubleJeopardyClues - i - 1);
384
+ simulateDailyDouble(game, clue, remainingMoney);
385
+ }
386
+ else
387
+ {
388
+ simulateClue(game, clue);
389
+ }
390
+ }
391
+
392
+ simulateFinalJeopardy(game);
393
+ }
394
+
395
+ void simulateGames(Game *game, int trials, int *wins)
396
+ {
397
+ wins[0] = 0;
398
+ wins[1] = 0;
399
+ wins[2] = 0;
400
+
401
+ int scores[3] = {game->players[0].score, game->players[1].score, game->players[2].score};
402
+
403
+ for (int i = 0; i<trials; i++)
404
+ {
405
+ gameReset(game, scores);
406
+ simulateGame(game);
407
+ int winningIndex = winningPlayerIndex(game);
408
+ if (winningIndex != -1)
409
+ {
410
+ wins[winningIndex] += 1;
411
+ }
412
+ }
413
+ }
@@ -0,0 +1,29 @@
1
+ //Game.h
2
+ #include "Player.h"
3
+
4
+ #ifndef GAME_H
5
+ #define GAME_H
6
+
7
+ typedef struct
8
+ {
9
+ Player players[3];
10
+ int numJeopardyClues;
11
+ Clue *jeopardyClues;
12
+ int numDoubleJeopardyClues;
13
+ Clue *doubleJeopardyClues;
14
+ int previousDailyDoubleColumn;
15
+ Clue finalJeopardyClue;
16
+ int playerInControlIndex;
17
+ } Game;
18
+
19
+ Game gameMake(Player *players, int numJeopardyClues, Clue *jeopardyClues, int numDoubleJeopardyClues, Clue *doubleJeopardyClues, Clue finalJeopardyClue, int playerInControlIndex);
20
+ Game averageGame();
21
+ void gameFree(Game game);
22
+ void gameReset(Game *game, int *scores);
23
+ void addDailyDouble(Game *game, int round);
24
+ void simulateGames(Game *game, int trials, int *wins);
25
+ void shuffleClues(Clue *clues, int numClues);
26
+ int winningScore(Game *game);
27
+ int winningPlayerIndex(Game *game);
28
+
29
+ #endif
@@ -0,0 +1,16 @@
1
+ //
2
+ // MinMax.h
3
+ // ArrayTest
4
+ //
5
+ // Created by Devin Shelly on 4/21/15.
6
+ // Copyright (c) 2015 Devin Shelly. All rights reserved.
7
+ //
8
+ #ifndef MINMAX_H
9
+ #define MINMAX_H
10
+
11
+ #define MAX(a, b) ((a) > (b) ? (a) : (b))
12
+ #define MIN(a, b) ((a) < (b) ? (a) : (b))
13
+
14
+ #define drand() (double)rand()/(double)RAND_MAX
15
+
16
+ #endif
@@ -0,0 +1,157 @@
1
+ //Player.c
2
+ #include <stdlib.h>
3
+ #include "math.h"
4
+ #include "MinMax.h"
5
+ #include "Player.h"
6
+
7
+ Player playerMake(int score, double buzzerRating, double confidenceRating, double knowledgeRating, double ddFJRating)
8
+ {
9
+ Player player;
10
+ player.score = score;
11
+ player.buzzerRating = buzzerRating;
12
+ player.confidenceRating = confidenceRating;
13
+ player.knowledgeRating = knowledgeRating;
14
+ player.ddFJRating = ddFJRating;
15
+ return player;
16
+ }
17
+
18
+ Player averagePlayer()
19
+ {
20
+ return playerMake(0, 1, 0, 0, 0);
21
+ }
22
+
23
+ double adjustedOdds(double odds, double rating)
24
+ {
25
+ if (rating > 0.0)
26
+ {
27
+ return odds + (1.0-odds) * rating;
28
+ }
29
+ return odds + odds*rating;
30
+ }
31
+
32
+ double oddsPlayerAnsweredClue(Player *player, Clue *clue)
33
+ {
34
+ double rawOdds = rawOddsOfAnsweringClue(clue);
35
+ return adjustedOdds(rawOdds, player->knowledgeRating);
36
+ }
37
+
38
+ ResponseType playerAnsweredClue(Player *player, Clue *clue)
39
+ {
40
+ return oddsPlayerAnsweredClue(player, clue) >= drand() ? CORRECT_ANSWER : INCORRECT_ANSWER;
41
+ }
42
+
43
+ double oddsPlayerAnsweredDailyDouble(Player *player, Clue *clue)
44
+ {
45
+ double rawOdds = rawOddsOfAnsweringDailyDouble(clue);
46
+ return adjustedOdds(rawOdds, player->ddFJRating);
47
+ }
48
+
49
+ ResponseType playerAnsweredDailyDouble(Player *player, Clue *clue)
50
+ {
51
+ return oddsPlayerAnsweredDailyDouble(player, clue) >= drand() ? CORRECT_ANSWER : INCORRECT_ANSWER;
52
+ }
53
+
54
+ double oddsPlayerAnsweredFinalJeopardy(Player *player, Clue *clue)
55
+ {
56
+ double rawOdds = rawOddsOfAnsweringFinalJeopardy(clue);
57
+ return adjustedOdds(rawOdds, player->ddFJRating);
58
+ }
59
+
60
+ ResponseType playerAnsweredFinalJeopardy(Player *player, Clue *clue)
61
+ {
62
+ return oddsPlayerAnsweredFinalJeopardy(player, clue) >= drand() ? CORRECT_ANSWER : INCORRECT_ANSWER;
63
+ }
64
+
65
+ double oddsPlayerAttemptedToRingIn(Player *player, Clue *clue)
66
+ {
67
+ double rawOdds = rawOddsOfRingingIn(clue);
68
+ return adjustedOdds(rawOdds, player->confidenceRating);
69
+ }
70
+
71
+ int playerAttemptedToRingIn(Player *player, Clue *clue)
72
+ {
73
+ return oddsPlayerAttemptedToRingIn(player, clue) >= drand();
74
+ }
75
+
76
+ void otherPlayersMinMaxScores(Player *players, int playerIndex, int *minScore, int *maxScore)
77
+ {
78
+ *maxScore = -INFINITY;
79
+ *minScore = INFINITY;
80
+ for (int i = 0; i <3; i++)
81
+ {
82
+ if (i != playerIndex)
83
+ {
84
+ *maxScore = MAX(*maxScore, players[i].score);
85
+ *minScore = MIN(*minScore, players[i].score);
86
+ }
87
+ }
88
+ }
89
+
90
+ int dailyDoubleWager(Player *players, int playerInControlIndex, int moneyLeft)
91
+ {
92
+ int minScore, maxScore;
93
+ otherPlayersMinMaxScores(players, playerInControlIndex, &minScore, &maxScore);
94
+
95
+ Player player = players[playerInControlIndex];
96
+
97
+ /* Wager the minimum if the game is already a lock even if the player in second gets every remaining question */
98
+ if ((moneyLeft + maxScore)*2 < player.score && player.score >= 0)
99
+ {
100
+ return 5;
101
+ }
102
+
103
+ /* Wager everything in the first round */
104
+ if (moneyLeft >= 36000)
105
+ {
106
+ return MAX(1000, player.score);
107
+ }
108
+
109
+
110
+ /* Otherwise, return a random value between 2k and 6k, since that's what your average contestant will do anyways, regardless of the score */
111
+ return MAX(2000, MIN(2000 + rand()%20*200, players[playerInControlIndex].score));
112
+ }
113
+
114
+ int finalJeopardyWager(Player *players, int playerIndex)
115
+ {
116
+ int minScore, maxScore;
117
+ otherPlayersMinMaxScores(players, playerIndex, &minScore, &maxScore);
118
+
119
+ Player player = players[playerIndex];
120
+
121
+ /* Bet nothing if we have no money to wager */
122
+ if (player.score <= 0)
123
+ {
124
+ return 0;
125
+ }
126
+
127
+ /* Bet everything but a dollar if we are the only ones playing final jeopardy */
128
+ if (maxScore <=0)
129
+ {
130
+ return player.score-1;
131
+ }
132
+
133
+ /* First place */
134
+ if (player.score >= maxScore)
135
+ {
136
+ int coverWager = player.score*2 == 3*maxScore ? maxScore*2 - player.score : maxScore*2 - player.score + 1;
137
+ int safeWager = player.score - maxScore*2 - 1;
138
+ return MIN(player.score, MAX(coverWager, safeWager));
139
+ }
140
+
141
+ /* Second place */
142
+ int firstPlaceIndex = players[0].score == maxScore ? 0 : players[1].score == maxScore ? 1 : 2;
143
+ int firstWager = finalJeopardyWager(players, firstPlaceIndex);
144
+ int firstMiss = maxScore - firstWager;
145
+ if (player.score >= minScore)
146
+ {
147
+ int canWin = firstMiss <= 2 * player.score;
148
+ int coverFirst = canWin && firstMiss > player.score ? player.score : 0;
149
+ int coverThird = player.score > minScore * 2 ? 0 : canWin ? 2 * minScore - player.score + 1 : 2 * minScore - player.score; /* Go for the outright win if possible, tie for second if not*/
150
+ return MIN(player.score, MAX(coverFirst, coverThird));
151
+ }
152
+
153
+ int secondPlaceIndex = 3 - firstPlaceIndex - playerIndex;
154
+ int secondWager = finalJeopardyWager(players, secondPlaceIndex);
155
+ int secondMiss = minScore - secondWager;
156
+ return secondMiss > player.score || firstMiss > player.score ? player.score : 0;
157
+ }
@@ -0,0 +1,34 @@
1
+ //Player.h
2
+ #include "Clue.h"
3
+
4
+ #ifndef PLAYER_H
5
+ #define PLAYER_H
6
+
7
+ typedef struct
8
+ {
9
+ int score;
10
+ double buzzerRating;
11
+ double confidenceRating;
12
+ double knowledgeRating;
13
+ double ddFJRating;
14
+ } Player;
15
+
16
+ Player playerMake(int score, double buzzerRating, double confidenceRating, double knowledgeRating, double ddFJRating);
17
+ Player averagePlayer();
18
+
19
+ double oddsPlayerAnsweredClue(Player *player, Clue *clue);
20
+ ResponseType playerAnsweredClue(Player *player, Clue *clue);
21
+
22
+ double oddsPlayerAnsweredDailyDouble(Player *player, Clue *clue);
23
+ ResponseType playerAnsweredDailyDouble(Player *player, Clue *clue);
24
+
25
+ double oddsPlayerAnsweredFinalJeopardy(Player *player, Clue *clue);
26
+ ResponseType playerAnsweredFinalJeopardy(Player *player, Clue *clue);
27
+
28
+ double oddsPlayerAttemptedToRingIn(Player *player, Clue *clue);
29
+ int playerAttemptedToRingIn(Player *player, Clue *clue);
30
+
31
+ int dailyDoubleWager(Player *players, int playerInControl, int moneyLeft);
32
+ int finalJeopardyWager(Player *players, int playerIndex);
33
+
34
+ #endif
@@ -0,0 +1,9 @@
1
+ # Loads mkmf which is used to make makefiles for Ruby extensions
2
+ require 'mkmf'
3
+
4
+ # Give it a name
5
+ extension_name = 'jeopardy/jeopardy'
6
+
7
+ $CFLAGS += " -std=c99"
8
+
9
+ create_makefile(extension_name)
@@ -0,0 +1,276 @@
1
+ #include "ruby.h"
2
+ #include "Game.h"
3
+ #include <time.h>
4
+
5
+ VALUE Jeopardy;
6
+ VALUE rbGame;
7
+ VALUE rbClue;
8
+ VALUE rbPlayer;
9
+
10
+ static Clue c_clue(VALUE rb_clue)
11
+ {
12
+ if (rb_obj_is_kind_of(rb_clue, rbClue) == Qfalse)
13
+ {
14
+ rb_raise(rb_eTypeError, "Tried to convert a non-Clue Ruby object to a Clue struct.");
15
+ }
16
+ Clue clue;
17
+ clue.value = NUM2INT(rb_funcall(rb_clue, rb_intern("value"), 0));
18
+ clue.round = NUM2INT(rb_funcall(rb_clue, rb_intern("round"), 0));
19
+ clue.row = NUM2INT(rb_funcall(rb_clue, rb_intern("row"), 0));
20
+ clue.column = NUM2INT(rb_funcall(rb_clue, rb_intern("column"), 0));
21
+ clue.value = NUM2INT(rb_funcall(rb_clue, rb_intern("value"), 0));
22
+ clue.isDailyDouble = 0;
23
+ clueReset(&clue);
24
+ return clue;
25
+ }
26
+
27
+ static Clue *c_clues(VALUE rb_clues)
28
+ {
29
+ Check_Type(rb_clues, T_ARRAY);
30
+ Clue *clues = malloc(sizeof(Clue)*RARRAY_LEN(rb_clues));
31
+ for (int i = 0; i<RARRAY_LEN(rb_clues); i++)
32
+ {
33
+ VALUE iV = INT2NUM(i);
34
+ VALUE rb_clue = rb_ary_aref(1, &iV, rb_clues);
35
+ clues[i] = c_clue(rb_clue);
36
+ }
37
+ return clues;
38
+ }
39
+
40
+ static Player c_player(VALUE rb_player)
41
+ {
42
+ if (rb_obj_is_kind_of(rb_player, rbPlayer) == Qfalse)
43
+ {
44
+ rb_raise(rb_eTypeError, "Tried to convert a non-Player Ruby object to a Player struct.");
45
+ }
46
+
47
+ Player player;
48
+ player.score = NUM2INT(rb_funcall(rb_player, rb_intern("score"), 0));
49
+ player.buzzerRating = NUM2DBL(rb_funcall(rb_player, rb_intern("buzzer_rating"), 0));
50
+ player.confidenceRating = NUM2DBL(rb_funcall(rb_player, rb_intern("confidence_rating"), 0));
51
+ player.knowledgeRating = NUM2DBL(rb_funcall(rb_player, rb_intern("knowledge_rating"), 0));
52
+ player.ddFJRating = NUM2DBL(rb_funcall(rb_player, rb_intern("dd_fj_rating"), 0));
53
+ return player;
54
+ }
55
+
56
+ static Player *c_players(VALUE rb_players)
57
+ {
58
+ Check_Type(rb_players, T_ARRAY);
59
+ if (RARRAY_LEN(rb_players) != 3)
60
+ {
61
+ rb_raise(rb_eArgError, "Players array must contain three players.");
62
+ }
63
+
64
+ Player *players = malloc(sizeof(Player)*3);
65
+ for (int i = 0; i<3; i++)
66
+ {
67
+ VALUE iV = INT2NUM(i);
68
+ VALUE rb_player = rb_ary_aref(1, &iV, rb_players);
69
+ players[i] = c_player(rb_player);
70
+ }
71
+ return players;
72
+ }
73
+
74
+ static Game c_game(VALUE rb_game)
75
+ {
76
+ Game g;
77
+ Player *players_pt = c_players(rb_funcall(rb_game, rb_intern("players"), 0));
78
+ memcpy(g.players, players_pt, sizeof(Player)*3);
79
+ free(players_pt);
80
+
81
+ VALUE jeopardy_clues = rb_funcall(rb_game, rb_intern("jeopardy_clues"), 0);
82
+ g.jeopardyClues = c_clues(jeopardy_clues);
83
+ g.numJeopardyClues = RARRAY_LEN(jeopardy_clues);
84
+
85
+ VALUE double_jeopardy_clues = rb_funcall(rb_game, rb_intern("double_jeopardy_clues"), 0);
86
+ g.doubleJeopardyClues = c_clues(double_jeopardy_clues);
87
+ g.numDoubleJeopardyClues = RARRAY_LEN(double_jeopardy_clues);
88
+ g.finalJeopardyClue = clueMake(0, 3, 0, 0, 0);
89
+ g.playerInControlIndex = 0;
90
+ VALUE previous_daily_double_column = rb_funcall(rb_game, rb_intern("previous_daily_double_column"), 0);
91
+
92
+ if (TYPE(previous_daily_double_column) != T_FIXNUM && TYPE(previous_daily_double_column) != T_NIL)
93
+ {
94
+ rb_raise(rb_eTypeError, "Previous daily double column must either be a FIXNUM or NIL");
95
+ }
96
+
97
+ g.previousDailyDoubleColumn = previous_daily_double_column == Qnil ? -1 : NUM2INT(previous_daily_double_column);
98
+
99
+ VALUE jeopardy_daily_doubles_left = rb_funcall(rb_game, rb_intern("jeopardy_daily_doubles_left"), 0);
100
+ Check_Type(jeopardy_daily_doubles_left, T_FIXNUM);
101
+ for (int i = 0; i<NUM2INT(jeopardy_daily_doubles_left); i++)
102
+ {
103
+ addDailyDouble(&g, 1);
104
+ }
105
+ VALUE double_jeopardy_daily_doubles_left = rb_funcall(rb_game, rb_intern("double_jeopardy_daily_doubles_left"), 0);
106
+ Check_Type(double_jeopardy_daily_doubles_left, T_FIXNUM);
107
+ for (int i = 0; i<NUM2INT(double_jeopardy_daily_doubles_left); i++)
108
+ {
109
+ addDailyDouble(&g, 2);
110
+ }
111
+
112
+ return g;
113
+ }
114
+
115
+ static void sync_clue(VALUE rb_clue, Clue c_clue, VALUE rb_players)
116
+ {
117
+ VALUE rb_answers = rb_hash_new();
118
+ VALUE rb_wagers = rb_hash_new();
119
+ rb_funcall(rb_clue, rb_intern("answers="), 1, rb_answers);
120
+ rb_funcall(rb_clue, rb_intern("wagers="), 1, rb_wagers);
121
+
122
+ for (int j = 0; j<3; j++)
123
+ {
124
+ VALUE jV = INT2NUM(j);
125
+ VALUE rb_player = rb_ary_aref(1, &jV, rb_players);
126
+ if(c_clue.wagers[j] != NO_WAGER)
127
+ {
128
+ rb_hash_aset(rb_wagers, rb_player, INT2NUM(c_clue.wagers[j]));
129
+ }
130
+ if (c_clue.answers[j] == CORRECT_ANSWER)
131
+ {
132
+ rb_hash_aset(rb_answers, rb_player, Qtrue);
133
+ }
134
+ else if (c_clue.answers[j] == INCORRECT_ANSWER)
135
+ {
136
+ rb_hash_aset(rb_answers, rb_player, Qfalse);
137
+ }
138
+ rb_funcall(rb_clue, rb_intern("round="), 1, INT2NUM(c_clue.round));
139
+ rb_funcall(rb_clue, rb_intern("row="), 1, INT2NUM(c_clue.row));
140
+ rb_funcall(rb_clue, rb_intern("column="), 1, INT2NUM(c_clue.column));
141
+ rb_funcall(rb_clue, rb_intern("value="), 1, INT2NUM(c_clue.value));
142
+ }
143
+ }
144
+
145
+ static void sync_player(VALUE rb_player, Player c_player)
146
+ {
147
+ rb_funcall(rb_player, rb_intern("score="), 1, INT2NUM(c_player.score));
148
+ }
149
+
150
+
151
+ static void sync_game(VALUE rb_game, Game c_game)
152
+ {
153
+ VALUE rb_players = rb_funcall(rb_game, rb_intern("players"), 0);
154
+ VALUE rb_jeopardy_clues = rb_funcall(rb_game, rb_intern("jeopardy_clues"), 0);
155
+ for (int i = 0; i<c_game.numJeopardyClues; i++)
156
+ {
157
+ VALUE iV = INT2NUM(i);
158
+ VALUE rb_clue = rb_ary_aref(1, &iV, rb_jeopardy_clues);
159
+ sync_clue(rb_clue, c_game.jeopardyClues[i], rb_players);
160
+ }
161
+
162
+ VALUE rb_double_jeopardy_clues = rb_funcall(rb_game, rb_intern("double_jeopardy_clues"), 0);
163
+ for (int i = 0; i<c_game.numDoubleJeopardyClues; i++)
164
+ {
165
+ VALUE iV = INT2NUM(i);
166
+ VALUE rb_clue = rb_ary_aref(1, &iV, rb_double_jeopardy_clues);
167
+ sync_clue(rb_clue, c_game.doubleJeopardyClues[i], rb_players);
168
+ }
169
+
170
+ VALUE rb_final_jeopardy_clue = rb_funcall(rb_game, rb_intern("final_jeopardy_clue"), 0);
171
+ sync_clue(rb_final_jeopardy_clue, c_game.finalJeopardyClue, rb_players);
172
+
173
+ for (int i = 0; i<3; i++)
174
+ {
175
+ VALUE iV = INT2NUM(i);
176
+ VALUE rb_player = rb_ary_aref(1, &iV, rb_players);
177
+ sync_player(rb_player, c_game.players[i]);
178
+ }
179
+ }
180
+
181
+ static void rb_hash_set_if_nil(VALUE hash, VALUE key, VALUE val)
182
+ {
183
+ if (rb_hash_aref(hash, key) == Qnil)
184
+ {
185
+ rb_hash_aset(hash, key, val);
186
+ }
187
+ }
188
+
189
+ static VALUE simulate(int argc, VALUE *argv, VALUE self)
190
+ {
191
+ VALUE options;
192
+ rb_scan_args(argc, argv, "01", &options);
193
+ if (options == Qnil)
194
+ {
195
+ options = rb_hash_new();
196
+ }
197
+ Check_Type(options, T_HASH);
198
+ VALUE trials_key = ID2SYM(rb_intern("trials"));
199
+ VALUE seed_key = ID2SYM(rb_intern("seed"));
200
+ rb_hash_set_if_nil(options, trials_key, INT2NUM(1));
201
+ rb_hash_set_if_nil(options, seed_key, INT2NUM(time(NULL)));
202
+
203
+ VALUE trials = rb_hash_aref(options, trials_key);
204
+ VALUE seed = rb_hash_aref(options, seed_key);
205
+
206
+ Check_Type(trials, T_FIXNUM);
207
+ Check_Type(seed, T_FIXNUM);
208
+ if (NUM2INT(trials) < 1)
209
+ {
210
+ rb_raise(rb_eArgError, "The number of trials a game is simulated must be greater than zero.");
211
+ }
212
+
213
+ srand(NUM2INT(seed));
214
+ srand48(NUM2INT(seed));
215
+
216
+ Game game = c_game(self);
217
+
218
+ int wins[3] = {0, 0, 0};
219
+ simulateGames(&game, NUM2INT(trials), wins);
220
+
221
+ sync_game(self, game);
222
+ gameFree(game);
223
+ VALUE wins_hash = rb_hash_new();
224
+ VALUE players = rb_funcall(self, rb_intern("players"), 0);
225
+
226
+ for (int i = 0; i<3; i++)
227
+ {
228
+ VALUE iV = INT2NUM(i);
229
+ VALUE player = rb_ary_aref(1, &iV, players);
230
+ rb_hash_aset(wins_hash, player, INT2NUM(wins[i]));
231
+ }
232
+
233
+ return wins_hash;
234
+ }
235
+
236
+ static VALUE daily_double_odds(VALUE self, VALUE rb_clue)
237
+ {
238
+ Player player = c_player(self);
239
+ Clue clue = c_clue(rb_clue);
240
+ clue.isDailyDouble = 1;
241
+ return DBL2NUM(oddsPlayerAnsweredDailyDouble(&player, &clue));
242
+ }
243
+
244
+ static VALUE clue_odds(VALUE self, VALUE rb_clue)
245
+ {
246
+ Player player = c_player(self);
247
+ Clue clue = c_clue(rb_clue);
248
+ return DBL2NUM(oddsPlayerAnsweredClue(&player, &clue));
249
+ }
250
+
251
+ static VALUE final_jeopardy_odds(VALUE self)
252
+ {
253
+ Player player = c_player(self);
254
+ Clue finalJeopardy;
255
+ finalJeopardy.finalJeopardyStandardDeviations = 0.0;
256
+ return DBL2NUM(oddsPlayerAnsweredFinalJeopardy(&player, &finalJeopardy));
257
+ }
258
+
259
+ void Init_jeopardy()
260
+ {
261
+
262
+ #if RDOC_CAN_PARSE_DOCUMENTATION
263
+ Jeopardy = rb_define_module("Jeopardy");
264
+ rbGame = rb_define_class_under(Jeopardy, "Game", rb_cObject);
265
+ rbPlayer = rb_define_class_under(Jeopardy, "Player", rb_cObject);
266
+ rbClue = rb_define_class_under(Jeopardy, "Clue", rb_cObject);
267
+ #endif
268
+ Jeopardy = rb_const_get(rb_cObject, rb_intern("Jeopardy"));
269
+ rbGame = rb_const_get(Jeopardy, rb_intern("Game"));
270
+ rbClue = rb_const_get(Jeopardy, rb_intern("Clue"));
271
+ rbPlayer = rb_const_get(Jeopardy, rb_intern("Player"));
272
+ rb_define_method(rbGame, "simulate", simulate, -1);
273
+ rb_define_method(rbPlayer, "daily_double_odds", daily_double_odds, 1);
274
+ rb_define_method(rbPlayer, "clue_odds", clue_odds, 1);
275
+ rb_define_method(rbPlayer, "final_jeopardy_odds", final_jeopardy_odds, 0);
276
+ }
data/lib/jeopardy.rb ADDED
@@ -0,0 +1,250 @@
1
+ require 'set'
2
+
3
+ module Jeopardy
4
+
5
+ class Game
6
+
7
+ attr_accessor :jeopardy_clues, :double_jeopardy_clues, :final_jeopardy_clue, :players, :previous_daily_double_column, :jeopardy_daily_doubles_left, :double_jeopardy_daily_doubles_left
8
+
9
+ ##
10
+ # Initializes a new Jeopardy::Game with an optional hash containing the following keys:
11
+ #
12
+ # [:jeopardy_clues] An array containing the Jeopardy::Clue instances remaining in the Jeopardy! round (default: a full round)
13
+ # [:double_jeopardy_clues] An array containing the Jeopardy::Clue instances remaining in Double Jeopardy! (default: a full round)
14
+ # [:players] An array containing three Jeopardy::Player instances (default: three average players with scores of zero)
15
+ # [:jeopardy_daily_doubles_left] The number of Daily Doubles yet to be uncovered in the Jeopardy! round (default : 1)
16
+ # [:double_jeopardy_daily_doubles_left] The number of Daily Doubles yet to be uncovered in the Double Jeopardy! round (default: 2)
17
+ # [:previous_daily_double_column] The column that the first DJ Daily Double was found in, eliminating any remaining clues in that column from potentially being the remaining Daily Double (default: nil)
18
+
19
+ def initialize(options = {})
20
+ options[:jeopardy_clues] ||= 0.upto(29).map { |location| Jeopardy::Clue.new(row: location.to_i%5, column: location.to_i/5, value: location.to_i%5*200+200) }
21
+ options[:double_jeopardy_clues] ||= 0.upto(29).map { |location| Jeopardy::Clue.new(round: 2, row: location.to_i%5, column: location.to_i/5, value: location.to_i%5*400+400) }
22
+ options[:players] ||= [Jeopardy::Player.new, Jeopardy::Player.new, Jeopardy::Player.new]
23
+ options[:jeopardy_daily_doubles_left] ||= 1
24
+ options[:double_jeopardy_daily_doubles_left] ||= 2
25
+
26
+ @jeopardy_clues = options[:jeopardy_clues]
27
+ @double_jeopardy_clues = options[:double_jeopardy_clues]
28
+ @final_jeopardy_clue = Jeopardy::Clue.new(round: 3, value: 0)
29
+ @players = options[:players]
30
+ @previous_daily_double_column = options[:previous_daily_double_column]
31
+ @jeopardy_daily_doubles_left = options[:jeopardy_daily_doubles_left]
32
+ @double_jeopardy_daily_doubles_left = options[:double_jeopardy_daily_doubles_left]
33
+ end
34
+
35
+ ##
36
+ # Simulates a Jeopardy::Game with an options hash containing the following keys:
37
+ #
38
+ # [:trials] The number of times the Monte Carlo sim will be run (default: 1)
39
+ # [:seed] The seed which is fed to the random number generators, or nil for a time based seed (default: nil)
40
+ #
41
+ # Returns a hash with Jeopardy::Game#players as keys and their respective wins
42
+ # as values.
43
+ #
44
+ # *Usage*
45
+ #
46
+ # [<tt>Jeopardy::Game.new.simulate(seed: 0)</tt>] #=> <tt>{ Jeopardy::Game.players[0] => 0, Jeopardy::Game.players[1] => 0, Jeopardy::Game.players[2] => 1 }</tt>
47
+ # [<tt>Jeopardy::Game.new.simulate(seed: 0, trials: 1000)</tt>] #=> <tt>{ Jeopardy::Game.players[0] => 338, Jeopardy::Game.players[1] => 343, Jeopardy::Game.players[2] => 319 }</tt>
48
+
49
+ def simulate(options = {})
50
+ #empty, just used a placeholder for documentation
51
+ #actual implementation is in jeopardy.c
52
+ end
53
+
54
+ ##
55
+ # Resets the games scores to the values found in the given array. Returns self so that a simulate call may be chained
56
+ #
57
+ # [:scores] Three scores corresponding to the new score for each player (default: [0, 0, 0])
58
+ #
59
+ # *Usage*
60
+ #
61
+ # [<tt>g = Jeopardy::Game.new</tt>]
62
+ # [<tt>g.simulate</tt>]
63
+ # [<tt>g.reset_scores([0, 0, 0])</tt>] #=> <tt>g</tt>
64
+
65
+ def reset_scores(scores = [0, 0, 0])
66
+ @players.each_with_index {|player, index| player.score = scores[index]}
67
+ return self
68
+ end
69
+
70
+ ##
71
+ # Returns an array containing the Daily Doubles with the following parameter:
72
+ #
73
+ # [:rounds] An array or integer declaring which round(s) to look for Daily Doubles in (default: [1,2])
74
+ #
75
+ # Note: Daily Doubles are not determined until after a call to Jeopardy::Game#simulate.
76
+ #
77
+ # *Usage*
78
+ #
79
+ # [<tt>g = Jeopardy::Game.new</tt>]
80
+ # [<tt>g.simulate(seed: 0)</tt>]
81
+ # [<tt>g.daily_doubles([1,2])</tt>] #=> <tt>[<Jeopardy::Clue (@round = 1)>, <Jeopardy::Clue (@round = 2)>, <Jeopardy::Clue (@round = 2)>]</tt>
82
+ # [<tt>g.daily_doubles(1)</tt>] #=> <tt>[<Jeopardy::Clue (@round = 1)>]</tt>
83
+
84
+ def daily_doubles(rounds = [1, 2])
85
+ rounds = Set.new([rounds].flatten)
86
+ all_clues = @jeopardy_clues + @double_jeopardy_clues
87
+ all_clues.select {|clue| clue.wagers.count > 0 && rounds.include?(clue.round)}
88
+ end
89
+
90
+ ##
91
+ # Returns a hash containing the Jeopardy::Game#players as keys and the corresponding Coryat scores as values.
92
+ #
93
+ # Note: Coryat scores cannot be calculated prior to a call to Jeopardy::Game#simulate
94
+ #
95
+ # *Usage*
96
+ #
97
+ # [<tt>g = Jeopardy::Game.new</tt>]
98
+ # [<tt>g.simulate</tt>]
99
+ # [<tt>g.coryats</tt>] #=> <tt>{ g.players[0] => 14_400, g.players[1] => 15_800, g.players[2] => 14_000 }</tt>
100
+
101
+ def coryats
102
+ coryats = {}
103
+ players.each {|player| coryats[player] = 0}
104
+
105
+ (@jeopardy_clues + @double_jeopardy_clues).each do |clue|
106
+ clue.answers.each do |player, answer|
107
+ coryats[player] += clue.value if answer
108
+ coryats[player] -= clue.value if !answer && clue.wagers.count == 0
109
+ end
110
+ end
111
+ return coryats
112
+ end
113
+
114
+ ##
115
+ # Returns a hash containing the Jeopardy::Game#players as keys and their corresponding scores as values, with the following optional parameter:
116
+ #
117
+ # rounds: An array or integer declaring which round(s) to calculate scores for (default: [1,2,3])
118
+ #
119
+ # Note: Scores cannot be determined until after a call to Jeopardy::Game#simulate. If the simulation starts in the middle of a round, this will not
120
+ # coincide with the scores found in Jeopardy::Game#players, as those scores include the beginning value. These scores only show the results of the clues
121
+ # simulated.
122
+ #
123
+ # *Usage*
124
+ #
125
+ # [<tt>g = Jeopardy::Game.new</tt>]
126
+ # [<tt>g.simulate(seed: 0)</tt>]
127
+ # [<tt>g.scores(1)</tt>] #=> <tt>{ g.players[0] => 5_200, g.players[2] => 4_600, g.players[3] => 7_400 }</tt>
128
+ # [<tt>g.scores(2)</tt>] #=> <tt>{ g.players[0] => 9_200, g.players[2] => 6_000, g.players[3] => 7_600 }</tt>
129
+ # [<tt>g.scores( [1,2])</tt>] #=> <tt>{ g.players[0] => 14_400, g.players[2] => 10_600, g.players[3] => 15_000 }</tt>
130
+ # [<tt>g.scores(3)</tt>] #=> <tt>{ g.players[0] => 6801, g.players[2] => 0, g.players[3] => 13_801 }</tt>
131
+ # [<tt>g.scores([1,2,3])</tt>] #=> <tt>{ g.players[0] => 21_201, g.players[1] => 10_600, g.players[2] => 28_801 }</tt>
132
+
133
+ def scores(rounds=[1,2,3])
134
+ rounds = Set.new([rounds].flatten)
135
+ all_clues = @jeopardy_clues + @double_jeopardy_clues + [@final_jeopardy_clue]
136
+ clues_for_rounds = all_clues.select {|clue| rounds.include?(clue.round)}
137
+ scores = {}
138
+ players.each {|player| scores[player] = 0}
139
+ clues_for_rounds.each do |clue|
140
+ clue.answers.each do |player, answer|
141
+ wager = clue.wagers[player]
142
+ scores[player] += wager if !wager.nil? && answer
143
+ scores[player] -= wager if !wager.nil? && !answer
144
+ scores[player] += clue.value if wager.nil? && answer
145
+ scores[player] -= clue.value if wager.nil? && !answer
146
+ end
147
+ end
148
+ return scores
149
+ end
150
+
151
+ end
152
+
153
+ class Player
154
+
155
+ attr_accessor :score, :buzzer_rating, :knowledge_rating, :dd_fj_rating, :confidence_rating
156
+
157
+ ##
158
+ # Initializes a new Jeopardy::Game with an optional hash containing the following keys:
159
+ #
160
+ # [:score] The current score of the player.
161
+ # [:buzzer_rating] A rating from zero to infinity of how likely a player is to ring in first. When multiple players ring in, the odds of an individual player ringing in first is equal to their rating divided by the sum of the ratings. (default: 1.0)
162
+ # [:knowledge_rating] A rating from -1.0 to 1.0 of how likely a player is to answer correctly after ringing in. It's a piecewise function with -1.0 ratings always answering incorrectly, 0.0 answering at the Jeopardy! average, and 1.0 always answering correctly. (default: 0.0)
163
+ # [:dd_fj_knowledge] The same as knowledge_rating, but pertaining to Daily Doubles/Final Jeopardy! instead of after ringing in. (default: 0.0)
164
+ # [:confidence_rating] How often a player attempts to ring in. -1.0 will never ring in, 0.0 rings in at an average rate, and 1.0 rings in every clue. (default: 0.0)
165
+
166
+ def initialize(options = {})
167
+ options[:score] ||= 0
168
+ options[:buzzer_rating] ||= 1.0
169
+ options[:knowledge_rating] ||= 0.0
170
+ options[:dd_fj_rating] ||= 0.0
171
+ options[:confidence_rating] ||= 0.0
172
+
173
+ @score = options[:score].to_i
174
+ @buzzer_rating = options[:buzzer_rating].to_f
175
+ @knowledge_rating = options[:knowledge_rating].to_f
176
+ @dd_fj_rating = options[:dd_fj_rating].to_f
177
+ @confidence_rating = options[:confidence_rating].to_f
178
+ end
179
+
180
+ ##
181
+ # Returns the decimal odds of the Player answering a Daily Double correctly.
182
+ #
183
+ # *Usage*
184
+ #
185
+ # [<tt>Jeopardy::Player.new.odds_of_answering_daily_double(Jeopardy::Clue.new)</tt>] #=> 0.76
186
+ #
187
+
188
+ def daily_double_odds(daily_double)
189
+ #empty, just used a placeholder for documentation
190
+ #actual implementation is in jeopardy.c
191
+ end
192
+
193
+ ##
194
+ # Return the decimal odds of the Player answering an ordinary clue correctly.
195
+ #
196
+ # *Usage*
197
+ #
198
+ # [<tt>Jeopardy::Player.new.odds_of_answering_clue(Jeopardy::Clue.new)</tt>] #=> 0.9316826244
199
+ #
200
+
201
+ def clue_odds(clue)
202
+ #empty, just used a placeholder for documentation
203
+ #actual implementation is in jeopardy.c
204
+ end
205
+
206
+ ##
207
+ # Return the decimal odds of the Player answering final jeopardy correctly.
208
+ #
209
+ # *Usage*
210
+ #
211
+ # [<tt>Jeopardy::Player.new.final_jeopardy_odds(Jeopardy::Clue.new)</tt>] #=> 0.9316826244
212
+ #
213
+ def final_jeopardy_odds()
214
+ #empty, just used a placeholder for documentation
215
+ #actual implementation is in jeopardy.c
216
+ end
217
+ end
218
+
219
+ class Clue
220
+
221
+ attr_accessor :round, :row, :column, :value, :answers, :wagers
222
+
223
+ ##
224
+ # Initializes a new Jeopardy::Game with an optional hash containing the following keys:
225
+ #
226
+ # [:round] An integer corresponding to the round the clue is in. 1 is Jeopardy!, 2 is Double Jeopardy!, 3 is Final Jeopardy! (default: 1)
227
+ # [:row] The row on the game board the clue is found. Zero is the topmost row, four the bottom row. Meaningless for a FJ! clue. (default: 0)
228
+ # [:column] The column on the game board. Zero is the leftmost column, five the rightmost. Again, meaningless for a FJ! clue. (default: 0)
229
+ # [:answers] A hash containing each player who answered as the key and true/false for a correct/incorrect answer. (default: {})
230
+ # [:wagers] A hash containing each player who wagered as the key and their wager as the value. Should be empty except for Daily Doubles/FJ. (default: {})
231
+
232
+ def initialize(options = {})
233
+ options[:round] ||= 1
234
+ options[:row] ||= 0
235
+ options[:column] ||= 0
236
+ options[:wagers] ||= {}
237
+ options[:answers] ||= {}
238
+
239
+ @round = options[:round]
240
+ @row = options[:row]
241
+ @column = options[:column]
242
+ @value = (options[:row]*200 + 200) * options[:round] if options[:round] == 1 || options[:round] == 2
243
+ @value = 0 if @value.nil?
244
+ @answers = options[:answers]
245
+ @wagers = options[:wagers]
246
+ end
247
+ end
248
+ end
249
+
250
+ require_relative "jeopardy/jeopardy"
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jeopardy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.26
5
+ platform: ruby
6
+ authors:
7
+ - Devin Shelly
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-29 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A fast Monte Carlo simulation of the television game show Jeopardy!
14
+ email: devin@devinshelly.com
15
+ executables: []
16
+ extensions:
17
+ - ext/jeopardy/extconf.rb
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ext/jeopardy/Clue.c
21
+ - ext/jeopardy/Clue.h
22
+ - ext/jeopardy/Game.c
23
+ - ext/jeopardy/Game.h
24
+ - ext/jeopardy/MinMax.h
25
+ - ext/jeopardy/Player.c
26
+ - ext/jeopardy/Player.h
27
+ - ext/jeopardy/extconf.rb
28
+ - ext/jeopardy/jeopardy.c
29
+ - lib/jeopardy.rb
30
+ homepage: http://rubygems.org/gems/jeopardy
31
+ licenses:
32
+ - MIT
33
+ metadata: {}
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 2.2.2
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Jeopardy Game Simulator
54
+ test_files: []
55
+ has_rdoc: