[Keith Vetter] 2019-11-06 : I recently stumbled across a public API for
past [https://www.jeopardy.com/%|%Jeopardy%|%] questions. The API is called
http://jservice.io/%|%jService%|% and allows you to query for clues, either
randomly or by category, value or date,
and for available categories.
I thought it would be fun to pull questions from this service and put up a
Jeopardy display, with six different question categories and with values from
$100 to $500. You can click on any of the cells and it will display the ''Answer'',
and another click will reveal the ''Question''.
[screenshot_jeopardy]
----
[Jeff Smith] 2019-11- : Below is an online demo using [CloudTk]. Reduced Font size to display on laptop screens. You may have to click on the noVNC tab at the side and display fullscreen.
<<inlinehtml>>
<iframe height="750" width="7950" src="https://cloudtk.tcl-lang.org/cloudtk/VNC?session=new&Tk=Jeopardy" allowfullscreen></iframe>
<<inlinehtml>>
----
======
##+##########################################################################
#
# Jeopardy
# by Keith Vetter 2019-11-06
# Uses the JService API to Jeopardy clues database
# See http://jservice.io/
#
package require json
package require http
package require textutil
package require Tk
font create titleFont -family Helvetica -size 24 -weight bold
font create moneyFont -family Helvetica -size 36 -weight bold
font create questionFont -family Helvetica -size 24 -weight bold
set S(wrap,length) 10
set S(color,border) aquamarine
set S(color,board) blue
set S(color,money) yellow
set S(color,title) white
set S(color,question,border) aquamarine
set S(color,question,bg) gainsboro
set S(color,question,fg) black
set SPINNERS {
{boxBounce 120 { ▖ ▘ ▝ ▗ }}
{line 130 { - \\ | / }}
{dots 80 { ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ }}
{pipe 100 { ┤ ┘ ┴ └ ├ ┌ ┬ ┐ }}
}
set SPINNER [lindex $SPINNERS 0]
proc DoDisplay {} {
global J
wm title . "Jeopardy"
set padding ".05i"
. config -bg $::S(color,border) -padx $padding -pady $padding
pack [frame .f -bg black -padx $padding -pady $padding] -fill both -expand 1
for {set col 0} {$col < 6} {incr col} {
set J(title,$col) "Title $col"
label .title_$col -textvariable J(title,$col) -font titleFont -fg $::S(color,title) \
-bg $::S(color,board)
grid .title_$col -row 0 -column $col -padx $padding -pady $padding -pady $padding \
-ipadx .2i -ipady .3i -sticky news -in .f
}
for {set row 1} {$row < 6} {incr row} {
for {set col 0} {$col < 6} {incr col} {
set w .clue_${row}_${col}
set J(clue,$row,$col) "$[expr {$row * 100}]"
label $w -textvariable J(clue,$row,$col) -font moneyFont -fg $::S(color,money) \
-bg $::S(color,board)
grid $w -row $row -column $col -sticky news -padx $padding -pady $padding \
-ipadx .2i -ipady .3i -sticky news -in .f
bind $w <1> [list ClickQuestion $w $row $col]
}
}
grid columnconfig .f all -uniform a
::ttk::button .new -text New -command {NewGame new}
::ttk::button .reset -text Reset -command {NewGame reset}
pack .reset .new -side right -pady .1i -padx .1i
}
proc NewGame {how} {
global questions
destroy .q
if {$how ne "reset"} {
Spinner start
set questions [::JServiceAPI::GetAllQuestions 6]
destroy .q
}
FillInBoard $questions
}
proc FillInBoard {questions} {
global J
set J(questions) $questions
for {set col 0} {$col < 6} {incr col} {
set title [dict get [lindex $questions $col] title]
set title [::textutil::adjust $title -length $::S(wrap,length)]
# TODO:
# make sure at most 2 lines ?
# adjust font to fit better
set J(title,$col) [string toupper $title]
}
for {set row 1} {$row < 6} {incr row} {
for {set col 0} {$col < 6} {incr col} {
set w .clue_${row}_${col}
set J(clue,$row,$col) "$[expr {$row * 100}]"
$w config -font moneyFont -fg $::S(color,money)
}
}
}
proc ClickQuestion {w row col} {
global J
destroy .q
if {$J(clue,$row,$col) eq ""} return
set J(clue,$row,$col) ""
set category [lindex $J(questions) $col]
set offset [dict get $category offset]
set clue [lindex [dict get $category clues] [expr {$offset+$row-1}]]
set question [dict get $clue question]
set answer [dict get $clue answer]
ShowQuestion $question $answer
}
proc ShowQuestion {question answer} {
global J
if {$question ne "%answer%"} {
set text $question
set btext "Answer"
set cmd [list ShowQuestion "%answer%" $answer]
} else {
set text [CleanupAnswer $answer]
set btext "Close"
set cmd [list destroy .q]
}
set wrap 20
while {1} {
set text [::textutil::adjust $text -length $wrap]
set lcount [regsub -all {\n} $text {\n} .]
if {$lcount < 5} break
incr wrap 10
if {$wrap > 80} break
}
destroy .q
frame .q -bg $::S(color,question,border) -bd 0 -padx .1i -pady .1i
pack [frame .q.q -bg $::S(color,question,bg) -bd 10 -relief solid -padx .5i -pady .5i] -fill both -expand 1
label .q.q.question -text $text -font questionFont -fg $::S(color,question,fg) -bg $::S(color,question,bg)
button .q.q.answer -text $btext -command $cmd -font questionFont -padx .2i
pack .q.q.question -side top
pack .q.q.answer -side bottom -padx .2i -pady {.2i 0}
place .q -in . -relx .5 -rely .3 -anchor n
}
proc CleanupAnswer {txt} {
set txt [regsub -all {</?.>} $txt ""]
set txt [regsub -all {\\'} $txt ']
return $txt
}
proc _Loading {} {
destroy .q
frame .q -bg $::S(color,question,border) -bd 0 -padx .1i -pady .1i
pack [frame .q.q -bg $::S(color,question,bg) -bd 10 -relief solid -padx .5i -pady .5i] -fill both -expand 1
label .q.q.question -text "Loading_" -font moneyFont -fg $::S(color,question,fg) -bg $::S(color,question,bg) -width 8
pack .q.q.question -side top
place .q -in . -relx .5 -rely .3 -anchor n
return .q.q.question
}
proc Spinner {{start ""}} {
global SPINNERS SPINNER
if {$start ne ""} {
set widget [_Loading]
lassign [lindex $SPINNERS [expr {int(rand() * [llength $SPINNERS])}]] . freq chars
set SPINNER [list 0 $freq $chars $widget]
}
lassign $SPINNER idx freq chars widget
if {! [winfo exists $widget]} return
set idx [expr {($idx + 1) % [llength $chars]}]
set newChar [lindex $chars $idx]
set oldText [$widget cget -text]
$widget config -text [string replace $oldText end end $newChar]
set SPINNER [list $idx $freq $chars $widget]
set aid [after $freq Spinner]
}
proc DumpQuestions {{col -}} {
global J
set lo 0
set hi end
if {$col ne "-"} { set lo $col ; set hi $col }
foreach category [lrange $J(questions) $lo $hi] {
set title [dict get $category title]
puts "\n$title\n[string repeat = [string length $title]]"
set offset [dict get $category offset]
set clues [dict get $category clues]
foreach clue [lrange $clues $offset $offset+4] {
set question [dict get $clue question]
set answer [dict get $clue answer]
puts " Q: $question"
puts " A: $answer"
}
}
}
namespace eval ::JServiceAPI {
variable MAX_CATEGORY 18414 ;# As of 2019-10-28
variable URL_JSERVICE http://jservice.io/api/
}
proc ::JServiceAPI::GetAllQuestions {{how_many 6}} {
variable MAX_CATEGORY
set results {}
for {set i 0} {$i < $how_many} {incr i} {
set id [expr {int(rand() * $MAX_CATEGORY)}]
set jdata [::JServiceAPI::JServiceAPI category id=$id]
set count [dict get $jdata clues_count]
set offset [expr {5 * int(rand() * ($count / 5))}]
dict set jdata offset $offset
lappend result $jdata
}
return $result
}
proc ::JServiceAPI::JServiceAPI {service args} {
variable URL_JSERVICE
variable url
set url "${URL_JSERVICE}$service"
if {$args ne ""} {
append url "?$args"
}
set token [::http::geturl $url]
set ncode [::http::ncode $token]
set code [::http::code $token]
set data [::http::data $token] ; list
::http::cleanup $token
if {$ncode != 200} { error "bad response from server: $code" }
set jdata [::json::json2dict $data]
return $jdata
}
DoDisplay
NewGame new
======