TriviaEngine.tcl 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378
  1. # The trivia engine YAY
  2. # vim: foldmethod=marker:foldcolumn=2:foldmarker=<<<,>>>:sw=2:ts=2
  3. ### INIT <<<
  4. # load mysqltcl
  5. package require mysqltcl
  6. # the channel to play in
  7. set trivia_channel "#triviacow"
  8. # the time between hints (sec)
  9. set trivia_speed 20
  10. # the time between rounds (sec)
  11. set trivia_delay 30
  12. # hold the current question info <<<
  13. set trivia_q_id ""
  14. set trivia_q_cat ""
  15. set trivia_q_question ""
  16. set trivia_q_answer ""
  17. set trivia_q_hint ""
  18. set trivia_q_attempts 0
  19. set trivia_unanswered 0
  20. set trivia_run_last 0
  21. set trivia_run_qty 0
  22. set trivia_run_nick ""
  23. set trivia_guesser ""
  24. set trivia_guesser_count 0
  25. set trivia_last_qid 0
  26. if [info exists trivia_must_rehash] {
  27. if {$trivia_must_rehash == 1} {
  28. set trivia_must_rehash 2
  29. } else {
  30. set trivia_must_rehash 0
  31. }
  32. } else {
  33. set trivia_must_rehash 0
  34. }
  35. #>>>
  36. # 0 = off
  37. # 1 = on
  38. # -1 = disabled
  39. set trivia_status 0
  40. set trivia_timer ""
  41. set trivia_watchdog_timer ""
  42. set trivia_last_ts 0
  43. #colours <<<
  44. set trivia_c(off) "\003"
  45. set trivia_c(red) "\0034"
  46. set trivia_c(blue) "\0033"
  47. set trivia_c(purple) "\0036"
  48. set trivia_c(bold) "\002"
  49. #>>>
  50. bind pubm - * trivia_input
  51. bind msg - trivia trivia_msg
  52. # connect mysql <<<
  53. proc trivia_connect { } {
  54. global trivia_db_handle
  55. set trivia_db_handle [mysqlconnect -host localhost -user root -password 7MKh3ako -db trivialive]
  56. }
  57. #>>>
  58. #>>>
  59. ### SETTINGS <<<
  60. set trivia_flag T
  61. set trivia_admin S
  62. # >>>
  63. # Handle a /msg
  64. proc trivia_msg { nick host handle cmd } {
  65. #<<<
  66. global trivia_db_handle trivia_last_qid
  67. global trivia_q_cat trivia_c
  68. regsub "(trivia )" $cmd "" cmd
  69. putlog "trivia: msg command $cmd from $nick"
  70. if {($nick == "Greeneyez") || ($nick == "JamesOff")} {
  71. if [regexp -nocase "^setcat (.+)" $cmd matches category] {
  72. if {$trivia_last_qid == 0} {
  73. puthelp "PRIVMSG $nick :Can't fathom last question, unable to update category"
  74. return
  75. }
  76. set orig_cat $category
  77. set category [mysqlescape $category]
  78. set sql "SELECT cat_id FROM categories WHERE cat_name='$category'"
  79. set result [mysqlquery $trivia_db_handle $sql]
  80. if {[set row [mysqlnext $result]] != ""} {
  81. set cat_id [lindex $row 0]
  82. mysqlendquery $result
  83. puthelp "PRIVMSG $nick :Moving question $trivia_last_qid to category$trivia_c(purple) $category$trivia_c(off) ($cat_id)"
  84. set sql "UPDATE questions SET cat_id='$cat_id' WHERE question_id='$trivia_last_qid'"
  85. mysqlexec $trivia_db_handle $sql
  86. set trivia_q_cat $orig_cat
  87. return
  88. } else {
  89. puthelp "PRIVMSG $nick :Creating new category$trivia_c(purple) $category"
  90. set sql "INSERT INTO categories VALUES (null, '$category', 1)"
  91. mysqlexec $trivia_db_handle $sql
  92. set cat_id [mysqlinsertid $trivia_db_handle]
  93. puthelp "PRIVMSG $nick :Moving question $trivia_last_qid to category$trivia_c(purple) $category$trivia_c(off) ($cat_id)"
  94. set sql "UPDATE questions SET cat_id='$cat_id' WHERE question_id='$trivia_last_qid'"
  95. mysqlexec $trivia_db_handle $sql
  96. set trivia_q_cat $orig_cat
  97. return
  98. }
  99. }
  100. }
  101. }
  102. #>>>
  103. # Handle input coming from a channel
  104. proc trivia_input { nick host handle channel arg } {
  105. #<<<
  106. global trivia_channel trivia_status trivia_q_answer
  107. global trivia_guesser_count trivia_guesser trivia_c
  108. if {[string tolower $trivia_channel] != [string tolower $channel]} {
  109. return 0
  110. }
  111. if [regexp -nocase "^!t(rivia)? (.+)" $arg matches cmd param] {
  112. trivia_command $nick $host $handle $channel $param
  113. return 0
  114. }
  115. if [regexp -nocase "^!t(rivia)?$" $arg] {
  116. if {$trivia_status == 1} {
  117. puthelp "PRIVMSG $trivia_channel :!trivia ... ?"
  118. return 0
  119. } else {
  120. puthelp "PRIGMSG $trivia_channel :Perhaps you want $trivia_c(purple)!trivia start"
  121. return 0
  122. }
  123. }
  124. # need to be running below here
  125. if {$trivia_status == -1} {
  126. return 0
  127. }
  128. if {$trivia_q_answer == ""} {
  129. return 0
  130. }
  131. #see if someone else is playing
  132. if {($nick == $trivia_guesser) && ($nick != "NoTopic")} {
  133. incr trivia_guesser_count
  134. putloglev d * "$nick has talked $trivia_guesser_count times in a row..."
  135. } else {
  136. set trivia_guesser_count 0
  137. set trivia_guesser $nick
  138. }
  139. #tidy the string up
  140. set arg [string tolower $arg]
  141. set arg [string trim $arg]
  142. if {$arg == [string tolower $trivia_q_answer]} {
  143. trivia_correct $nick
  144. return 0
  145. }
  146. #if they didn't get it right, and it's been more than 20 lines, taunt!
  147. if {($trivia_guesser_count > 0) && (($trivia_guesser_count % 20)) == 0} {
  148. putserv "PRIVMSG $trivia_channel :Playing with yourself, $nick? ;)"
  149. putlog "Is $nick playing with themselves?"
  150. }
  151. }
  152. #>>>
  153. # Someone got it right!
  154. proc trivia_correct { nick } {
  155. #<<<
  156. global trivia_q_id trivia_q_cat trivia_q_question trivia_q_answer trivia_q_hint trivia_q_attempts trivia_channel trivia_status
  157. global trivia_timer trivia_delay trivia_db_handle trivia_unanswered trivia_run_qty trivia_run_last trivia_run_nick trivia_c
  158. if {$trivia_status != 1} {
  159. #something strange going on
  160. return 0
  161. }
  162. trivia_killtimer
  163. set answer $trivia_q_answer
  164. set trivia_q_answer ""
  165. set newuser 0
  166. set uid [trivia_get_uid $nick]
  167. if {$uid == 0} {
  168. putlog "$nick does not have entry in database... creating"
  169. set uid [trivia_create_user $nick]
  170. set newuser 1
  171. }
  172. putlog "$nick has uid $uid"
  173. trivia_incr_score $uid
  174. putquick "PRIVMSG $trivia_channel :Congratulations $trivia_c(purple)$nick$trivia_c(off)! The answer was$trivia_c(purple) $answer$trivia_c(off)."
  175. if {$newuser == 1} {
  176. putquick "PRIVMSG $trivia_channel :Welcome to our newest player, $trivia_c(purple)$nick$trivia_c(off) :)"
  177. }
  178. putquick "PRIVMSG $trivia_channel :rank0rs: [trivia_near_five2 $uid]"
  179. #set score [trivia_get_score $uid]
  180. #putserv "PRIVMSG $trivia_channel :$nick now has $score points."
  181. set trivia_timer [utimer $trivia_delay trivia_start_round]
  182. set trivia_unanswered 0
  183. if {$trivia_run_last == $uid} {
  184. incr trivia_run_qty
  185. if {$trivia_run_qty > 2} {
  186. putquick "PRIVMSG $trivia_channel :$nick [trivia_get_run $trivia_run_qty] $trivia_run_qty in a row!"
  187. }
  188. } else {
  189. #end of streak
  190. if {$trivia_run_qty > 2} {
  191. putquick "PRIVMSG $trivia_channel :$nick ended $trivia_run_nick's winning spree."
  192. }
  193. set trivia_run_qty 1
  194. set trivia_run_last $uid
  195. set trivia_run_nick $nick
  196. }
  197. if {[rand 100] > 95} {
  198. putserv "PRIVMSG $trivia_channel :Remember, to report a problem with a question or answer, type $trivia_c(purple)!trivia report <description of problem>"
  199. }
  200. trivia_check_rehash
  201. }
  202. #>>>
  203. #See if we need to rehash
  204. proc trivia_check_rehash { } {
  205. #<<<
  206. global trivia_must_rehash trivia_channel trivia_c
  207. if {$trivia_must_rehash == 1} {
  208. putquick "PRIVMSG $trivia_channel :$trivia_c(red)### Please wait, reloading trivia script ###"
  209. trivia_killtimer
  210. rehash
  211. }
  212. }
  213. #>>>
  214. # Turn a winning spree into text
  215. proc trivia_get_run { row } {
  216. #<<<
  217. switch $row {
  218. 3 {
  219. return "is on a winning spree!"
  220. }
  221. 5 {
  222. return "is on a rampage!"
  223. }
  224. 6 {
  225. return "is unstoppable!"
  226. }
  227. 8 {
  228. return "is on a Google spree!"
  229. }
  230. 10 {
  231. return "is GODLIKE!"
  232. }
  233. 12 {
  234. return "needs to get out more."
  235. }
  236. }
  237. return "is on a roll ..."
  238. }
  239. #>>>
  240. # Get someone's user ID from their nick
  241. proc trivia_get_uid { nick } {
  242. #<<<
  243. global trivia_db_handle
  244. putloglev 4 * "trivia_get_uid ($nick)"
  245. set nick [mysqlescape $nick]
  246. set sql "SELECT user_id FROM users WHERE user_name = '$nick'"
  247. set result [mysqlquery $trivia_db_handle $sql]
  248. #putlog $sql
  249. if {[set row [mysqlnext $result]] != ""} {
  250. set user_id [lindex $row 0]
  251. mysqlendquery $result
  252. return $user_id
  253. } else {
  254. return 0
  255. }
  256. }
  257. #>>>
  258. # Get a period
  259. proc trivia_get_period { } {
  260. #<<<
  261. return [expr [clock scan "last friday 23:59" -gmt true] + 60]
  262. }
  263. #>>>
  264. # Get a score from a UID
  265. proc trivia_get_score { user_id } {
  266. #<<<
  267. global trivia_db_handle
  268. putloglev 4 * "trivia_get_score ($user_id)"
  269. set dt [trivia_get_period]
  270. set sql "SELECT COUNT(*) AS yarr FROM scores WHERE dt > '$dt' AND user_id='$user_id'"
  271. set result [mysqlquery $trivia_db_handle $sql]
  272. if {[set row [mysqlnext $result]] != ""} {
  273. set score [lindex $row 0]
  274. mysqlendquery $result
  275. return $score
  276. } else {
  277. return 0
  278. }
  279. }
  280. #>>>
  281. # Turn a timestamp into a date
  282. proc trivia_ts_to_date { ts } {
  283. #<<<
  284. return [clock format $ts -format "%Y/%m/%d %H:%M"]
  285. }
  286. #>>>
  287. # Get someone's userinfo from their UID
  288. proc trivia_get_userinfo { user_id } {
  289. #<<<
  290. global trivia_db_handle
  291. putloglev 4 * "trivia_get_userinfo ($user_id)"
  292. set sql "SELECT user_score, user_last, user_reg FROM users WHERE user_id = '$user_id'"
  293. set result [mysqlquery $trivia_db_handle $sql]
  294. if {[set row [mysqlnext $result]] != ""} {
  295. set last [lindex $row 1]
  296. set score [trivia_get_score $user_id]
  297. if {$last == ""} {
  298. set last "Unknown"
  299. } else {
  300. set last [trivia_ts_to_date $last]
  301. }
  302. set reg [lindex $row 2]
  303. if {$reg == ""} {
  304. set reg "Unkown"
  305. } else {
  306. set reg [trivia_ts_to_date $reg]
  307. }
  308. return "$score|$last|$reg"
  309. mysqlendquery $result
  310. } else {
  311. return "No stats"
  312. }
  313. }
  314. #>>>
  315. # Get someone's stats from their username
  316. proc trivia_user_stats { user_name } {
  317. #<<<
  318. global trivia_channel
  319. set uid [trivia_get_uid $user_name]
  320. if {$uid == 0} {
  321. putserv "PRIVMSG $trivia_channel :Unknown user '$user_name'"
  322. return
  323. }
  324. set stats [trivia_get_userinfo $uid]
  325. if {$stats == "No stats"} {
  326. putserv "PRIVMSG $trivia_channel :No stats available."
  327. return
  328. }
  329. set stats2 [split $stats "|"]
  330. putserv "PRIVMSG $trivia_channel :Trivia stats for $user_name:"
  331. putserv "PRIVMSG $trivia_channel : Current score: [lindex $stats2 0]"
  332. putserv "PRIVMSG $trivia_channel : Ranking: [trivia_get_rank $uid]"
  333. putserv "PRIVMSG $trivia_channel : First seen: [lindex $stats2 2]"
  334. putserv "PRIVMSG $trivia_channel : Last scored: [lindex $stats2 1]"
  335. }
  336. #>>>
  337. # Increase a UID's score
  338. proc trivia_incr_score { id { howmuch 1 } } {
  339. #<<<
  340. global trivia_db_handle
  341. putloglev 4 * "trivia_incr_score ($id, $howmuch)"
  342. #set sql "UPDATE users SET user_score = user_score + $howmuch WHERE user_id = '$id'"
  343. #mysqlexec $trivia_db_handle $sql
  344. set sql "UPDATE users SET user_last = UNIX_TIMESTAMP() WHERE user_id = '$id'"
  345. mysqlexec $trivia_db_handle $sql
  346. set sql "INSERT INTO scores VALUES ('$id', '[clock seconds]')"
  347. mysqlexec $trivia_db_handle $sql
  348. }
  349. #>>>
  350. # Create a new user
  351. proc trivia_create_user { nick } {
  352. #<<<
  353. global trivia_db_handle
  354. putloglev 4 * "trivia_create_user ($nick)"
  355. set nick [mysqlescape $nick]
  356. set sql "INSERT INTO users VALUES (null, '$nick', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())"
  357. mysqlexec $trivia_db_handle $sql
  358. set uid [mysqlinsertid $trivia_db_handle]
  359. return $uid
  360. }
  361. #>>>
  362. # Handle a !trivia command
  363. proc trivia_command { nick host handle channel param } {
  364. #<<<
  365. global trivia_channel trivia_status trivia_flag trivia_admin trivia_c
  366. set arg ""
  367. regexp {^([^ ]+)( .+)?$} $param matches cmd arg
  368. set param [string tolower $cmd]
  369. set arg [string trim $arg]
  370. putloglev d * "trivia command: $param from: $nick ($arg)"
  371. if {[matchattr $handle |$trivia_admin $trivia_channel] && ($param != "enable") && ($trivia_status == -1)} {
  372. return 0
  373. }
  374. if [regexp -nocase "^(score|place)( .+)?" $param] {
  375. putserv "PRIVMSG $trivia_channel :[trivia_score $arg $nick]"
  376. if [string match -nocase $nick $arg] {
  377. putserv "PRIVMSG $trivia_channel :(You know putting your own nick is optional, right? :)"
  378. }
  379. return 0
  380. }
  381. if {$param == "top10"} {
  382. putserv "PRIVMSG $trivia_channel :[trivia_get_top10]"
  383. return 0
  384. }
  385. if {$param == "info"} {
  386. trivia_user_stats $arg
  387. return 0
  388. }
  389. if {$param == "start"} {
  390. trivia_start
  391. return 0
  392. }
  393. if {$param == "report"} {
  394. trivia_report $nick $arg
  395. return 0
  396. }
  397. if {![matchattr $handle |$trivia_flag $channel]} {
  398. putserv "PRIVMSG $nick :use: !trivia \[score|top10|start\]"
  399. return 0
  400. }
  401. if {$param == "stop"} {
  402. trivia_stop
  403. return 0
  404. }
  405. if {$param == "skip"} {
  406. trivia_skip $nick
  407. return 0
  408. }
  409. if {$param == "stats"} {
  410. trivia_stats
  411. return 0
  412. }
  413. if {![matchattr $handle |$trivia_admin $channel]} {
  414. putserv "PRIVMSG $nick :use: !trivia \[score|top10|start|stop|skip|stats\]"
  415. return 0
  416. }
  417. if {$param == "disable"} {
  418. trivia_disable
  419. return 0
  420. }
  421. if {$param == "enable"} {
  422. trivia_enable
  423. return 0
  424. }
  425. if {$param == "merge"} {
  426. trivia_merge $nick $arg
  427. #puthelp "PRIGMSG $trivia_channel :Score merging is disabled"
  428. return 0
  429. }
  430. if {$param == "rehash"} {
  431. trivia_rehash
  432. return 0
  433. }
  434. putserv "PRIVMSG $nick :use: !trivia \[start|stop|score|top10|skip|stats|enable|disable|merge\]"
  435. return 0
  436. }
  437. #>>>
  438. # Rehash
  439. proc trivia_rehash { } {
  440. #<<<
  441. global trivia_status trivia_channel trivia_must_rehash
  442. if {$trivia_must_rehash == 1} {
  443. putserv "PRIVMSG $trivia_channel :! Reloading trivia now..."
  444. rehash
  445. return 0
  446. }
  447. if {$trivia_status != 1} {
  448. putserv "PRIVMSG $trivia_channel :! Reloading trivia now..."
  449. rehash
  450. return 0
  451. }
  452. set trivia_must_rehash 1
  453. putserv "PRIVMSG $trivia_channel :Trivia will reload after current round"
  454. }
  455. #>>>
  456. # Disable the script
  457. proc trivia_disable { } {
  458. #<<<
  459. global trivia_status trivia_channel
  460. if {$trivia_status == 1} {
  461. #stop the current game
  462. trivia_stop
  463. }
  464. set trivia_status -1
  465. putserv "PRIVMSG $trivia_channel :Trivia disabled."
  466. return 0
  467. }
  468. #>>>
  469. # Enable the script1
  470. proc trivia_enable {} {
  471. #<<<
  472. global trivia_status trivia_channel
  473. set trivia_status 0
  474. putserv "PRIVMSG $trivia_channel :Trivia enabled."
  475. set alive [::mysql::ping $trivia_db_handle]
  476. if {$alive == false} {
  477. putlog "ERROR: mysql has gone away :("
  478. putquick "PRIVMSG $trivia_channel :Warning: Unable to reach database!"
  479. }
  480. return 0
  481. }
  482. #>>>
  483. #Make a hint
  484. proc trivia_make_hint { hint answer } {
  485. #<<<
  486. set hint [string toupper $hint]
  487. set answer [string toupper $answer]
  488. set answer_words [split $answer { }]
  489. set final_hint ""
  490. #are we making the very first hint?
  491. if {$hint == ""} {
  492. foreach word $answer_words {
  493. append final_hint [string repeat "_" [string length $word]]
  494. append final_hint " "
  495. }
  496. set final_hint [string trim $final_hint]
  497. #now put the punctuation in
  498. set letters [split $answer {}]
  499. set i 0
  500. foreach letter $letters {
  501. if {![regexp -nocase {[A-Z0-9 ]} $letter]} {
  502. set final_hint [string replace $final_hint $i $i $letter]
  503. }
  504. incr i
  505. }
  506. return [string trim $final_hint]
  507. }
  508. # explode the into words
  509. set hint_words [split $hint { }]
  510. set i 0
  511. foreach word $hint_words {
  512. set answer_word [lindex $answer_words $i]
  513. putloglev 1 * "considering $answer_word ($word)"
  514. #are we on the first iteration?
  515. if [regexp "^_+$" $word] {
  516. #use the first letter
  517. set letters [string length $word]
  518. #don't hint for single-letter words
  519. if {$letters > 1} {
  520. set word [string index $answer_word 0]
  521. append word [string repeat "_" [expr $letters - 1]]
  522. } else {
  523. set word "_"
  524. }
  525. append final_hint "$word "
  526. } else {
  527. #not the first iteration so figure out where the gaps are
  528. set gaps [list]
  529. set letters [split $word {}]
  530. set j 0
  531. foreach letter $letters {
  532. if {$letter == "_"} {
  533. lappend gaps $j
  534. }
  535. incr j
  536. }
  537. if {[llength $gaps] <= 1} {
  538. #eek no letters to replace, or only one gap
  539. putloglev 1 * " insufficient gaps, using $word"
  540. append final_hint "$word "
  541. } else {
  542. #now we pick a random letter position
  543. set pos [trivia_random_element $gaps]
  544. putloglev 1 * " filling in $pos"
  545. set word [string replace $word $pos $pos [string index $answer_word $pos]]
  546. putloglev 1 * " --> $word"
  547. append final_hint "$word "
  548. }
  549. }
  550. incr i
  551. }
  552. set final_hint [string trim $final_hint]
  553. return $final_hint
  554. }
  555. #>>>
  556. # Fetch a question
  557. proc trivia_get_question { } {
  558. #<<<
  559. global trivia_db_handle trivia_q_id trivia_q_cat trivia_q_question trivia_q_answer trivia_q_hint trivia_channel
  560. # make sure we're connected
  561. set alive [::mysql::ping $trivia_db_handle]
  562. if {$alive == false} {
  563. putlog "ERROR: mysql has gone away :("
  564. putquick "PRIVMSG $trivia_channel :Couldn't reach database to load next question :("
  565. return 0
  566. }
  567. set sql "SELECT q.question, q.question_id, q.answer, c.cat_name FROM questions q LEFT JOIN categories c USING (cat_id) WHERE c.cat_enabled=1 ORDER BY count ASC, rand() LIMIT 1"
  568. set result [mysqlquery $trivia_db_handle $sql]
  569. if {[set row [mysqlnext $result]] != ""} {
  570. set trivia_q_id [lindex $row 1]
  571. set trivia_q_cat [lindex $row 3]
  572. set trivia_q_question [lindex $row 0]
  573. set trivia_q_answer [string toupper [lindex $row 2]]
  574. set trivia_q_hint ""
  575. mysqlendquery $result
  576. } else {
  577. putlog "ERROR: Couldn't fetch a question from the database."
  578. }
  579. #update the times used
  580. set trivia_q_id [mysqlescape $trivia_q_id]
  581. set sql "UPDATE questions SET count = count + 1 WHERE question_id = '$trivia_q_id'"
  582. mysqlexec $trivia_db_handle $sql
  583. }
  584. #>>>
  585. # Start a round
  586. proc trivia_start_round { } {
  587. #<<<
  588. global trivia_q_id trivia_q_cat trivia_q_question trivia_q_answer trivia_q_hint trivia_q_attempts trivia_channel trivia_status trivia_last_qid
  589. if {$trivia_status != 1} {
  590. #we're switched off, abort
  591. return 0
  592. }
  593. #init variables
  594. set trivia_q_id ""
  595. set trivia_q_cat ""
  596. set trivia_q_question ""
  597. set trivia_q_answer ""
  598. set trivia_q_hint ""
  599. putlog "Fetching next question..."
  600. trivia_get_question
  601. if {$trivia_q_id == ""} {
  602. putlog "Couldn't init trivia round, aborting."
  603. return
  604. }
  605. putlog "Successfully fetched question $trivia_q_id from database"
  606. putloglev 4 * "question = $trivia_q_question"
  607. putloglev 4 * "answer = $trivia_q_answer"
  608. set trivia_q_attempts 1
  609. set trivia_last_qid $trivia_q_id
  610. trivia_round
  611. }
  612. #>>>
  613. # Run a round
  614. proc trivia_round { } {
  615. #<<<
  616. global trivia_q_id trivia_q_cat trivia_q_question trivia_q_answer trivia_q_hint trivia_q_attempts trivia_channel trivia_status
  617. global trivia_timer trivia_speed trivia_c trivia_last_ts
  618. if {$trivia_status != 1} {
  619. #we're switched off, abort
  620. return 0
  621. }
  622. if {$trivia_q_attempts == 5} {
  623. trivia_end_round
  624. return 0
  625. }
  626. if {$trivia_q_answer == ""} {
  627. return 0
  628. }
  629. #update the hint
  630. set trivia_q_hint [trivia_make_hint $trivia_q_hint $trivia_q_answer]
  631. #say our stuff
  632. if {$trivia_q_attempts > 1} {
  633. set hint " \[[expr $trivia_q_attempts - 1] of 3\]"
  634. } else {
  635. set hint ""
  636. }
  637. putserv "PRIVMSG $trivia_channel :$trivia_c(red)--== Trivia ==--$trivia_c(off) \[category: \002$trivia_q_cat\002\]"
  638. #\[question id: \002$trivia_q_id\002\]"
  639. set split_question [trivia_question_split $trivia_q_question]
  640. foreach q $split_question {
  641. if {$q != ""} {
  642. putserv "PRIVMSG $trivia_channel :$trivia_c(blue) [trivia_question_inject $q]"
  643. }
  644. }
  645. #set new_question [trivia_question_inject $trivia_q_question]
  646. #putserv "PRIVMSG $trivia_channel :$trivia_c(blue) $new_question"
  647. putserv "PRIVMSG $trivia_channel :Hint$hint: [trivia_explode $trivia_q_hint]"
  648. incr trivia_q_attempts
  649. set trivia_last_ts [clock seconds]
  650. global trivia_must_rehash
  651. if {$trivia_must_rehash != 2} {
  652. set trivia_timer [utimer $trivia_speed trivia_round]
  653. }
  654. }
  655. #>>>
  656. # Make it harder for things to break questions (inject bold codes)
  657. proc trivia_question_inject { question } {
  658. #<<<
  659. putlog "trivia_question_inject $question"
  660. #inject random bold/underline/colour codes into question
  661. global trivia_c
  662. set l [string length $question]
  663. putlog "length: $l"
  664. if {$l < 1} {
  665. return $question
  666. }
  667. set pos [rand $l]
  668. set first [string range $question 0 $pos]
  669. incr pos
  670. set second [string range $question $pos end]
  671. switch [rand 1] {
  672. 0 {
  673. putlog "question_inject: using bold at pos $pos"
  674. return $first$trivia_c(bold)$trivia_c(bold)$second
  675. }
  676. 1 {
  677. putlog "question_inject: using purple at pos $pos"
  678. return $first$trivia_c(purple)$trivia_c(off)$second
  679. }
  680. }
  681. return $question
  682. }
  683. #>>>
  684. # Make it harder for things to break questions (inject line breaks)
  685. proc trivia_question_split { question } {
  686. #<<<
  687. putlog "trivia_question_split $question"
  688. #explodes a question into two lines at word boundaries, if it's long enough
  689. #don't split unscrambles incorrectly
  690. if [regexp -nocase "(unscramble .+:) (\[A-Z \]+)" $question matches first second] {
  691. return [list $first $second]
  692. }
  693. set words [split $question " "]
  694. set wordcount [llength $words]
  695. if {$wordcount < 4} {
  696. putlog "aborting, too short"
  697. return [list $question]
  698. }
  699. #enough words to split
  700. #we want at least two on the first line
  701. putlog "wordcount: $wordcount"
  702. incr wordcount -2
  703. if {$wordcount < 0} {
  704. return [list $question]
  705. }
  706. set pos [rand $wordcount]
  707. incr pos 2
  708. putlog "picked pos $pos"
  709. set wordlist [list]
  710. set i 0
  711. set line ""
  712. foreach word $words {
  713. #putlog "word = $word, i = $i"
  714. append line "$word "
  715. if {$i == $pos} {
  716. #putlog "splitting here, line = $line"
  717. lappend wordlist [string trim $line]
  718. set line ""
  719. }
  720. incr i
  721. }
  722. #putlog "done, appending $line to list"
  723. if {$line != ""} {
  724. lappend wordlist [string trim $line]
  725. }
  726. #putlog "split question list is: $wordlist"
  727. return $wordlist
  728. }
  729. #>>>
  730. # Finish a round (without a winner)
  731. proc trivia_end_round { } {
  732. #<<<
  733. global trivia_q_id trivia_q_cat trivia_q_question trivia_q_answer trivia_q_hint trivia_q_attempts trivia_channel trivia_status
  734. global trivia_timer trivia_delay trivia_db_handle trivia_unanswered
  735. global trivia_run_last trivia_run_nick trivia_run_qty trivia_c
  736. if {$trivia_status != 1} {
  737. #we're switched off, abort
  738. return 0
  739. }
  740. set trivia_q_answer [string toupper $trivia_q_answer]
  741. putquick "PRIVMSG $trivia_channel :Time's up! Nobody got it right. The answer was$trivia_c(purple) $trivia_q_answer"
  742. set trivia_q_answer ""
  743. incr trivia_unanswered
  744. if {$trivia_run_qty > 2} {
  745. putserv "PRIVMSG $trivia_channel :So much for $trivia_run_nick's winning spree."
  746. }
  747. set trivia_run_last 0
  748. set trivia_run_qty 0
  749. set trivia_run_nick ""
  750. if {[rand 100] > 90} {
  751. putserv "PRIVMSG $trivia_channel :Remember, to report a problem with a question or answer, type $trivia_c(purple)!trivia report <description of problem>"
  752. }
  753. if {$trivia_unanswered > 3} {
  754. putserv "PRIVMSG $trivia_channel :Three unanswered in a row, stopping the game."
  755. set trivia_status 0
  756. } else {
  757. set trivia_timer [utimer $trivia_delay trivia_start_round]
  758. trivia_check_rehash
  759. }
  760. }
  761. #>>>
  762. # Skip the rest of this question
  763. proc trivia_skip { nick } {
  764. #<<<
  765. global trivia_q_id trivia_q_cat trivia_q_question trivia_q_answer trivia_q_hint trivia_q_attempts trivia_channel trivia_status
  766. global trivia_timer trivia_delay trivia_db_handle trivia_unanswered
  767. if {$trivia_status != 1} {
  768. #we're switched off, abort
  769. return 0
  770. }
  771. if {$trivia_q_answer == ""} {
  772. #no round in progress
  773. return 0
  774. }
  775. set trivia_q_question ""
  776. set trivia_q_answer ""
  777. trivia_killtimer
  778. putserv "PRIVMSG $trivia_channel :Skipping this question by $nick's request."
  779. set trivia_timer [utimer $trivia_delay trivia_start_round]
  780. }
  781. #>>>
  782. # Utility function to fetch a random item from a list
  783. proc trivia_random_element { l } {
  784. #<<<
  785. return [lindex $l [rand [llength $l]]]
  786. }
  787. #>>>
  788. # Start the game
  789. proc trivia_start { } {
  790. #<<<
  791. global trivia_status trivia_channel trivia_unanswered trivia_run_last trivia_run_qty trivia_c trivia_must_rehash trivia_db_handle
  792. if {$trivia_status == 1} {
  793. putserv "PRIVMSG $trivia_channel :Trivia is already running."
  794. return 0
  795. }
  796. if {$trivia_status == -1} {
  797. putserv "PRIVMSG $trivia_channel :Trivia is currently disabled."
  798. return 0
  799. }
  800. set alive [::mysql::ping $trivia_db_handle]
  801. if {$alive == false} {
  802. putlog "ERROR: mysql has gone away :("
  803. putquick "PRIVMSG $trivia_channel :Unable to start game; can't reach database :("
  804. }
  805. # go go go
  806. set trivia_status 1
  807. if {$trivia_must_rehash == 2 } {
  808. putquick "PRIVMSG $trivia_channel :$trivia_c(blue)### Resuming trivia game ###" -next
  809. } else {
  810. putquick "PRIVMSG $trivia_channel :Trivia game started!" -next
  811. }
  812. #trivia_stats
  813. set trivia_unanswered 0
  814. set trivia_run_last 0
  815. set trivia_run_qty 0
  816. trivia_start_round
  817. return 0
  818. }
  819. #>>>
  820. # Stop the game
  821. proc trivia_stop { } {
  822. #<<<
  823. global trivia_channel trivia_status
  824. if {$trivia_status == 1} {
  825. putserv "PRIVMSG $trivia_channel :Trivia game stopped."
  826. set trivia_status 0
  827. trivia_killtimer
  828. }
  829. return 0
  830. }
  831. #>>>
  832. # Kill our timer
  833. proc trivia_killtimer { } {
  834. #<<<
  835. global trivia_timer
  836. if {$trivia_timer != ""} {
  837. killutimer $trivia_timer
  838. }
  839. }
  840. #>>>
  841. # Utility function to explode line into letters
  842. proc trivia_explode { line } {
  843. #<<<
  844. set letters [split $line {}]
  845. set newline ""
  846. foreach letter $letters {
  847. append newline "$letter "
  848. }
  849. return [string trim $newline]
  850. }
  851. #>>>
  852. # Get a UID's ranking
  853. proc trivia_get_rank { uid } {
  854. #<<<
  855. global trivia_db_handle
  856. putloglev 4 * "trivia_get_rank ($uid)"
  857. set sql "SELECT user_id FROM users ORDER by user_score DESC"
  858. set dt [trivia_get_period]
  859. set sql "select user_id, count(dt) as d from scores where dt > $dt group by user_id order by d desc"
  860. set result [mysqlsel $trivia_db_handle $sql -flatlist]
  861. set pos [lsearch -exact $result $uid]
  862. return [expr $pos + 1]
  863. }
  864. #>>>
  865. # Get the top 10 users
  866. proc trivia_get_top10 { } {
  867. #<<<
  868. global trivia_db_handle
  869. set sql "SELECT user_name, user_score FROM users ORDER BY user_score DESC LIMIT 10"
  870. set dt [trivia_get_period]
  871. set sql "select users.user_name, count(dt) as d from scores left join users using (user_id) where dt > $dt group by scores.user_id order by d desc limit 10"
  872. set result [mysqlsel $trivia_db_handle $sql -list]
  873. set output ""
  874. set index 0
  875. foreach place $result {
  876. incr index
  877. append output "\002$index:\002 "
  878. append output [lindex $place 0]
  879. append output " ("
  880. append output [lindex $place 1]
  881. append output ") "
  882. }
  883. set output "The top $index players are... $output"
  884. return $output
  885. }
  886. #>>>
  887. # Fix a number into text
  888. proc trivia_ordinal { number } {
  889. #<<<
  890. if [regexp {1[0-9]$} $number] {
  891. return "th"
  892. }
  893. set last [string range $number end end]
  894. if {$last == "1"} {
  895. return "st"
  896. }
  897. if {$last == "2"} {
  898. return "nd"
  899. }
  900. if {$last == "3"} {
  901. return "rd"
  902. }
  903. return "th"
  904. }
  905. #>>>
  906. # Get a score
  907. proc trivia_score { n { nick "" } } {
  908. #<<<
  909. set n [string trim $n]
  910. putloglev 4 * "trivia_score($n, $nick)"
  911. if {$n == ""} {
  912. set ni $nick
  913. set o "You are"
  914. } else {
  915. set ni $n
  916. set o "$n is"
  917. }
  918. set uid [trivia_get_uid $ni]
  919. set score [trivia_get_score $uid]
  920. set pos [trivia_get_rank $uid]
  921. if {$score == 0} {
  922. return "No score found for $n."
  923. }
  924. return "$o ranked $pos[trivia_ordinal $pos] with $score points."
  925. }
  926. #>>>
  927. # Get the scores around a UID
  928. proc trivia_near_five { uid } {
  929. #<<<
  930. global trivia_db_handle trivia_c
  931. set sql "SELECT user_id, user_name, user_score FROM users ORDER BY user_score DESC"
  932. set dt [trivia_get_period]
  933. set sql "select users.user_id, users.user_name, count(dt) as user_score from scores left join users using (user_id) where dt > $dt group by scores.user_id order by user_score desc"
  934. set result [mysqlsel $trivia_db_handle $sql -flatlist]
  935. set pos [lsearch -exact $result $uid]
  936. #we use +- 6 here because it's a flat list, and we want 2 nodes either side
  937. putlog $result
  938. set near_five [lrange $result [expr $pos - 6] [expr $pos + 8]]
  939. putlog $near_five
  940. set low_pos [expr round(($pos-1) / 3)]
  941. if {$low_pos < 1} {
  942. set low_pos 1
  943. }
  944. putlog $low_pos
  945. set i 0
  946. set output ""
  947. while {$i < [llength $near_five]} {
  948. set user_id [lindex $near_five $i]
  949. incr i
  950. set nick [lindex $near_five $i]
  951. incr i
  952. set score [lindex $near_five $i]
  953. incr i
  954. if {$uid == $user_id} {
  955. append output "$trivia_c(purple)$trivia_c(bold)"
  956. }
  957. append output "$low_pos[trivia_ordinal $low_pos]:"
  958. append output "$nick"
  959. append output " ($score) "
  960. if {$uid == $user_id} {
  961. append output "$trivia_c(off)$trivia_c(bold)"
  962. }
  963. incr low_pos
  964. }
  965. return $output
  966. }
  967. #>>>
  968. # Turn a list into a stats list
  969. proc trivia_list_to_stats { pos user_id l } {
  970. #<<<
  971. putloglev 4 * "trivia_list_to_stats ($pos, $user_id, $l)"
  972. global trivia_c
  973. set uid [lindex $l 0]
  974. set nick [lindex $l 1]
  975. set score [lindex $l 2]
  976. incr pos
  977. set result ""
  978. if {$user_id == $uid} {
  979. set result "$trivia_c(purple)$trivia_c(bold)"
  980. }
  981. append result "$pos[trivia_ordinal $pos]: $nick ($score) "
  982. if {$user_id == $uid} {
  983. append result "$trivia_c(off)$trivia_c(bold)"
  984. }
  985. return $result
  986. }
  987. #>>>
  988. # Another function to get the nearest users to your score
  989. proc trivia_near_five2 { uid } {
  990. #<<<
  991. global trivia_db_handle trivia_c
  992. set sql "SELECT user_id, user_name, user_score FROM users ORDER BY user_score DESC"
  993. set dt [trivia_get_period]
  994. set sql "select users.user_id, users.user_name, count(dt) as user_score from scores left join users using (user_id) where dt > $dt group by scores.user_id order by user_score desc"
  995. set result [mysqlsel $trivia_db_handle $sql -list]
  996. putloglev d * "current score list: $result"
  997. set position 0
  998. foreach item $result {
  999. if {[lindex $item 0] == $uid} {
  1000. putloglev d * "found at position $position"
  1001. break
  1002. }
  1003. incr position
  1004. }
  1005. set line ""
  1006. if {$position > 2} {
  1007. append line [trivia_list_to_stats [expr $position - 2] $uid [lindex $result [expr $position - 2]]]
  1008. }
  1009. if {$position > 1} {
  1010. append line [trivia_list_to_stats [expr $position - 1] $uid [lindex $result [expr $position - 1]]]
  1011. }
  1012. append line [trivia_list_to_stats $position $uid [lindex $result $position]]
  1013. if {$position < [expr [llength $result] - 1]} {
  1014. append line [trivia_list_to_stats [expr $position + 1] $uid [lindex $result [expr $position + 1]]]
  1015. }
  1016. if {$position < [expr [llength $result] - 2]} {
  1017. append line [trivia_list_to_stats [expr $position + 2] $uid [lindex $result [expr $position + 2]]]
  1018. }
  1019. return $line
  1020. }
  1021. #>>>
  1022. # Get some stats from the DB
  1023. proc trivia_stats { } {
  1024. #<<<
  1025. global trivia_db_handle trivia_channel
  1026. set sql "SELECT COUNT(*) AS total FROM questions LEFT JOIN categories USING (cat_id) WHERE categories.cat_enabled = 1;"
  1027. set result [mysqlsel $trivia_db_handle $sql -flatlist]
  1028. set stat_total_questions [lindex $result 0]
  1029. set sql "SELECT COUNT(*) AS total FROM categories WHERE cat_enabled = 1"
  1030. set result [mysqlsel $trivia_db_handle $sql -flatlist]
  1031. set stat_total_cats [lindex $result 0]
  1032. putserv "PRIVMSG $trivia_channel :Using\002 $stat_total_questions\002 questions in\002 $stat_total_cats\002 categories."
  1033. }
  1034. #>>>
  1035. # Merge two users
  1036. proc trivia_merge { nick param } {
  1037. #<<<
  1038. global trivia_db_handle
  1039. putloglev 4 * "trivia_merge ($nick, $param)"
  1040. if [regexp {^([^ ]+) (.+)$} $param matches nick1 nick2] {
  1041. set olduid [trivia_get_uid [mysqlescape $nick1]]
  1042. set newuid [trivia_get_uid [mysqlescape $nick2]]
  1043. if {$olduid == 0} {
  1044. puthelp "PRIVMSG $nick :Can't find user '$nick1'"
  1045. return 0
  1046. }
  1047. if {$newuid == 0} {
  1048. puthelp "PRIVMSG $nick :Can't find user '$nick2'"
  1049. return 0
  1050. }
  1051. puthelp "PRIVMSG $nick :Meging score for $nick1 into score for $nick2"
  1052. set sql "UPDATE scores SET user_id=$newuid WHERE user_id=$olduid"
  1053. mysqlexec $trivia_db_handle $sql
  1054. set newscore [trivia_get_score $newuid]
  1055. puthelp "PRIVMSG $nick :$nick2 now has $newscore points."
  1056. set sql "DELETE FROM users WHERE user_id = $olduid"
  1057. mysqlexec $trivia_db_handle $sql
  1058. puthelp "PRIVMSG $nick :User $nick1 has been deleted."
  1059. return 0
  1060. } else {
  1061. puthelp "PRIVMSG $nick :Use: !trivia merge <olduser> <newuser>"
  1062. puthelp "PRIVMSG $nick : olduser's score is merged with newuser's and olduser is deleted."
  1063. }
  1064. }
  1065. #>>>
  1066. # Find people who can start the script
  1067. # TODO: is this still used?
  1068. proc trivia_find_starters { } {
  1069. #<<<
  1070. global trivia_channel trivia_flag
  1071. set nicks [chanlist #triviacow &$trivia_flag]
  1072. if {[llength $nicks] > 0} {
  1073. return "One of the following users may be able to help: $nicks"
  1074. } else {
  1075. return "Sorry, noone who can start trivia is present."
  1076. }
  1077. }
  1078. #>>>
  1079. # Report a broken question
  1080. proc trivia_report { nick msg } {
  1081. #<<<
  1082. global trivia_channel trivia_db_handle trivia_last_qid trivia_c
  1083. if {$trivia_last_qid == 0} {
  1084. puthelp "PRIVMSG $trivia_channel :Sorry, unable to work out which question to report on :("
  1085. return 0
  1086. }
  1087. set nick [mysqlescape $nick]
  1088. set msg [mysqlescape $msg]
  1089. set sql "INSERT INTO reports VALUES (null, UNIX_TIMESTAMP(), '$nick', '$trivia_last_qid', '$msg', 'N')"
  1090. mysqlexec $trivia_db_handle $sql
  1091. puthelp "PRIVMSG $trivia_channel :Added a report against question ID$trivia_c(purple) $trivia_last_qid$trivia_c(off) from $trivia_c(purple)$nick$trivia_c(off)."
  1092. set trivia_last_qid 0
  1093. }
  1094. #>>>
  1095. ### WATCHDOG STUFF <<<
  1096. # Watchdog timer to make sure we're not ruined
  1097. proc trivia_watchdog { } {
  1098. #<<<
  1099. global trivia_last_ts trivia_watchdog_timer trivia_status trivia_channel
  1100. putloglev d * "trivia watchdog: tick"
  1101. if {$trivia_last_ts == 0} {
  1102. #never asked a question
  1103. set trivia_watchdog_timer [utimer 45 trivia_watchdog]
  1104. return 0
  1105. }
  1106. set current_ts [clock seconds]
  1107. set difference [expr $current_ts - $trivia_last_ts]
  1108. if {$difference > 60} {
  1109. if {$trivia_status == 1} {
  1110. putlog "watchdog: trivia is broken"
  1111. #putserv "PRIVMSG $trivia_channel :Oops, I think I'm broken. Attempting to recover..."
  1112. #set trivia_status 0
  1113. #trivia_start
  1114. putlog "trivia watchdog: current: $current_ts, last: $trivia_last_ts, difference: $difference"
  1115. }
  1116. }
  1117. set trivia_watchdog_timer [utimer 10 trivia_watchdog]
  1118. return 0
  1119. }
  1120. #>>>
  1121. # Kill the watchdog timeer
  1122. proc trivia_killwatchdog { } {
  1123. #<<<
  1124. global trivia_watchdog_timer
  1125. if {$trivia_watchdog_timer != ""} {
  1126. killutimer $trivia_watchdog_timer
  1127. }
  1128. }
  1129. #>>>
  1130. trivia_killwatchdog
  1131. set trivia_watchdog_timer [utimer 45 trivia_watchdog]
  1132. #>>>
  1133. trivia_connect
  1134. putlog {TriviaEngine ENGAGED(*$£&($}
  1135. if {$trivia_must_rehash == 2} {
  1136. putlog "Auto-restarting trivia..."
  1137. trivia_start
  1138. set trivia_must_rehash 0
  1139. }