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 +7 -0
- data/ext/jeopardy/Clue.c +99 -0
- data/ext/jeopardy/Clue.h +35 -0
- data/ext/jeopardy/Game.c +413 -0
- data/ext/jeopardy/Game.h +29 -0
- data/ext/jeopardy/MinMax.h +16 -0
- data/ext/jeopardy/Player.c +157 -0
- data/ext/jeopardy/Player.h +34 -0
- data/ext/jeopardy/extconf.rb +9 -0
- data/ext/jeopardy/jeopardy.c +276 -0
- data/lib/jeopardy.rb +250 -0
- metadata +55 -0
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
|
data/ext/jeopardy/Clue.c
ADDED
@@ -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
|
+
|
data/ext/jeopardy/Clue.h
ADDED
@@ -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
|
data/ext/jeopardy/Game.c
ADDED
@@ -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
|
+
}
|
data/ext/jeopardy/Game.h
ADDED
@@ -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,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:
|