1616from os .path import exists
1717from os import devnull
1818import sys
19+ from random import sample , choice
1920
2021# from this project
2122from wait_4_buzz import wait_4_buzz
2223from 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 )
2728from game_audio import build_audio_engine
2829from question_states import *
4142QUESTIONS_FILE = config .get ("core" , "questions_file" )
4243PERSIST_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+
4454def 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+
85130def 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
145208def 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+
297439def 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
310453def 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
319462if __name__ == '__main__' :
320463 curses .wrapper (main )
0 commit comments