Skip to content

Commit 158de58

Browse files
committed
daily doubles!!! closes #14
1 parent 9ca7774 commit 158de58

File tree

2 files changed

+178
-25
lines changed

2 files changed

+178
-25
lines changed

curses_drawing.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,13 @@ def add_to_screen_if_gt_zero(screen, msg, y, x, curses_color_pair):
317317
if len(msg)>0:
318318
screen.addstr(
319319
y, x, msg, curses_color_pair )
320+
321+
def draw_daily_double_splash(screen, player_names, player_scores):
322+
screen.clear()
323+
height, width = screen.getmaxyx()
324+
text_in_screen_center(screen, "Daily Double!!!", color=COLOUR_PAIR_MEH)
325+
for i, (player_name, player_score) in enumerate(reversed(
326+
zip(player_names, player_scores) ), 1):
327+
screen.addstr(height-i, 2, "%s : %s" % (player_name, player_score),
328+
CURSES_COLOUR_PAIR_MEH )
329+
screen.refresh()

jeopardy.py

Lines changed: 168 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
from os.path import exists
1717
from os import devnull
1818
import sys
19+
from random import sample, choice
1920

2021
# from this project
2122
from wait_4_buzz import wait_4_buzz
2223
from curses_drawing import \
2324
(draw_window_grid_and_refresh,
2425
draw_window_question_prompts_and_refresh,
25-
init_colors, draw_splash,
26+
init_colors, draw_splash, draw_daily_double_splash,
2627
)
2728
from game_audio import build_audio_engine
2829
from question_states import *
@@ -41,6 +42,15 @@
4142
QUESTIONS_FILE = config.get("core", "questions_file")
4243
PERSIST_FILE = config.get("core", "persist_file")
4344

45+
# traditionally 1 in the first round and 2 in the second, but we're only doing
46+
# single rounds so make it two
47+
# must not exceed the number of categories
48+
NUM_DAILY_DOUBLES = 2
49+
50+
MIN_DAILY_DOUBLE_WAGER = 5
51+
# highest category value
52+
MAX_DAILY_DOUBLE_FOR_NEG = 500
53+
4454
def make_player_scores(player_names, scores):
4555
return tuple(("%s" + PLAYER_SCORE_SEPARATION + "%s") % a
4656
for a in zip(player_names, scores) )
@@ -82,8 +92,43 @@ def edit_scores(screen, scores):
8292
break
8393
break
8494

95+
def pick_dd_player(screen, player_names):
96+
height, width = screen.getmaxyx()
97+
98+
player_codes = get_player_id_codes(player_names)
99+
while True:
100+
event = screen.getch()
101+
if event == ord(" "):
102+
return None
103+
elif event in player_codes:
104+
return int(chr(event))
105+
106+
curses.echo()
107+
while True:
108+
try:
109+
how_much = int( screen.getstr(height-1, 2) )
110+
except ValueError: pass
111+
else:
112+
curses.noecho()
113+
break
114+
115+
116+
def input_dd_player_wager(screen, min_wager, max_wager):
117+
height, width = screen.getmaxyx()
118+
119+
curses.echo()
120+
while True:
121+
try:
122+
how_much = int( screen.getstr(height-1, 2) )
123+
except ValueError: pass
124+
else:
125+
if min_wager <= how_much <= max_wager:
126+
curses.noecho()
127+
return how_much
128+
129+
85130
def run_questions_menu(screen, questions, answered_questions, player_names,
86-
scores, answer_server):
131+
scores, daily_doubles, answer_server):
87132
selected_question = [0, 100]
88133

89134
# initialize selected question bounds
@@ -118,39 +163,76 @@ def run_questions_menu(screen, questions, answered_questions, player_names,
118163

119164
selected_question_dict = \
120165
questions[question_cat]["questions"][question_row]
121-
run_question(
166+
167+
is_dd = (question_cat, question_row) in daily_doubles
168+
169+
question_was_shown = run_question(
122170
screen,
123171
questions[question_cat]["name"],
124172
selected_question_dict["question"],
125173
selected_question_dict["answer"],
174+
is_dd,
126175
# documenting the silly convention here, someday this should be
127176
# 0 through n-1 indexed and calucated by adding 1 and multipling
128177
# by 100 or whatever. (change to *200 for modern jeopardy..)
129178
selected_question[1]*100//100,
130-
selected_question, answered_questions, player_names, scores,
179+
selected_question, answered_questions, player_names,
180+
scores, daily_doubles,
131181
answer_server
132182
)
183+
if question_was_shown:
184+
if is_dd:
185+
daily_doubles.remove( (question_cat, question_row) )
186+
answered_questions.add( tuple(selected_question) )
187+
save_database(answered_questions, player_names,
188+
scores, daily_doubles)
189+
# perhaps other saving could be done here and not
190+
# redundantly done in some of the sub-calls... but
191+
# remember we want to put in wrong penalities right away
192+
save_database(answered_questions, player_names,
193+
scores, daily_doubles)
133194

134195
elif event == ord("e"):
135196
edit_scores(screen, scores)
136-
save_database(answered_questions, player_names, scores)
197+
save_database(answered_questions, player_names,
198+
scores, daily_doubles)
137199
elif event == ord("n"):
138200
edit_names(screen, player_names)
139-
save_database(answered_questions, player_names, scores)
201+
save_database(answered_questions, player_names,
202+
scores, daily_doubles)
140203

141204
draw_window_grid_and_refresh(
142205
screen, questions, selected_question, answered_questions,
143206
make_player_scores(player_names, scores) )
144207

145208
def run_question(
146-
screen, category, question, answer, question_score,
147-
selected_question, answered_questions, player_names, scores, answer_server):
209+
screen, category, question, answer, is_dd, question_score,
210+
selected_question, answered_questions, player_names, scores,
211+
daily_doubles, answer_server):
148212

149213
answer_server.current_answer = answer
150214

151-
pre_question = (
152-
question if not SHOW_CATEGORY
153-
else "%s for %s" % (category, question_score) )
215+
if is_dd:
216+
draw_daily_double_splash(screen, player_names, scores)
217+
pick_player = pick_dd_player(screen, player_names)
218+
if pick_player == None:
219+
return False
220+
else:
221+
draw_daily_double_splash(
222+
screen,
223+
(player_names[pick_player],),
224+
(scores[pick_player],) )
225+
how_much = input_dd_player_wager(
226+
screen,
227+
MIN_DAILY_DOUBLE_WAGER,
228+
max(scores[pick_player], MAX_DAILY_DOUBLE_FOR_NEG) )
229+
230+
pre_question = ( question if not SHOW_CATEGORY
231+
else "%s for %s" % (category,
232+
question_score if not is_dd
233+
else how_much
234+
)
235+
)
154236

155237
draw_window_question_prompts_and_refresh(
156238
screen, pre_question,
@@ -161,10 +243,33 @@ def run_question(
161243
event = screen.getch()
162244

163245
if event == ord('s'):
164-
answered_questions.add( tuple(selected_question) )
165-
save_database(answered_questions, player_names, scores)
166-
run_buzzin_attempts(screen, question, answer, question_score,
167-
answered_questions, player_names, scores)
246+
if is_dd:
247+
draw_window_question_prompts_and_refresh(
248+
screen, question, player_names, pick_player,
249+
QUESTION_WAIT_ANSWER)
250+
correct = run_wait_for_right_wrong(screen)
251+
scores[pick_player] += (
252+
how_much if correct
253+
else -how_much )
254+
if correct:
255+
draw_window_question_prompts_and_refresh(
256+
screen, answer, player_names, pick_player,
257+
QUESTION_ANSWERED_RIGHT)
258+
else:
259+
draw_window_question_prompts_and_refresh(
260+
screen, question, player_names, pick_player,
261+
QUESTION_EVERYBODY_WRONG)
262+
run_until_space(screen)
263+
draw_window_question_prompts_and_refresh(
264+
screen, answer, player_names, pick_player,
265+
QUESTION_EVERYBODY_WRONG)
266+
# wait for space after showing the answer as is the case
267+
# in both if and else above
268+
run_until_space(screen)
269+
270+
else:
271+
run_buzzin_attempts(screen, question, answer, question_score,
272+
answered_questions, player_names, scores)
168273
return True
169274

170275
elif event == ord(" "):
@@ -190,10 +295,11 @@ def main(screen):
190295
with open(QUESTIONS_FILE) as f:
191296
questions = json.load(f)
192297

193-
answered_questions, player_names, scores = load_database(questions)
298+
answered_questions, player_names, scores, daily_doubles = \
299+
load_database(questions)
194300

195301
run_questions_menu(screen, questions, answered_questions, player_names,
196-
scores, answer_server)
302+
scores, daily_doubles, answer_server)
197303
screen.clear()
198304
finally:
199305
answer_server.shutdown()
@@ -249,13 +355,13 @@ def run_buzzin_attempts(
249355
if run_wait_for_right_wrong(screen):
250356
adjust_score_and_save(
251357
buzzed_in_player_id, answered_questions, player_names,
252-
scores, question_score)
358+
scores, daily_doubles, question_score)
253359
state = QUESTION_ANSWERED_RIGHT
254360
audio.correct()
255361
else:
256362
adjust_score_and_save(
257363
buzzed_in_player_id, answered_questions, player_names,
258-
scores, -question_score)
364+
scores, daily_doubles, -question_score)
259365
# if all the players have had a chance
260366
if len(players_allowed) == 1:
261367
audio.everybody_wrong()
@@ -294,27 +400,64 @@ def run_wait_for_right_wrong(screen):
294400
elif event == ord('w'):
295401
return False
296402

403+
def generate_daily_double_positions(questions):
404+
num_cat = len(questions)
405+
assert(NUM_DAILY_DOUBLES <= num_cat)
406+
407+
# based on http://j-archive.com/ddstats.php?season=26
408+
# but with some minimal probability of row 1 being a daily double added in
409+
DAILY_DOUBLE_ROW_STATISTIC = [1, 53, 119, 170, 109]
410+
daily_double_distrib = []
411+
num_rows = len(questions[0])
412+
for i, stat in enumerate(DAILY_DOUBLE_ROW_STATISTIC):
413+
if i == num_rows:
414+
break
415+
daily_double_distrib.extend( (i,) * stat )
416+
417+
# if we have more rows than our statistical table
418+
if num_rows > len(DAILY_DOUBLE_ROW_STATISTIC):
419+
# use the last statistic and add all remaining rows in that
420+
# many times.
421+
# Multiplying a tuple by a constant int is fun!
422+
# As is correctly making the arguments to range
423+
daily_double_distrib.extend(
424+
tuple(range(
425+
len(DAILY_DOUBLE_ROW_STATISTIC),
426+
len(DAILY_DOUBLE_ROW_STATISTIC) +
427+
num_rows - len(DAILY_DOUBLE_ROW_STATISTIC),
428+
) )
429+
* DAILY_DOUBLE_ROW_STATISTIC[-1] # last statistic
430+
)
431+
432+
# we sample NUM_DAILY_DOUBLES of the categories because there
433+
# can be at most 1 daily double per category, and in each case
434+
# chose the row from the row distribution table
435+
return [ (cat, choice(daily_double_distrib))
436+
for cat in sample(range(num_cat), NUM_DAILY_DOUBLES)
437+
]
438+
297439
def load_database(questions):
298440
if not exists(PERSIST_FILE):
299441
attempted_questions = set()
300442
with open('buzzin') as f:
301443
player_names = list(player_name.strip() for player_name in f )
302444
#TODO: Combine names and scores into one datastructure
303445
scores = [0,] * len(player_names)
446+
daily_doubles = generate_daily_double_positions(questions)
304447
else:
305448
with open(PERSIST_FILE) as f:
306-
attempted_questions, player_names, scores = load(f)
449+
attempted_questions, player_names, scores, daily_doubles = load(f)
307450

308-
return attempted_questions, player_names, scores
451+
return attempted_questions, player_names, scores, daily_doubles
309452

310453
def adjust_score_and_save(player_id, attempted_questions, player_names,
311-
scores, adj):
454+
scores, daily_doubles, adj):
312455
scores[player_id] += adj
313-
save_database(attempted_questions, player_names, scores)
456+
save_database(attempted_questions, player_names, scores, daily_doubles)
314457

315-
def save_database(attempted_questions, player_names, scores):
458+
def save_database(attempted_questions, player_names, scores, daily_doubles):
316459
with open(PERSIST_FILE, 'w') as f:
317-
dump( (attempted_questions, player_names, scores), f )
460+
dump( (attempted_questions, player_names, scores, daily_doubles), f )
318461

319462
if __name__=='__main__':
320463
curses.wrapper(main)

0 commit comments

Comments
 (0)