#pragma mode( separator(.,;) integer(h32) )

// ============================================================
// TESTCHOOSER - beta 2a
// Which statistical test?  A tappable flowchart, the table,
// and a quiz
// ============================================================
// Author: Roger Metcalf, 2026.  Written with AI assistance
//   (Anthropic Claude); concept, direction and all hardware
//   testing by the author.
//
// The scheme is the one Stanton Glantz uses in "Primer of
//   Biostatistics" (McGraw-Hill): the scale of the outcome
//   variable (interval, nominal, ordinal, survival) against the
//   design (two groups, three or more groups, before and after,
//   repeated conditions, association).  His 1992 DOS program for
//   the book opened with "What kind of measurement scale was
//   used?" -- the same first question this program asks.  The
//   text, the Prime pointers and the code are new.
//
// FREE FOR EDUCATIONAL AND PERSONAL USE.  Keep this header with
//   the file and credit the author if you pass it on.  Not for
//   sale or commercial redistribution.  Provided as is, with no
//   warranty.  A chooser suggests; the analyst decides.
// DISCLAIMER: a teaching and demonstration aid. Not validated for
//   forensic, clinical or legal use; do not rely on its results
//   for legal purposes.
//
// Firmware: written for the 2026-09-09 firmware (2.4.2).  This
//   version has NOT yet been run on hardware or on the emulator,
//   only in the PC harness (ppl_run_v3e, 19 Oct 2026); beta 2
//   passed the emulator (HP Prime Virtual Calculator, build
//   2026-09-09, 17 Oct 2026).
//
// beta 2a (19 Oct 2026) - what changed: T1, T2, T3 and three
//   wordings (fleet review 19 Oct 2026, findings_B).  Line numbers
//   refer to beta 2.  No test, quiz case or key changed.
//   - T1: three details pages sent students to ConTable2 and FEP,
//     which are retired: EpiStats v1.6 took them over, in its module
//     1, "2x2 Table Analysis", with its Paired reading (RR and OR are
//     also in its module 2, "Risk Analysis").  Chi-square 2x2 (291):
//     "Fleet: EpiStats > 2x2 Table; ChiTable."  McNemar (302-303):
//     "Fleet: EpiStats > 2x2 Table > Paired." and, under it,
//     "By hand, 1 df: X2 =
//     (|b-c| - 1)^2/(b+c)." (the same statistic, re-flowed).  RR / OR
//     (314): "Fleet: EpiStats > Risk; Inference X2."
//   - T2: the menu item "Quiz: which test fits the case?" (1759) was
//     31 characters, and the emulator cut menu lines of 31 to 36
//     characters at the box's edge: now "Quiz: which test fits?".
//   - T3: the two "(none)" survival cells were hatched differently:
//     test 18 (before/after) says "Not on the Prime" on its fourth
//     line, test 19 on its third, and tcNotP (1737) had only 19.
//     Both are hatched now (the legend: hatched = not on the
//     Prime); thirteen hatched cells, not twelve.
//   - Wording, where the text claimed more than is true:
//     the log-rank test (349) does not ASSUME proportional hazards,
//     it is most powerful under them: "Most powerful if hazards are
//     proportional."; Cochran's rule (295) is not "most expected
//     counts at least 5" but "Expected < 5 in <= 20% of cells;
//     none < 1."; cochran.qtest (308) is not base R but the
//     RVAideMemoire package: "Not on the Prime.  R: RVAideMemoire
//     pkg." (the function's name no longer fits the 42-character
//     line; the package is what has to be installed).
//   - Not changed: "Welch form if SDs differ" (259) stays until
//     Roger decides D12 (Welch by default).
//
// beta 2 (16 Oct 2026) - what changed: the touch and graphics
//   version from the fleet review (section 6), built on beta 1b
//   with every beta 1b fix kept.  Line numbers refer to beta 1b.
//   - GUIDE = A TAPPABLE FLOWCHART.  It replaces the two button
//     pages (tcPick, 396-446) and the path screen (tcShowPath,
//     488-538).  The root question "outcome scale?" has four
//     scale nodes; tap one and the other three grey out, it rises
//     to the top as a breadcrumb and its six design nodes drop
//     in.  Tap a design and the path lights up in orange, drawn
//     over six frames, ending in the leaf box with the test's
//     name; tap the leaf (or Enter) for the details.  Back = up
//     one level.  Arrows and Enter still do every step.  Boxes
//     are RECT_P, connectors LINE_P, arrowheads the three-line
//     kind the path screen used (506-508) -- no FILLPOLY_P.
//   - SOFT KEYS Guide, Table, Quiz, Back, Quit on every screen
//     (DRAWMENU after the blit, as ChiTable does); every footer
//     moved above y 218.
//   - TABLE: the first tap selects a cell and names the test in
//     the status line; a second tap on the same cell opens it
//     (beta 1b opened on the first tap, 645-655).  Tap a row
//     label to light the whole row, a column heading to light
//     the column (or + and -).  The one-sample column is a
//     second page: tap the corner key, or press Left from the
//     first column.  Cells stay 53 px: at 44 px the 8-character
//     cell labels would clip.
//   - QUIZ (new): twenty criminal-justice and forensic cases in
//     random order (RANDOM, no repeats in a round).  Tap the cell
//     for the right test: right = green flash and the details
//     page; wrong = red flash, the right cell outlined in green
//     and one line on why.  The score is shown.
//   - The twelve "Not on the Prime" tests are hatched with light
//     (thirteen since beta 2a, T3)
//     grey PIXON_P dots; a legend line says so.
//   - 14 x 12 pictograms in the column headings and row labels
//     (bars, joined dots, a scatter, a step-down curve).  They do
//     not fit inside the cells beside the text.
//   - Details page: a "Check:" line under the assumptions where
//     the fleet can help (Statistics 1Var normal probability plot,
//     ChiTable's expected counts, NormExplorer's data module).
//   - Help: two pages, with every new gesture and soft key.
//   - The table's background (cells, hatching, pictograms) is
//     drawn once into G2 and copied to G1 for each frame, so the
//     hatching is not redrawn on every key press.
// beta 1b (16 Oct 2026) - what changed: fixes from the fleet review
//   of 16 Oct.  Line numbers refer to beta 1a.
//   - The parameter ssS of tcScaleShort (88) was SSS, a built-in
//     command (the Prime ignores case) -- the fault that stopped
//     ChiTable's first paste (csC = CSC).  Now ssSc.
//   - The menu items carry no numbers; CHOOSE numbers them itself
//     (688-690).
//   - Footers in the house form, comma-separated with spaces round
//     "=" (399, 455, 511-512, 604, 664).  The up/down and left arrow
//     glyphs CHAR(8593), CHAR(8595) and CHAR(8592), which no fleet
//     program has shown on hardware, are replaced by the verified
//     up-down and left-right arrow glyphs and by the word "Left".
//   - Help page: the table shows designs 2-6, so it no longer claims
//     a one-sample column; the one-sample tests are reached through
//     the Guide (662-663).
//   - The row labels "Interval" and "Survival" get the whole label
//     column (x 1, width 53; was x 2, width 51), so they are less
//     likely to clip (581, 585).  Check on the device.
//   - tcDetails: the dead dtS == 0 branch is gone -- both callers
//     always pass the scale (436-437, 441-443); the stale box-width
//     comment is corrected, box 3 being 88 px (467-468).
//   - The program ends with RETURN "TestChooser: " + TC_TEST
//     instead of falling off END (699).
// beta 1a (11 Oct 2026): every "wait for the finger to lift" loop
//   now goes through tcDrain(), which gives up after one second, so
//   a phantom touch cannot freeze the program.  Nothing else changed.
//
// Run TestChooser() from the Program Catalog.
// ============================================================
//
// THREE WAYS IN (the menu, or the soft keys on any screen)
//   Guide ......... a flowchart: tap the outcome scale, then the
//                   design; the orange path ends at the test
//   Table ......... Glantz's table, scale x design; tap a cell
//                   twice (or the arrows and Enter) for details
//   Quiz .......... read a case, tap the test that fits it
//
// SOFT KEYS (the strip along the bottom of every screen)
//   Guide  Table  Quiz  (blank)  Back  Quit
//   Back = up one level; the Esc key does the same.  The soft
//   keys send no key code, so every one of them also has a key.
//
// EVERY TEST PAGE SAYS
//   what it compares, what it assumes (and how to check it, where
//   the Prime can), where to find it on the Prime (Inference app,
//   Statistics apps, or a fleet program), what to do afterwards
//   (post hoc tests), and the alternative when an assumption fails.
//
// HOME EXPORT
//   TC_TEST  the name of the test last looked at
//
// LAYOUT NOTE
//   Font 1 is about 6.5 px per character and font 2 about 8, so
//   a font-1 line that starts at x = 14 holds about 42 characters
//   and a font-2 line about 37.  Every string here is kept inside
//   those limits, and the detail lines also pass TEXTOUT_P a
//   width so that nothing can run over the right-hand edge.
//   The soft keys paint y 218-239, so nothing is drawn there.
// ============================================================


EXPORT TC_TEST;

// ---- session state (set in tcInit) ----
LOCAL tcRow, tcDes, tcOff, tcHiR, tcHiC;
//  table cursor: row 1..4 (scale) and design 1..6.  tcOff = 1
//  shows designs 2-6 (Glantz's table), 0 shows designs 1-5 (the
//  one-sample column).  tcHiR / tcHiC: the lit row or column, 0 = none
LOCAL tcBLUE, tcMAG, tcORNG, tcGRN, tcGRAY, tcFOOT;
LOCAL tcLBLU, tcLMAG, tcLORN, tcLGRN, tcLGRY, tcDODG;
LOCAL tcLDOD, tcRED, tcLRED, tcHATCH, tcPALE;
// the last tap, left here by tcWaitAny (-1 when it was a key)
LOCAL tcTX, tcTY;
// hit boxes, recorded while a flowchart screen is drawn
LOCAL tcHN, tcHX1, tcHY1, tcHX2, tcHY2, tcHId;
// the flowchart's remembered scale and design
LOCAL tcGS, tcGD;
// table geometry for browsing or the quiz (set in tcGeom)
LOCAL tcQz, tcTY0, tcRH, tcHY, tcCY1, tcCY2;
LOCAL tcPY, tcCK, tcNotP;
// the quiz: bank size, the round's order, where we are, the score
LOCAL tcQN, tcQOrd, tcQPos, tcQCur, tcQRight, tcQAsk;


// ------------------------------------------------------------
// THE KNOWLEDGE: names, table cells, and five lines per test
// ------------------------------------------------------------

// Test id for a scale (1 interval, 2 nominal, 3 ordinal, 4 survival)
// and a design (1 one group vs a value, 2 two independent groups,
// 3 three or more groups, 4 before/after same people, 5 several
// conditions same people, 6 association of two variables).
tcIdFor(idS, idD)
BEGIN
  LOCAL idMap;
  idMap := {21, 1, 2, 3, 4, 5,
            22, 6, 7, 8, 9, 10,
            23, 11, 12, 13, 14, 15,
            24, 16, 17, 18, 19, 20};
  RETURN idMap((idS - 1) * 6 + idD);
END;

// The scale, in one word (fits a 96-pixel box).
tcScaleShort(ssSc)
BEGIN
  IF ssSc == 1 THEN RETURN "interval"; END;
  IF ssSc == 2 THEN RETURN "nominal"; END;
  IF ssSc == 3 THEN RETURN "ordinal"; END;
  RETURN "survival";
END;

// The design, short enough for a subtitle (at most 26 characters).
tcDesignShort(dsD)
BEGIN
  IF dsD == 1 THEN RETURN "1 group vs a value"; END;
  IF dsD == 2 THEN RETURN "2 independent groups"; END;
  IF dsD == 3 THEN RETURN "3+ independent groups"; END;
  IF dsD == 4 THEN RETURN "before/after, same people"; END;
  IF dsD == 5 THEN RETURN "3+ conditions, same people"; END;
  RETURN "association of 2 variables";
END;

// The full name of a test (at most 37 characters: one font-2 line).
tcFull(fnI)
BEGIN
  CASE
    IF fnI == 1 THEN RETURN "Unpaired (two-sample) t test"; END;
    IF fnI == 2 THEN RETURN "One-way ANOVA"; END;
    IF fnI == 3 THEN RETURN "Paired t test"; END;
    IF fnI == 4 THEN RETURN "Repeated-measures ANOVA"; END;
    IF fnI == 5 THEN RETURN "Pearson r and linear regression"; END;
    IF fnI == 6 THEN RETURN "Chi-square 2x2, or Fisher's exact"; END;
    IF fnI == 7 THEN RETURN "Chi-square for an r x c table"; END;
    IF fnI == 8 THEN RETURN "McNemar's test"; END;
    IF fnI == 9 THEN RETURN "Cochran's Q test"; END;
    IF fnI == 10 THEN RETURN "Relative risk, odds ratio, chi-square"; END;
    IF fnI == 11 THEN RETURN "Mann-Whitney U (Wilcoxon rank-sum)"; END;
    IF fnI == 12 THEN RETURN "Kruskal-Wallis test"; END;
    IF fnI == 13 THEN RETURN "Wilcoxon signed-rank test"; END;
    IF fnI == 14 THEN RETURN "Friedman test"; END;
    IF fnI == 15 THEN RETURN "Spearman rank correlation"; END;
    IF fnI == 16 THEN RETURN "Log-rank test (two groups)"; END;
    IF fnI == 17 THEN RETURN "Log-rank test (several groups)"; END;
    IF fnI == 18 THEN RETURN "Before/after survival: no usual test"; END;
    IF fnI == 19 THEN RETURN "Repeated survival: no standard test"; END;
    IF fnI == 20 THEN RETURN "Cox proportional-hazards regression"; END;
    IF fnI == 21 THEN RETURN "One-sample t test (z if sigma known)"; END;
    IF fnI == 22 THEN RETURN "One-proportion z / chi-square GOF"; END;
    IF fnI == 23 THEN RETURN "One-sample Wilcoxon signed-rank/sign"; END;
    DEFAULT RETURN "One-sample survival comparison";
  END;
END;

// The two short lines shown in a table cell (at most 8 characters each).
tcCell(clI)
BEGIN
  CASE
    IF clI == 1 THEN RETURN {"t test", "indep."}; END;
    IF clI == 2 THEN RETURN {"one-way", "ANOVA"}; END;
    IF clI == 3 THEN RETURN {"paired", "t test"}; END;
    IF clI == 4 THEN RETURN {"repeated", "ANOVA"}; END;
    IF clI == 5 THEN RETURN {"Pearson", "r, regr."}; END;
    IF clI == 6 THEN RETURN {"chi-sq./", "Fisher"}; END;
    IF clI == 7 THEN RETURN {"chi-sq.", "r x c"}; END;
    IF clI == 8 THEN RETURN {"McNemar", ""}; END;
    IF clI == 9 THEN RETURN {"Cochran", "Q"}; END;
    IF clI == 10 THEN RETURN {"RR, OR,", "chi-sq."}; END;
    IF clI == 11 THEN RETURN {"Mann-", "Whitney"}; END;
    IF clI == 12 THEN RETURN {"Kruskal-", "Wallis"}; END;
    IF clI == 13 THEN RETURN {"Wilcoxon", "signed"}; END;
    IF clI == 14 THEN RETURN {"Friedman", ""}; END;
    IF clI == 15 THEN RETURN {"Spearman", "rho"}; END;
    IF clI == 16 THEN RETURN {"log-rank", ""}; END;
    IF clI == 17 THEN RETURN {"log-rank", "k groups"}; END;
    IF clI == 18 THEN RETURN {"(none)", ""}; END;
    IF clI == 19 THEN RETURN {"(none)", ""}; END;
    DEFAULT RETURN {"Cox", "regress."};
  END;
END;

// Five lines about a test: what it compares, what it assumes, where
// it lives on the Prime, what to do afterwards, and the alternative.
// Each line is at most 42 characters (font 1 from x = 14).
tcDetail(dtI)
BEGIN
  CASE
    IF dtI == 1 THEN RETURN {
      "Means of two independent groups.",
      "Normal-ish data; Welch form if SDs differ.",
      "Inference > Hyp. test > T-Test: mu1 - mu2.",
      "Also T-Int: mu1 - mu2 for the difference.",
      "Not normal or ordinal?  Mann-Whitney U."}; END;
    IF dtI == 2 THEN RETURN {
      "Means of three or more independent groups.",
      "Normal data, similar spread in each group.",
      "Inference > ANOVA: one list per group.",
      "Significant F?  Tukey HSD, or Dunnett vs",
      "a control; Bonferroni t for a few pairs."}; END;
    IF dtI == 3 THEN RETURN {
      "Matched pairs or before/after: mean diff.",
      "The DIFFERENCES should be roughly normal.",
      "Form the differences, then Inference >",
      "Hyp. test > T-Test: 1 mu, with mu0 = 0.",
      "Skewed differences?  Wilcoxon signed-rank."}; END;
    IF dtI == 4 THEN RETURN {
      "Same subjects, 3 or more conditions.",
      "Normal data; sphericity of the diffs.",
      "Not on the Prime.  R: aov + Error(); JMP.",
      "Post hoc: paired t tests, Bonferroni.",
      "Nonparametric alternative: Friedman."}; END;
    IF dtI == 5 THEN RETURN {
      "Straight-line relation: r and the slope.",
      "Both interval; linear; residuals normal.",
      "Statistics 2Var: r, slope.  Inference >",
      "Regression: t test and CI for the slope.",
      "Curved or ordinal?  Spearman's rho."}; END;
    IF dtI == 6 THEN RETURN {
      "Two proportions; two yes/no traits linked?",
      "Expected counts >= 5; else Fisher's exact.",
      "Inference > X2 test > 2-way (matrix).",
      "Fleet: EpiStats > 2x2 Table; ChiTable.",
      "Report the OR or RR with a 95% CI."}; END;
    IF dtI == 7 THEN RETURN {
      "Proportions in 3 or more groups.",
      "Expected < 5 in <= 20% of cells; none < 1.",
      "Inference > X2 test > 2-way (matrix).",
      "Ordered groups?  TrendTest (X2 for trend).",
      "Which cells differ?  Check contributions."}; END;
    IF dtI == 8 THEN RETURN {
      "Paired yes/no outcomes, same people.",
      "Uses only the discordant pairs b and c.",
      "Fleet: EpiStats > 2x2 Table > Paired.",
      "By hand, 1 df: X2 = (|b-c| - 1)^2/(b+c).",
      "Small b + c?  Exact binomial version."}; END;
    IF dtI == 9 THEN RETURN {
      "Yes/no under 3+ conditions, same subjects.",
      "Extends McNemar; chi-square, k - 1 df.",
      "Not on the Prime.  R: RVAideMemoire pkg.",
      "Then: pairwise McNemar with Bonferroni.",
      "Only two conditions?  That is McNemar."}; END;
    IF dtI == 10 THEN RETURN {
      "Association of two nominal variables.",
      "RR for cohorts, OR for case-control.",
      "Fleet: EpiStats > Risk; Inference X2.",
      "Ordered exposure levels?  TrendTest.",
      "Phi or Cramer's V gives the strength."}; END;
    IF dtI == 11 THEN RETURN {
      "Two independent groups; ordinal/skewed.",
      "Compares the two distributions by ranks.",
      "Not on the Prime.  R: wilcox.test(x, y).",
      "Report both medians and U (or W).",
      "Normal, equal spread?  t test has power."}; END;
    IF dtI == 12 THEN RETURN {
      "Three or more groups, ranks not means.",
      "Rank one-way ANOVA; chi-square, k - 1 df.",
      "Not on the Prime.  R: kruskal.test; JMP.",
      "Post hoc: Dunn's test or pairwise M-W.",
      "Ordered groups?  Jonckheere-Terpstra."}; END;
    IF dtI == 13 THEN RETURN {
      "Paired data, or one sample vs a median.",
      "Ranks |differences|; assumes symmetry.",
      "Not on the Prime.  R: wilcox.test(paired).",
      "Few pairs or many ties?  Sign test.",
      "Normal differences?  Paired t has power."}; END;
    IF dtI == 14 THEN RETURN {
      "Same subjects, 3+ conditions, ranked.",
      "Rank repeated-measures ANOVA; k - 1 df.",
      "Not on the Prime.  R: friedman.test; JMP.",
      "Post hoc: pairwise Wilcoxon, Bonferroni.",
      "Just two conditions?  Wilcoxon signed."}; END;
    IF dtI == 15 THEN RETURN {
      "Monotonic link of two ordinal/skewed vars.",
      "Pearson r on the ranks; -1 to +1.",
      "Rank both lists, then Statistics 2Var r.",
      "R: cor.test(method = 'spearman'); JMP.",
      "Many ties?  Kendall's tau instead."}; END;
    IF dtI == 16 THEN RETURN {
      "Survival curves of two groups, censored.",
      "Most powerful if hazards are proportional.",
      "Not on the Prime.  R: survdiff; JMP.",
      "Show Kaplan-Meier curves; HR from Cox.",
      "Early events matter more?  Gehan-Wilcoxon."}; END;
    IF dtI == 17 THEN RETURN {
      "Survival curves of three or more groups.",
      "Chi-square, k - 1 df; prop. hazards.",
      "Not on the Prime.  R: survdiff; JMP.",
      "Post hoc: pairwise log-rank, Bonferroni.",
      "Ordered groups?  Log-rank test for trend."}; END;
    IF dtI == 18 THEN RETURN {
      "One person has one time to the event.",
      "Recurrent events need special models.",
      "Re-think: compare groups, or use Cox.",
      "Not on the Prime.  R: coxph + cluster().",
      "Is the outcome really a time to an event?"}; END;
    IF dtI == 19 THEN RETURN {
      "As before/after: one time per subject.",
      "Clustered events: frailty/marginal models.",
      "Not on the Prime.  R: coxph + frailty().",
      "Often better framed as grouped survival.",
      "Ask a statistician before collecting data."}; END;
    IF dtI == 20 THEN RETURN {
      "Survival time vs one or more predictors.",
      "Prop. hazards; non-informative censoring.",
      "Not on the Prime.  R: coxph; JMP Fit PH.",
      "Reports hazard ratios with 95% CIs.",
      "One grouping factor only?  Log-rank."}; END;
    IF dtI == 21 THEN RETURN {
      "One group's mean against a given mu0.",
      "Roughly normal, or n large (CLT).",
      "Inference > Hyp. test > T-Test: 1 mu.",
      "Also the T-Int: 1 mu confidence interval.",
      "Skewed data?  Wilcoxon signed-rank/sign."}; END;
    IF dtI == 22 THEN RETURN {
      "One proportion vs pi0; counts vs expected.",
      "n pi0 and n(1-pi0) >= 10; GOF: exp. >= 5.",
      "Inference > Z-Test: 1 pi; X2 > Goodness.",
      "Small n?  Exact binomial via BINOMIAL_CDF.",
      "Report p-hat with its CI (Z-Int: 1 pi)."}; END;
    IF dtI == 23 THEN RETURN {
      "One sample vs a median; skewed data.",
      "Signed-rank needs symmetry; sign does not.",
      "Not on the Prime.  R: wilcox.test(x, mu).",
      "Sign test: BINOMIAL_CDF on count above m0.",
      "Normal data?  One-sample t has more power."}; END;
    DEFAULT RETURN {
      "One group's survival vs a reference.",
      "Usually a standardized mortality ratio.",
      "Not on the Prime.  Start with KM + CI.",
      "R: survival (survfit; survdiff + offset).",
      "Consider a concurrent control group."};
  END;
END;

// "How to check it": one line under the assumptions, where a
// built-in app or a fleet program can test the assumption (at most
// 42 characters); "" when nothing on the Prime helps.
tcCheck(ckI)
BEGIN
  IF ckI == 1 OR ckI == 2 THEN RETURN "Check: Stats 1Var normal plot, each group."; END;
  IF ckI == 3 OR ckI == 4 THEN RETURN "Check: Stats 1Var normal plot of diffs."; END;
  IF ckI == 5 THEN RETURN "Check: residuals in NormExplorer's data."; END;
  IF ckI == 6 OR ckI == 7 OR ckI == 10 THEN RETURN "Check: expected counts >= 5 in ChiTable."; END;
  IF ckI == 21 THEN RETURN "Check: NormExplorer's data module (D1)."; END;
  IF ckI == 13 OR ckI == 23 THEN RETURN "Check symmetry: NormExplorer's data (D1)."; END;
  RETURN "";
END;

// A scale node of the flowchart: its name and a two-line hint
// (at most 20 characters a line: the node is 136 px wide).
tcScaleTx(stS)
BEGIN
  IF stS == 1 THEN RETURN {"Interval", "measurements,", "roughly normal"}; END;
  IF stS == 2 THEN RETURN {"Nominal", "categories,", "yes / no counts"}; END;
  IF stS == 3 THEN RETURN {"Ordinal", "ranks, scores; or", "skewed / small n"}; END;
  RETURN {"Survival", "time to an event,", "some censored"};
END;

// A design node of the flowchart, on two lines.
tcDesTx(dxD)
BEGIN
  IF dxD == 1 THEN RETURN {"one group vs", "a known value"}; END;
  IF dxD == 2 THEN RETURN {"two groups,", "different people"}; END;
  IF dxD == 3 THEN RETURN {"3+ groups,", "different people"}; END;
  IF dxD == 4 THEN RETURN {"before & after,", "same people"}; END;
  IF dxD == 5 THEN RETURN {"3+ conditions,", "same people"}; END;
  RETURN {"association of", "two variables"};
END;

// The status line when a whole row (a scale) is lit: what the
// tests in that row have in common.  At most 46 characters.
tcRowSum(rsR)
BEGIN
  IF rsR == 1 THEN RETURN "Interval row: means; assumes roughly normal"; END;
  IF rsR == 2 THEN RETURN "Nominal row: counts and proportions"; END;
  IF rsR == 3 THEN RETURN "Ordinal row: rank tests - ranks, not means"; END;
  RETURN "Survival row: time to event, with censoring";
END;

// The status line when a whole column (a design) is lit.
tcColSum(cmD)
BEGIN
  IF cmD == 1 THEN RETURN "1 group vs a value: one-sample tests"; END;
  IF cmD == 2 THEN RETURN "2 independent groups: different people"; END;
  IF cmD == 3 THEN RETURN "3+ independent groups: different people"; END;
  IF cmD == 4 THEN RETURN "Before/after, same people: paired tests"; END;
  IF cmD == 5 THEN RETURN "3+ conditions, same people: repeated"; END;
  RETURN "Association: two variables, no groups";
END;

// THE QUIZ BANK: twenty cases from criminal justice and forensic
// science.  Each is {scale, design, also-accepted cell, three lines
// of the case, the one-line reason}.  The also-accepted cell is
// scale * 10 + design, or 0: a skewed sentence length may fairly go
// to Kruskal-Wallis, and a yes/no outcome in two groups is the same
// 2 x 2 table as two linked yes/no traits.  Every case uses designs
// 2-6, the columns of Glantz's table, so the quiz table needs no
// second page.  The lines are at most 46 characters (font 1 across
// the screen) and were checked against tcIdFor and tcFull on the PC
// (verify_TestChooser.py).
tcQ(qbI)
BEGIN
  CASE
    // 1. One-way ANOVA
    IF qbI == 1 THEN RETURN {1, 3, 33,
      "Sentence length (months) for similar burglary",
      "convictions, handed down by three different",
      "judges.",
      "Months = interval; 3 judges = 3+ groups"}; END;
    // 2. Chi-square 2x2, or Fisher's exact
    IF qbI == 2 THEN RETURN {2, 2, 26,
      "Re-arrest within two years (yes/no) for",
      "parolees in a drug-court programme and in",
      "standard probation.",
      "Yes/no outcome, 2 separate groups; give the OR"}; END;
    // 3. Paired t test
    IF qbI == 3 THEN RETURN {1, 4, 0,
      "Accuracy (% correct) of the same 25 latent-",
      "print examiners before and after a training",
      "course.",
      "The same examiners measured twice: PAIRED"}; END;
    // 4. Mann-Whitney U (Wilcoxon rank-sum)
    IF qbI == 4 THEN RETURN {3, 2, 0,
      "Confidence in a bitemark match on a 1-5 Likert",
      "scale: board-certified versus non-certified",
      "examiners.",
      "Likert scores are ordinal; 2 separate groups"}; END;
    // 5. Log-rank test (two groups)
    IF qbI == 5 THEN RETURN {4, 2, 0,
      "Days from release to re-arrest in two re-entry",
      "programmes; some were never re-arrested during",
      "follow-up.",
      "Time to an event, some censored: survival"}; END;
    // 6. Pearson r and linear regression
    IF qbI == 6 THEN RETURN {1, 6, 0,
      "Crime rate and unemployment rate, both",
      "measured in each of 50 counties.",
      "",
      "Two measured variables, no groups: association"}; END;
    // 7. Unpaired (two-sample) t test
    IF qbI == 7 THEN RETURN {1, 2, 0,
      "Blood alcohol concentration of drivers stopped",
      "at a rural and at an urban checkpoint.",
      "",
      "BAC is measured; 2 separate groups of drivers"}; END;
    // 8. McNemar's test
    IF qbI == 8 THEN RETURN {2, 4, 0,
      "The same 60 examiners judge one bitemark",
      "twice, a year apart: 'match' or 'no match'",
      "each time.",
      "Same examiners, yes/no, twice: paired nominal"}; END;
    // 9. Chi-square for an r x c table
    IF qbI == 9 THEN RETURN {2, 3, 0,
      "Guilty plea (yes/no) by clients of public",
      "defenders, appointed counsel and private",
      "attorneys.",
      "Yes/no outcome in three separate groups"}; END;
    // 10. Kruskal-Wallis test
    IF qbI == 10 THEN RETURN {3, 3, 0,
      "Injury severity (none, minor, serious, fatal)",
      "in assaults with four different weapon types.",
      "",
      "Ranked categories = ordinal; 4 separate groups"}; END;
    // 11. Wilcoxon signed-rank test
    IF qbI == 11 THEN RETURN {3, 4, 0,
      "Stress rated 1-10 by the same 20 officers",
      "before and after a critical-incident",
      "debriefing.",
      "Same officers rated twice, ordinal: paired"}; END;
    // 12. Repeated-measures ANOVA
    IF qbI == 12 THEN RETURN {1, 5, 0,
      "Reaction time (ms) of the same 15 officers in",
      "a shooting simulator under three lighting",
      "levels.",
      "Same officers under 3 conditions: repeated"}; END;
    // 13. Friedman test
    IF qbI == 13 THEN RETURN {3, 5, 0,
      "The same 12 mock jurors rate (1-7) how",
      "convincing eyewitness, DNA and confession",
      "evidence is.",
      "Same jurors, 3 conditions, ordinal ratings"}; END;
    // 14. Cochran's Q test
    IF qbI == 14 THEN RETURN {2, 5, 0,
      "The same 30 trainees, each marked pass or fail",
      "at three successive firearms qualifications.",
      "",
      "Same trainees, pass/fail, three occasions"}; END;
    // 15. Spearman rank correlation
    IF qbI == 15 THEN RETURN {3, 6, 0,
      "Fifteen crime labs ranked by case backlog, and",
      "ranked again by audit error rate.",
      "",
      "Two rankings of the same labs: rank corr."}; END;
    // 16. Log-rank test (several groups)
    IF qbI == 16 THEN RETURN {4, 3, 0,
      "Time to first parole violation under low,",
      "medium and high supervision, over three years",
      "of follow-up.",
      "Time to event, censored; 3 separate groups"}; END;
    // 17. Cox proportional-hazards regression
    IF qbI == 17 THEN RETURN {4, 6, 0,
      "Time to re-offence after release, with age,",
      "prior arrests and employment as predictors.",
      "",
      "Time to event against several predictors: Cox"}; END;
    // 18. Relative risk, odds ratio, chi-square
    IF qbI == 18 THEN RETURN {2, 6, 22,
      "In 300 autopsies: was the toxicology screen",
      "positive (yes/no), and was the death ruled",
      "accidental (yes/no)?",
      "Two yes/no traits on each case: association"}; END;
    // 19. Unpaired (two-sample) t test
    IF qbI == 19 THEN RETURN {1, 2, 0,
      "Intercanine width (mm) measured on the dental",
      "casts of 40 men and 40 women.",
      "",
      "Width is measured; men and women are separate"}; END;
    // 20. Paired t test
    DEFAULT RETURN {1, 4, 0,
      "Two odontologists each measure the same 30",
      "skulls. Do their mean measurements differ?",
      "",
      "The same skulls measured by both: PAIRED"};
  END;
END;


// ------------------------------------------------------------
// SMALL HELPERS
// ------------------------------------------------------------

// Wait for the finger to lift -- but never for more than a second.
// MOUSE(1) is -1 while nothing touches the screen.  Without the
// time limit a stuck or phantom touch would hold the program here
// for ever, and the only way out would be a reset.
tcDrain()
BEGIN
  LOCAL drT;
  drT := TICKS;                                  // milliseconds
  REPEAT UNTIL MOUSE(1) == -1 OR TICKS - drT > 1000;
END;

// Wait for any key or tap (drain, wait, drain) -- the house
// xxWaitAny.  Returns the key code, or -1 for a tap; a tap's x and
// y are left in tcTX, tcTY.  A GETKEY drain alone does not drain
// touch, which is why the MOUSE drain is here too.
tcWaitAny()
BEGIN
  LOCAL waK, waM, waM1, waK2;
  REPEAT waK := GETKEY; UNTIL waK == -1;
  tcDrain();
  REPEAT
    waK := GETKEY;
    waM := MOUSE;
    waM1 := waM(1);
  UNTIL waK > -1 OR SIZE(waM1) > 0;
  tcTX := -1; tcTY := -1;
  IF SIZE(waM1) > 0 THEN
    tcTX := waM1(1); tcTY := waM1(2); waK := -1;
  END;
  tcDrain();
  REPEAT waK2 := GETKEY; UNTIL waK2 == -1;
  RETURN waK;
END;

// Hold the picture still for about paMs milliseconds (an animation
// frame).  TICKS is the millisecond clock, as in tcDrain.
tcPause(paMs)
BEGIN
  LOCAL paT;
  paT := TICKS;
  REPEAT UNTIL TICKS - paT > paMs;
END;

// Show the finished frame, then the soft keys.  DRAWMENU paints the
// SCREEN, not G1, so it comes after the blit, every frame.
tcShow()
BEGIN
  BLIT_P(G0, 0, 0, G1);
  DRAWMENU({"Guide", "Table", "Quiz", "", "Back", "Quit"});
END;

// Which soft key a tap at x is (the tap has y >= 218): six slots of
// 53.4 px, left to right (ChiTable, G2 and G1).
tcSlot(slX)
BEGIN
  RETURN MIN(6, IP(slX / 53.4) + 1);
END;

// What a soft key asks for: 1 Guide, 2 Table, 3 Quiz, 0 Back (up
// one level, like Esc), 9 Quit; -1 for the blank fourth key.
tcSoftAct(saSl)
BEGIN
  IF saSl == 1 THEN RETURN 1; END;
  IF saSl == 2 THEN RETURN 2; END;
  IF saSl == 3 THEN RETURN 3; END;
  IF saSl == 5 THEN RETURN 0; END;
  IF saSl == 6 THEN RETURN 9; END;
  RETURN -1;
END;

// The colour of a scale: 1 blue, 2 orange, 3 magenta, 4 green,
// and its light fill.
tcColOf(coSc)
BEGIN
  IF coSc == 1 THEN RETURN tcBLUE; END;
  IF coSc == 2 THEN RETURN tcORNG; END;
  IF coSc == 3 THEN RETURN tcMAG; END;
  RETURN tcGRN;
END;

tcLightOf(loS)
BEGIN
  IF loS == 1 THEN RETURN tcLBLU; END;
  IF loS == 2 THEN RETURN tcLORN; END;
  IF loS == 3 THEN RETURN tcLMAG; END;
  RETURN tcLGRN;
END;

// A three-line arrowhead's two barbs, at the point (hdX, hdY) of a
// connector running in the direction (hdDX, hdDY): (1,0) right,
// (-1,0) left, (0,1) down.  The connector itself is the third line.
// The same shape the beta 1 path screen drew by hand.
tcHead(hdX, hdY, hdDX, hdDY, hdC)
BEGIN
  LINE_P(G1, hdX - 4 * hdDX - 4 * hdDY, hdY - 4 * hdDY - 4 * hdDX, hdX, hdY, hdC);
  LINE_P(G1, hdX - 4 * hdDX + 4 * hdDY, hdY - 4 * hdDY + 4 * hdDX, hdX, hdY, hdC);
END;

// A 2-pixel line (the lit path): a horizontal or vertical LINE_P
// and its neighbour one pixel down or right.
tcThick(thX1, thY1, thX2, thY2, thC)
BEGIN
  LINE_P(G1, thX1, thY1, thX2, thY2, thC);
  IF thX1 == thX2 THEN
    LINE_P(G1, thX1 + 1, thY1, thX2 + 1, thY2, thC);
  ELSE
    LINE_P(G1, thX1, thY1 + 1, thX2, thY2 + 1, thC);
  END;
END;

// A 2-pixel frame round a box, drawn with LINE_P so the inside
// (a cell's text) is left alone.
tcBox2(bxG, bxX1, bxY1, bxX2, bxY2, bxC)
BEGIN
  LOCAL bxI;
  FOR bxI FROM 0 TO 1 DO
    LINE_P(bxG, bxX1 + bxI, bxY1 + bxI, bxX2 - bxI, bxY1 + bxI, bxC);
    LINE_P(bxG, bxX1 + bxI, bxY2 - bxI, bxX2 - bxI, bxY2 - bxI, bxC);
    LINE_P(bxG, bxX1 + bxI, bxY1 + bxI, bxX1 + bxI, bxY2 - bxI, bxC);
    LINE_P(bxG, bxX2 - bxI, bxY1 + bxI, bxX2 - bxI, bxY2 - bxI, bxC);
  END;
END;

// Light diagonal hatching inside a box: dotted lines at 45 degrees,
// 6 px apart, a dot every second pixel, made with PIXON_P.  The
// inner loop's limits keep every dot inside the box, so no test is
// needed per dot (about 140 dots for a table cell).
tcHatch(htG, htX1, htY1, htX2, htY2, htC)
BEGIN
  LOCAL htK, htT, htW, htH;
  htW := htX2 - htX1;
  htH := htY2 - htY1;
  FOR htK FROM 2 TO htW + htH STEP 6 DO
    // the dots of one diagonal: x = x1 + k - t, y = y1 + t
    FOR htT FROM MAX(0, htK - htW) TO MIN(htH, htK) STEP 2 DO
      PIXON_P(htG, htX1 + htK - htT, htY1 + htT, htC);
    END;
  END;
END;

// A 14 x 12 pictogram with its top left corner at (ptX, ptY):
//   designs  1 one bar under a dotted value line   2 two bars
//            3 three bars   4 two dots joined (the same subject
//            twice)   5 three dots joined   6 a scatter
//   scales   7 interval: a bell   8 nominal: yes / no boxes
//            9 ordinal: ranked steps   10 survival: a step-down
//            (Kaplan-Meier) curve with a censoring tick
tcPict(ptG, ptK, ptX, ptY, ptC)
BEGIN
  LOCAL ptI;
  CASE
    IF ptK == 1 THEN
      RECT_P(ptG, ptX + 5, ptY + 4, ptX + 8, ptY + 11, ptC, ptC);
      FOR ptI FROM 0 TO 13 STEP 2 DO PIXON_P(ptG, ptX + ptI, ptY + 1, ptC); END;
    END;
    IF ptK == 2 THEN
      RECT_P(ptG, ptX + 2, ptY + 5, ptX + 5, ptY + 11, ptC, ptC);
      RECT_P(ptG, ptX + 8, ptY + 2, ptX + 11, ptY + 11, ptC, ptC);
    END;
    IF ptK == 3 THEN
      RECT_P(ptG, ptX, ptY + 6, ptX + 3, ptY + 11, ptC, ptC);
      RECT_P(ptG, ptX + 5, ptY + 1, ptX + 8, ptY + 11, ptC, ptC);
      RECT_P(ptG, ptX + 10, ptY + 4, ptX + 13, ptY + 11, ptC, ptC);
    END;
    IF ptK == 4 THEN
      RECT_P(ptG, ptX, ptY + 8, ptX + 2, ptY + 10, ptC, ptC);
      RECT_P(ptG, ptX + 11, ptY + 2, ptX + 13, ptY + 4, ptC, ptC);
      LINE_P(ptG, ptX + 2, ptY + 9, ptX + 11, ptY + 3, ptC);
    END;
    IF ptK == 5 THEN
      RECT_P(ptG, ptX, ptY + 8, ptX + 2, ptY + 10, ptC, ptC);
      RECT_P(ptG, ptX + 5, ptY + 1, ptX + 7, ptY + 3, ptC, ptC);
      RECT_P(ptG, ptX + 11, ptY + 5, ptX + 13, ptY + 7, ptC, ptC);
      LINE_P(ptG, ptX + 2, ptY + 9, ptX + 5, ptY + 2, ptC);
      LINE_P(ptG, ptX + 7, ptY + 2, ptX + 11, ptY + 6, ptC);
    END;
    IF ptK == 6 THEN
      RECT_P(ptG, ptX, ptY + 10, ptX + 1, ptY + 11, ptC, ptC);
      RECT_P(ptG, ptX + 3, ptY + 7, ptX + 4, ptY + 8, ptC, ptC);
      RECT_P(ptG, ptX + 5, ptY + 9, ptX + 6, ptY + 10, ptC, ptC);
      RECT_P(ptG, ptX + 8, ptY + 4, ptX + 9, ptY + 5, ptC, ptC);
      RECT_P(ptG, ptX + 10, ptY + 5, ptX + 11, ptY + 6, ptC, ptC);
      RECT_P(ptG, ptX + 12, ptY, ptX + 13, ptY + 1, ptC, ptC);
    END;
    IF ptK == 7 THEN
      LINE_P(ptG, ptX, ptY + 11, ptX + 3, ptY + 9, ptC);
      LINE_P(ptG, ptX + 3, ptY + 9, ptX + 5, ptY + 4, ptC);
      LINE_P(ptG, ptX + 5, ptY + 4, ptX + 6, ptY + 1, ptC);
      LINE_P(ptG, ptX + 6, ptY + 1, ptX + 7, ptY + 1, ptC);
      LINE_P(ptG, ptX + 7, ptY + 1, ptX + 8, ptY + 4, ptC);
      LINE_P(ptG, ptX + 8, ptY + 4, ptX + 10, ptY + 9, ptC);
      LINE_P(ptG, ptX + 10, ptY + 9, ptX + 13, ptY + 11, ptC);
    END;
    IF ptK == 8 THEN
      RECT_P(ptG, ptX, ptY + 3, ptX + 5, ptY + 8, ptC, ptC);
      RECT_P(ptG, ptX + 8, ptY + 3, ptX + 13, ptY + 8, ptC, #FFFFFFh);
    END;
    IF ptK == 9 THEN
      RECT_P(ptG, ptX, ptY + 8, ptX + 3, ptY + 11, ptC, ptC);
      RECT_P(ptG, ptX + 5, ptY + 5, ptX + 8, ptY + 11, ptC, ptC);
      RECT_P(ptG, ptX + 10, ptY + 1, ptX + 13, ptY + 11, ptC, ptC);
    END;
    DEFAULT
      LINE_P(ptG, ptX, ptY + 1, ptX + 4, ptY + 1, ptC);
      LINE_P(ptG, ptX + 4, ptY + 1, ptX + 4, ptY + 4, ptC);
      LINE_P(ptG, ptX + 4, ptY + 4, ptX + 8, ptY + 4, ptC);
      LINE_P(ptG, ptX + 8, ptY + 4, ptX + 8, ptY + 8, ptC);
      LINE_P(ptG, ptX + 8, ptY + 8, ptX + 13, ptY + 8, ptC);
      LINE_P(ptG, ptX + 11, ptY + 6, ptX + 11, ptY + 10, ptC);
  END;
END;

// Hit boxes: the drawing code records every tappable box as it
// draws it, and the tap handler scans the same list, so the two
// cannot disagree (ChiTable's csTX1..csTY2 pattern).
tcAddHit(ahX1, ahY1, ahX2, ahY2, ahId)
BEGIN
  tcHN := tcHN + 1;
  tcHX1(tcHN) := ahX1; tcHY1(tcHN) := ahY1;
  tcHX2(tcHN) := ahX2; tcHY2(tcHN) := ahY2;
  tcHId(tcHN) := ahId;
END;

// Which recorded box holds (hiX, hiY): its id, or 0 for none.
tcHitAt(hiX, hiY)
BEGIN
  LOCAL hiI;
  FOR hiI FROM 1 TO tcHN DO
    IF hiX >= tcHX1(hiI) AND hiX <= tcHX2(hiI) AND hiY >= tcHY1(hiI) AND hiY <= tcHY2(hiI) THEN
      RETURN tcHId(hiI);
    END;
  END;
  RETURN 0;
END;


// ------------------------------------------------------------
// THE DETAILS PAGE for one test
// dtS / dtD: the scale and design that led here.  Every caller (the
// guide, the table, the quiz) knows them, so the page is framed in
// the scale's colour and the subtitle names the scale and design.
// Returns 0 to go back, or 1 / 2 / 3 / 9 when a soft key asks for
// the Guide, the Table, the Quiz or Quit.
// ------------------------------------------------------------
tcDetails(dtI, dtS, dtD)
BEGIN
  LOCAL dtL, dtN, dtCol, dtLt, dtK, dtRet;
  dtL := tcDetail(dtI);
  dtN := tcFull(dtI);
  TC_TEST := dtN;
  dtCol := tcColOf(dtS); dtLt := tcLightOf(dtS);
  DIMGROB_P(G1, 320, 240, #FFFFFFh);
  RECT_P(G1, 4, 4, 315, 214, dtCol, dtLt);
  TEXTOUT_P(dtN, G1, 12, 8, 2, dtCol, 298);
  TEXTOUT_P(tcScaleShort(dtS) + "  |  " + tcDesignShort(dtD), G1, 12, 26, 1, tcGRAY, 298);
  LINE_P(G1, 12, 39, 307, 39, tcGRAY);
  TEXTOUT_P("What:", G1, 12, 43, 1, tcGRAY);
  TEXTOUT_P(dtL(1), G1, 14, 55, 1, #000000h, 296);
  TEXTOUT_P("Assumes:", G1, 12, 69, 1, tcGRAY);
  TEXTOUT_P(dtL(2), G1, 14, 81, 1, #000000h, 296);
  // how to check the assumption, where the Prime can (blank if not)
  TEXTOUT_P(tcCheck(dtI), G1, 14, 93, 1, tcDODG, 296);
  TEXTOUT_P("On the Prime:", G1, 12, 107, 1, tcGRAY);
  TEXTOUT_P(dtL(3), G1, 14, 119, 1, tcBLUE, 296);
  TEXTOUT_P("Afterwards:", G1, 12, 133, 1, tcGRAY);
  TEXTOUT_P(dtL(4), G1, 14, 145, 1, tcMAG, 296);
  TEXTOUT_P("If an assumption fails:", G1, 12, 159, 1, tcGRAY);
  TEXTOUT_P(dtL(5), G1, 14, 171, 1, tcGRN, 296);
  TEXTOUT_P("tap or any key = back, Quit = leave", G1, 12, 197, 1, tcFOOT, 296);
  dtRet := -2;
  WHILE dtRet == -2 DO
    tcShow();
    dtK := tcWaitAny();
    IF dtK == -1 AND tcTY >= 218 THEN
      dtRet := tcSoftAct(tcSlot(tcTX));
      IF dtRet == -1 THEN dtRet := -2; END;       // the blank key: stay
    ELSE
      dtRet := 0;
    END;
  END;
  RETURN dtRet;
END;


// ------------------------------------------------------------
// THE GUIDE: a flowchart you tap
//   level 0  the root question "outcome scale?" and four scales
//   level 1  the chosen scale at the top (a breadcrumb; tap it to
//            go back) and its six designs
//   level 2  the same, with the path to one design lit in orange
//            and the leaf box with the test at the bottom
// Two levels are on the screen at a time; 320 x 240 cannot hold
// all 24 leaves.  Nodes sit in two columns either side of a trunk
// at x = 160, numbered down the left column, then the right.
// ------------------------------------------------------------

// The box of node n at a level: {x1, y1, x2, y2}.  Level 0 has two
// rows of 50-px nodes, level 1 three rows of 30-px nodes; both
// columns are 136 px wide (x 10-146 and 174-310).
tcNodeBox(nbL, nbN)
BEGIN
  LOCAL nbR, nbC, nbW, nbX;
  nbR := 2;
  IF nbL == 1 THEN nbR := 3; END;
  nbC := IP((nbN - 1) / nbR) + 1;          // the column, 1 or 2
  nbW := nbN - (nbC - 1) * nbR;            // the row
  nbX := 10;
  IF nbC == 2 THEN nbX := 174; END;
  IF nbL == 0 THEN RETURN {nbX, 40 + (nbW - 1) * 60, nbX + 136, 90 + (nbW - 1) * 60}; END;
  RETURN {nbX, 36 + (nbW - 1) * 36, nbX + 136, 66 + (nbW - 1) * 36};
END;

// One node.  Styles: 0 normal (light fill, coloured text), 1 chosen
// or focused (solid colour, white text), 2 greyed out, 3 lit (on
// the orange path: a 3-px orange frame).  ndTx holds one line (font
// 2, centred in the height), two lines (font 1) or a name and two
// hint lines.
tcNode(ndX1, ndY1, ndX2, ndY2, ndSt, ndCol, ndLt, ndTx)
BEGIN
  LOCAL ndBd, ndFl, ndFg, ndN, ndW;
  ndBd := ndCol; ndFl := ndLt; ndFg := ndCol;
  ndW := ndX2 - ndX1 - 12;
  IF ndSt == 1 THEN ndFl := ndCol; ndFg := #FFFFFFh; END;
  IF ndSt == 2 THEN ndBd := tcPALE; ndFl := tcLGRY; ndFg := tcPALE; END;
  IF ndSt == 3 THEN
    RECT_P(G1, ndX1, ndY1, ndX2, ndY2, tcORNG, tcORNG);
    RECT_P(G1, ndX1 + 3, ndY1 + 3, ndX2 - 3, ndY2 - 3, tcORNG, tcLORN);
  ELSE
    RECT_P(G1, ndX1, ndY1, ndX2, ndY2, ndBd, ndFl);
  END;
  ndN := SIZE(ndTx);
  IF ndN == 1 THEN
    TEXTOUT_P(ndTx(1), G1, ndX1 + 8, ndY1 + IP((ndY2 - ndY1 - 14) / 2), 2, ndFg, ndW);
  END;
  IF ndN == 2 THEN
    TEXTOUT_P(ndTx(1), G1, ndX1 + 8, ndY1 + 3, 1, ndFg, ndW);
    TEXTOUT_P(ndTx(2), G1, ndX1 + 8, ndY1 + 16, 1, ndFg, ndW);
  END;
  IF ndN == 3 THEN
    TEXTOUT_P(ndTx(1), G1, ndX1 + 8, ndY1 + 5, 2, ndFg, ndW);
    TEXTOUT_P(ndTx(2), G1, ndX1 + 8, ndY1 + 24, 1, ndFg, ndW);
    TEXTOUT_P(ndTx(3), G1, ndX1 + 8, ndY1 + 36, 1, ndFg, ndW);
  END;
END;

// Level 0: the root and the four scales.  d0F is the focused (or
// chosen) scale.  d0Md: 0 normal; 1 the others greyed out; 2 the
// same, with the chosen node left out (it is being moved).
tcDrawL0(d0F, d0Md)
BEGIN
  LOCAL d0N, d0B, d0Y, d0St, d0C;
  DIMGROB_P(G1, 320, 240, #FFFFFFh);
  tcHN := 0;
  // the root question
  IF d0Md == 0 THEN
    RECT_P(G1, 90, 4, 230, 28, tcORNG, tcLORN);
    TEXTOUT_P("outcome scale?", G1, 100, 8, 2, #000000h, 126);
  ELSE
    RECT_P(G1, 90, 4, 230, 28, tcPALE, tcLGRY);
    TEXTOUT_P("outcome scale?", G1, 100, 8, 2, tcPALE, 126);
  END;
  LINE_P(G1, 160, 28, 160, 125, #000000h);          // the trunk
  FOR d0N FROM 1 TO 4 DO
    d0B := tcNodeBox(0, d0N);
    d0Y := IP((d0B(2) + d0B(4)) / 2);
    d0St := 0; d0C := #000000h;
    IF d0N == d0F THEN d0St := 1; END;
    IF d0Md > 0 AND d0N <> d0F THEN d0St := 2; d0C := tcPALE; END;
    // the branch from the trunk, its arrowhead at the node
    IF d0B(1) < 160 THEN
      LINE_P(G1, 160, d0Y, d0B(3), d0Y, d0C);
      tcHead(d0B(3), d0Y, -1, 0, d0C);
    ELSE
      LINE_P(G1, 160, d0Y, d0B(1), d0Y, d0C);
      tcHead(d0B(1), d0Y, 1, 0, d0C);
    END;
    IF d0Md < 2 OR d0N <> d0F THEN
      tcNode(d0B(1), d0B(2), d0B(3), d0B(4), d0St, tcColOf(d0N), tcLightOf(d0N), tcScaleTx(d0N));
    END;
    tcAddHit(d0B(1), d0B(2), d0B(3), d0B(4), d0N);
  END;
  TEXTOUT_P("Q1: what kind of OUTCOME?", G1, 10, 160, 2, #000000h, 300);
  TEXTOUT_P("the number you record for each subject", G1, 10, 180, 1, tcGRAY, 300);
  TEXTOUT_P("tap = pick, ⇆⇅ = move, Ent = pick, Back = menu", G1, 6, 204, 1, tcFOOT, 308);
END;

// Levels 1 and 2: the chosen scale dlS as the breadcrumb and its
// six designs.  dlF: the focused design.  dlLit: 0 at level 1, or
// the design whose path is lit; dlStg (1-6) how much of the path
// is drawn so far: 1 the trunk, 2 the branch, 3 the design node,
// 4 down the outside rail, 5 across to the leaf, 6 the leaf box.
// dlRows: how many rows of designs have dropped in (1-3).
tcDrawL1(dlS, dlF, dlLit, dlStg, dlRows)
BEGIN
  LOCAL dlN, dlB, dlY, dlSt, dlC, dlLast;
  LOCAL dlCol, dlI, dlNm, dlF2, dlTx;
  dlCol := tcColOf(dlS);
  DIMGROB_P(G1, 320, 240, #FFFFFFh);
  tcHN := 0;
  // the breadcrumb: the chosen scale, moved up to the top
  dlTx := tcScaleTx(dlS);
  tcNode(90, 4, 230, 28, 1, dlCol, tcLightOf(dlS), {dlTx(1)});
  tcAddHit(90, 4, 230, 28, 20);
  TEXTOUT_P("outcome", G1, 8, 10, 1, tcGRAY, 78);
  TEXTOUT_P("tap = up", G1, 238, 10, 1, tcGRAY, 78);
  // the trunk, as far down as the rows shown so far
  dlLast := 51 + (dlRows - 1) * 36;
  dlC := #000000h;
  IF dlLit > 0 THEN dlC := tcPALE; END;
  LINE_P(G1, 160, 28, 160, dlLast, dlC);
  FOR dlN FROM 1 TO 6 DO
    dlB := tcNodeBox(1, dlN);
    // its row (1-3) has dropped in?
    IF dlN - IP((dlN - 1) / 3) * 3 <= dlRows THEN
      dlY := IP((dlB(2) + dlB(4)) / 2);
      dlSt := 0; dlC := #000000h;
      IF dlLit == 0 AND dlN == dlF THEN dlSt := 1; END;
      IF dlLit > 0 AND dlN <> dlLit THEN dlSt := 2; dlC := tcPALE; END;
      IF dlLit > 0 AND dlN == dlLit AND dlStg >= 3 THEN dlSt := 3; END;
      IF dlB(1) < 160 THEN
        LINE_P(G1, 160, dlY, dlB(3), dlY, dlC);
        tcHead(dlB(3), dlY, -1, 0, dlC);
      ELSE
        LINE_P(G1, 160, dlY, dlB(1), dlY, dlC);
        tcHead(dlB(1), dlY, 1, 0, dlC);
      END;
      tcNode(dlB(1), dlB(2), dlB(3), dlB(4), dlSt, dlCol, tcLightOf(dlS), tcDesTx(dlN));
      tcAddHit(dlB(1), dlB(2), dlB(3), dlB(4), dlN);
    END;
  END;
  IF dlLit > 0 THEN
    // THE PATH, in orange, as far as stage dlStg
    dlB := tcNodeBox(1, dlLit);
    dlY := IP((dlB(2) + dlB(4)) / 2);
    tcThick(160, 28, 160, dlY, tcORNG);                       // 1 the trunk
    IF dlStg >= 2 THEN                                        // 2 the branch
      IF dlB(1) < 160 THEN
        tcThick(dlB(3), dlY, 160, dlY, tcORNG);
        tcHead(dlB(3), dlY, -1, 0, tcORNG);
      ELSE
        tcThick(160, dlY, dlB(1), dlY, tcORNG);
        tcHead(dlB(1), dlY, 1, 0, tcORNG);
      END;
    END;
    // 3: the design node is lit (style 3, in the loop above)
    IF dlStg >= 4 THEN                                        // 4 out and down the rail
      IF dlB(1) < 160 THEN
        tcThick(3, dlY, dlB(1), dlY, tcORNG);
        tcThick(3, dlY, 3, 175, tcORNG);
      ELSE
        tcThick(dlB(3), dlY, 316, dlY, tcORNG);
        tcThick(316, dlY, 316, 175, tcORNG);
      END;
    END;
    IF dlStg >= 5 THEN                                        // 5 across to the leaf
      IF dlB(1) < 160 THEN
        tcThick(3, 175, 24, 175, tcORNG);
        tcHead(24, 175, 1, 0, tcORNG);
      ELSE
        tcThick(296, 175, 316, 175, tcORNG);
        tcHead(296, 175, -1, 0, tcORNG);
      END;
    END;
    IF dlStg >= 6 THEN                                        // 6 the leaf
      dlI := tcIdFor(dlS, dlLit);
      dlNm := tcFull(dlI);
      RECT_P(G1, 24, 152, 296, 198, tcORNG, tcORNG);
      RECT_P(G1, 27, 155, 293, 195, dlCol, dlCol);
      dlF2 := 2;
      IF DIM(dlNm) > 31 THEN dlF2 := 1; END;          // a long name in the small font
      TEXTOUT_P(dlNm, G1, 34, 160, dlF2, #FFFFFFh, 254);
      IF tcNotP(dlI) == 1 THEN
        TEXTOUT_P("not on the Prime, tap or Ent = details", G1, 34, 180, 1, #FFFFFFh, 254);
      ELSE
        TEXTOUT_P("tap here or Ent = all the details", G1, 34, 180, 1, #FFFFFFh, 254);
      END;
      tcAddHit(24, 152, 296, 198, 30);
    END;
    TEXTOUT_P("tap the test or Ent = details, Back = up", G1, 6, 204, 1, tcFOOT, 308);
  ELSE
    IF dlRows == 3 THEN
      TEXTOUT_P("Q2: what is being compared?", G1, 10, 152, 2, #000000h, 300);
      TEXTOUT_P("different people, the same people, or a link?", G1, 10, 172, 1, tcGRAY, 300);
    END;
    TEXTOUT_P("tap = pick, ⇆⇅ = move, Ent = pick, Back = up", G1, 6, 204, 1, tcFOOT, 308);
  END;
END;

// Level 0 -> 1: the other three scales grey out (1 frame), the
// chosen node rises to the breadcrumb place (3 frames), and the six
// designs drop in a row at a time (3 frames).
tcAnimUp(auS)
BEGIN
  LOCAL auT, auB, auX1, auY1, auX2, auY2;
  LOCAL auR, auTx;
  auB := tcNodeBox(0, auS);
  auTx := tcScaleTx(auS);
  tcDrawL0(auS, 1);
  tcShow();
  tcPause(150);
  FOR auT FROM 1 TO 3 DO
    tcDrawL0(auS, 2);
    auX1 := auB(1) + IP((90 - auB(1)) * auT / 3);
    auY1 := auB(2) + IP((4 - auB(2)) * auT / 3);
    auX2 := auB(3) + IP((230 - auB(3)) * auT / 3);
    auY2 := auB(4) + IP((28 - auB(4)) * auT / 3);
    tcNode(auX1, auY1, auX2, auY2, 1, tcColOf(auS), tcLightOf(auS), {auTx(1)});
    tcShow();
    tcPause(60);
  END;
  FOR auR FROM 1 TO 3 DO
    tcDrawL1(auS, 0, 0, 0, auR);
    tcShow();
    tcPause(60);
  END;
END;

// Level 1 -> 2: the path to design apD draws itself in six frames.
tcAnimPath(apS, apD)
BEGIN
  LOCAL apG;
  FOR apG FROM 1 TO 6 DO
    tcDrawL1(apS, apD, apD, apG, 3);
    tcShow();
    tcPause(70);
  END;
  TC_TEST := tcFull(tcIdFor(apS, apD));
END;

// Arrow keys on a two-column flowchart: Up / Down move within the
// column, Left / Right jump to the other column on the same row.
// mvR is the number of rows (2 at level 0, 3 at level 1).
tcMove(mvN, mvK, mvR)
BEGIN
  LOCAL mvC, mvW;
  mvC := IP((mvN - 1) / mvR) + 1;
  mvW := mvN - (mvC - 1) * mvR;
  IF mvK == 2 AND mvW > 1 THEN mvW := mvW - 1; END;
  IF mvK == 12 AND mvW < mvR THEN mvW := mvW + 1; END;
  IF mvK == 7 THEN mvC := 1; END;
  IF mvK == 8 THEN mvC := 2; END;
  RETURN (mvC - 1) * mvR + mvW;
END;

// The guide's loop.  Returns 0 for the menu, or 1 / 2 / 3 / 9 when
// a soft key asks for the Guide, the Table, the Quiz or Quit.
tcGuide()
BEGIN
  LOCAL gdLev, gdS, gdD, gdK, gdRet, gdH;
  LOCAL gdSa, gdR, gdN;
  gdLev := 0; gdRet := -1;
  gdS := tcGS; gdD := tcGD;
  WHILE gdRet == -1 DO
    IF gdLev == 0 THEN tcDrawL0(gdS, 0); END;
    IF gdLev == 1 THEN tcDrawL1(gdS, gdD, 0, 0, 3); END;
    IF gdLev == 2 THEN tcDrawL1(gdS, gdD, gdD, 6, 3); END;
    tcShow();
    gdK := tcWaitAny();
    gdH := 0;                     // what a tap hit: 1-6 a node, 20 the breadcrumb, 30 the leaf
    IF gdK == -1 THEN
      IF tcTY >= 218 THEN
        gdSa := tcSoftAct(tcSlot(tcTX));
        IF gdSa == 0 THEN gdK := 4; END;          // Back = Esc
        IF gdSa > 0 THEN gdRet := gdSa; END;
      ELSE
        gdH := tcHitAt(tcTX, tcTY);
      END;
    END;
    IF gdRet == -1 THEN
      IF gdLev == 0 THEN
        IF gdK == 4 THEN gdRet := 0; END;
        IF gdK == 2 OR gdK == 12 OR gdK == 7 OR gdK == 8 THEN gdS := tcMove(gdS, gdK, 2); END;
        IF gdK == 30 THEN gdH := gdS; END;
        IF gdH >= 1 AND gdH <= 4 THEN
          gdS := gdH;
          tcAnimUp(gdS);
          gdLev := 1;
        END;
      ELSE
        IF gdK == 4 THEN gdLev := gdLev - 1; END;
        IF gdH == 20 THEN gdLev := 0; END;        // the breadcrumb: back to the scales
        IF gdLev >= 1 THEN
          IF gdK == 2 OR gdK == 12 OR gdK == 7 OR gdK == 8 THEN
            gdD := tcMove(gdD, gdK, 3);
            IF gdLev == 2 THEN tcAnimPath(gdS, gdD); END;
          END;
          gdN := 0;                               // 1 = open the details
          IF gdK == 30 THEN
            IF gdLev == 2 THEN gdN := 1; ELSE gdH := gdD; END;
          END;
          IF gdH == 30 THEN gdN := 1; END;
          IF gdH >= 1 AND gdH <= 6 THEN
            IF gdLev == 1 OR gdH <> gdD THEN
              gdD := gdH;
              tcAnimPath(gdS, gdD);
              gdLev := 2;
            END;
          END;
          IF gdN == 1 THEN
            gdR := tcDetails(tcIdFor(gdS, gdD), gdS, gdD);
            IF gdR > 0 THEN gdRet := gdR; END;
          END;
        END;
      END;
    END;
  END;
  tcGS := gdS; tcGD := gdD;
  RETURN gdRet;
END;


// ------------------------------------------------------------
// THE TABLE: 4 scales x 6 designs, Glantz's chapter table, five
// columns at a time (53 px each from x = 54).  tcOff = 1 shows
// designs 2-6, Glantz's own table; tcOff = 0 shows the one-sample
// column and designs 2-5.  Browsing and the quiz use different row
// heights (tcGeom); the drawing and the touch handlers read the
// same tcTY0 / tcRH.
// ------------------------------------------------------------

// The table's geometry: gmQ = 0 browsing, 1 the quiz (rows shorter,
// to leave three lines at the top for the case).
tcGeom(gmQ)
BEGIN
  tcQz := gmQ;
  IF gmQ == 0 THEN
    tcTY0 := 40; tcRH := 35; tcHY := 15; tcCY1 := 5; tcCY2 := 18; tcPY := 18;
  ELSE
    tcTY0 := 62; tcRH := 29; tcHY := 39; tcCY1 := 3; tcCY2 := 15; tcPY := 15;
  END;
END;

// One cell, row cdR (scale) and design cdD.  Styles: 0 normal,
// 1 solid in the scale colour (selected or lit), 2 green (right),
// 3 red (wrong).  A "Not on the Prime" test is hatched.
tcCellDraw(cdG, cdR, cdD, cdSt)
BEGIN
  LOCAL cdX, cdY, cdI, cdCell, cdBd, cdFl;
  LOCAL cdTx, cdHc, cdY1, cdY2;
  cdX := 54 + (cdD - tcOff - 1) * 53;
  cdY := tcTY0 + (cdR - 1) * tcRH;
  cdI := tcIdFor(cdR, cdD);
  cdCell := tcCell(cdI);
  cdBd := tcGRAY; cdFl := tcLightOf(cdR); cdTx := tcColOf(cdR); cdHc := tcHATCH;
  IF cdSt == 1 THEN cdBd := cdTx; cdFl := cdTx; cdHc := tcLightOf(cdR); cdTx := #FFFFFFh; END;
  IF cdSt == 2 THEN cdBd := tcGRN; cdFl := tcGRN; cdHc := tcLGRN; cdTx := #FFFFFFh; END;
  IF cdSt == 3 THEN cdBd := tcRED; cdFl := tcRED; cdHc := tcLRED; cdTx := #FFFFFFh; END;
  RECT_P(cdG, cdX, cdY, cdX + 52, cdY + tcRH - 1, cdBd, cdFl);
  IF tcNotP(cdI) == 1 THEN
    tcHatch(cdG, cdX + 2, cdY + 2, cdX + 50, cdY + tcRH - 3, cdHc);
  END;
  cdY1 := cdY + tcCY1; cdY2 := cdY + tcCY2;      // the two text lines
  TEXTOUT_P(cdCell(1), cdG, cdX + 3, cdY1, 1, cdTx, 49);
  TEXTOUT_P(cdCell(2), cdG, cdX + 3, cdY2, 1, cdTx, 49);
END;

// The table's background -- headings, pictograms, row labels, every
// cell with its hatching, the corner key and the legend -- drawn
// once into G2.  Each frame copies it to G1 and draws only what
// changes on top, so the ~1,400 hatching dots are not redrawn on
// every key press.  tcCK remembers which layout G2 holds.
tcTblBG()
BEGIN
  LOCAL bgR, bgD, bgX, bgY, bgS, bgT;
  LOCAL bgL;
  DIMGROB_P(G2, 320, 240, #FFFFFFh);
  // column headings: a pictogram (browsing only) and two lines
  bgS := {"1 group", "2 groups", "3+ grps", "before/", "repeat", "assoc."};
  bgT := {"vs value", "indep.", "indep.", "after", "same ppl", "2 vars"};
  FOR bgD FROM tcOff + 1 TO tcOff + 5 DO
    bgX := 54 + (bgD - tcOff - 1) * 53;
    IF tcQz == 0 THEN tcPict(G2, bgD, bgX + 19, 1, tcGRAY); END;
    TEXTOUT_P(bgS(bgD), G2, bgX + 3, tcHY, 1, tcGRAY, 50);
    TEXTOUT_P(bgT(bgD), G2, bgX + 3, tcHY + 11, 1, tcGRAY, 50);
  END;
  IF tcQz == 0 THEN
    // the corner key: the other page of the table
    RECT_P(G2, 1, 4, 51, 36, tcDODG, tcLDOD);
    IF tcOff == 1 THEN
      TEXTOUT_P("< show", G2, 5, 8, 1, tcDODG, 45);
      TEXTOUT_P("1 group", G2, 5, 21, 1, tcDODG, 45);
    ELSE
      TEXTOUT_P("show >", G2, 5, 8, 1, tcDODG, 45);
      TEXTOUT_P("assoc.", G2, 5, 21, 1, tcDODG, 45);
    END;
  END;
  // row labels with their pictograms, and the cells
  bgS := {"Interval", "Nominal", "Ordinal", "Survival"};
  FOR bgR FROM 1 TO 4 DO
    bgY := tcTY0 + (bgR - 1) * tcRH;
    // the whole label column (x 1 to 53): "Interval" and "Survival"
    // are 8 characters, about 50 px in font 1
    bgL := bgY + tcCY1 - 2;
    TEXTOUT_P(bgS(bgR), G2, 1, bgL, 1, tcColOf(bgR), 53);
    tcPict(G2, 6 + bgR, 20, bgY + tcPY, tcColOf(bgR));
    FOR bgD FROM tcOff + 1 TO tcOff + 5 DO
      tcCellDraw(G2, bgR, bgD, 0);
    END;
  END;
  IF tcQz == 0 THEN
    // the legend for the hatching
    RECT_P(G2, 6, 196, 20, 205, tcGRAY, #FFFFFFh);
    tcHatch(G2, 7, 197, 19, 204, tcHATCH);
    TEXTOUT_P("= not on the Prime (R or JMP; see details)", G2, 24, 195, 1, tcGRAY, 292);
  END;
  tcCK := tcQz * 10 + tcOff;
END;

// Start a table frame: rebuild G2 if the layout changed, then copy
// it into a fresh G1.
tcTblBase()
BEGIN
  IF tcCK <> tcQz * 10 + tcOff THEN tcTblBG(); END;
  DIMGROB_P(G1, 320, 240, #FFFFFFh);
  BLIT_P(G1, 0, 0, G2);
END;

// A browsing frame: the lit row or column, the selected cell (solid,
// with the red "you are here" frame), the status line, the footer.
tcDrawTable()
BEGIN
  LOCAL dwD, dwR, dwX, dwY, dwS, dwC;
  tcTblBase();
  IF tcHiR > 0 THEN
    FOR dwD FROM tcOff + 1 TO tcOff + 5 DO tcCellDraw(G1, tcHiR, dwD, 1); END;
    dwY := tcTY0 + (tcHiR - 1) * tcRH;
    RECT_P(G1, 0, dwY, 2, dwY + tcRH - 1, tcColOf(tcHiR), tcColOf(tcHiR));
  END;
  IF tcHiC > tcOff AND tcHiC <= tcOff + 5 THEN
    FOR dwR FROM 1 TO 4 DO tcCellDraw(G1, dwR, tcHiC, 1); END;
    dwX := 54 + (tcHiC - tcOff - 1) * 53;
    RECT_P(G1, dwX, tcTY0 - 2, dwX + 52, tcTY0 - 1, #000000h, #000000h);
  END;
  tcCellDraw(G1, tcRow, tcDes, 1);
  dwX := 54 + (tcDes - tcOff - 1) * 53;
  dwY := tcTY0 + (tcRow - 1) * tcRH;
  tcBox2(G1, dwX, dwY, dwX + 52, dwY + tcRH - 1, tcRED);
  // the status line: what the lit row or column shares, or the
  // selected test's full name
  IF tcHiR > 0 THEN
    dwS := tcRowSum(tcHiR); dwC := tcColOf(tcHiR);
  ELSE
    IF tcHiC > 0 THEN
      dwS := tcColSum(tcHiC); dwC := #000000h;
    ELSE
      dwS := tcFull(tcIdFor(tcRow, tcDes)); dwC := tcColOf(tcRow);
    END;
  END;
  TEXTOUT_P(dwS, G1, 6, 183, 1, dwC, 308);
  TEXTOUT_P("tap twice or Ent = details, +/- = row/col", G1, 6, 206, 1, tcFOOT, 308);
  tcShow();
END;

// The table's loop.  First tap on a cell selects it; a second tap
// on the same cell opens it.  A row label or a column heading
// lights the whole row or column (+ and - do the same from the
// keyboard).  Returns 0 for the menu, or 1 / 3 / 9 from a soft key.
tcTable()
BEGIN
  LOCAL tbK, tbR, tbC, tbSa, tbD, tbRet;
  LOCAL tbArm, tbX, tbY;
  tcGeom(0);
  IF tcDes < tcOff + 1 OR tcDes > tcOff + 5 THEN
    tcOff := 1;
    IF tcDes < 2 THEN tcOff := 0; END;
  END;
  tbRet := -1;
  tbArm := 0;                      // the cell tapped once (row * 10 + design), 0 = none
  WHILE tbRet == -1 DO
    tcDrawTable();
    tbK := tcWaitAny();
    IF tbK == -1 THEN
      tbX := tcTX; tbY := tcTY;
      IF tbY >= 218 THEN
        tbSa := tcSoftAct(tcSlot(tbX));
        IF tbSa == 0 THEN tbK := 4; END;
        IF tbSa > 0 AND tbSa <> 2 THEN tbRet := tbSa; END;
      ELSE
        IF tbX < 54 THEN
          IF tbY < tcTY0 THEN
            // the corner key: the other page of the table
            tcOff := 1 - tcOff;
            IF tcDes < tcOff + 1 THEN tcDes := tcOff + 1; END;
            IF tcDes > tcOff + 5 THEN tcDes := tcOff + 5; END;
            tcHiC := 0; tbArm := 0;
          ELSE
            tbR := IP((tbY - tcTY0) / tcRH) + 1;
            IF tbR <= 4 THEN                    // a row label: light the row
              IF tcHiR == tbR THEN tcHiR := 0; ELSE tcHiR := tbR; END;
              tcHiC := 0; tbArm := 0;
            END;
          END;
        ELSE
          tbC := IP((tbX - 54) / 53) + 1;
          IF tbC <= 5 THEN
            tbD := tbC + tcOff;
            IF tbY < tcTY0 THEN                 // a column heading: light the column
              IF tcHiC == tbD THEN tcHiC := 0; ELSE tcHiC := tbD; END;
              tcHiR := 0; tbArm := 0;
            ELSE
              tbR := IP((tbY - tcTY0) / tcRH) + 1;
              IF tbR <= 4 THEN
                IF tbArm == tbR * 10 + tbD THEN
                  tbK := 30;                    // the second tap: open it, as Enter does
                ELSE
                  tcRow := tbR; tcDes := tbD;   // the first tap: select it
                  tbArm := tbR * 10 + tbD;
                  tcHiR := 0; tcHiC := 0;
                END;
              END;
            END;
          END;
        END;
      END;
    END;
    // the keys (and the second tap, which arrives here as Enter)
    IF tbK == 7 OR tbK == 8 OR tbK == 2 OR tbK == 12 THEN
      tbArm := 0; tcHiR := 0; tcHiC := 0;
    END;
    IF tbK == 7 AND tcDes > 1 THEN
      tcDes := tcDes - 1;
      IF tcDes < tcOff + 1 THEN tcOff := tcDes - 1; END;     // slide to the one-sample page
    END;
    IF tbK == 8 AND tcDes < 6 THEN
      tcDes := tcDes + 1;
      IF tcDes > tcOff + 5 THEN tcOff := tcDes - 5; END;
    END;
    IF tbK == 2 AND tcRow > 1 THEN tcRow := tcRow - 1; END;
    IF tbK == 12 AND tcRow < 4 THEN tcRow := tcRow + 1; END;
    IF tbK == 50 THEN                                          // + : light the row
      IF tcHiR == tcRow THEN tcHiR := 0; ELSE tcHiR := tcRow; END;
      tcHiC := 0;
    END;
    IF tbK == 45 THEN                                          // - : light the column
      IF tcHiC == tcDes THEN tcHiC := 0; ELSE tcHiC := tcDes; END;
      tcHiR := 0;
    END;
    IF tbK == 30 THEN
      tbArm := 0;
      tbSa := tcDetails(tcIdFor(tcRow, tcDes), tcRow, tcDes);
      IF tbSa > 0 AND tbSa <> 2 THEN tbRet := tbSa; END;
    END;
    IF tbK == 4 THEN tbRet := 0; END;
  END;
  RETURN tbRet;
END;


// ------------------------------------------------------------
// THE QUIZ: a case above the table; tap the test that fits.
// ------------------------------------------------------------

// A new round: the twenty cases in a random order, no repeats
// (Fisher-Yates: each place from the last down to the second swaps
// with a random place at or before it), and the score cleared.
tcQNew()
BEGIN
  LOCAL nqI, nqJ, nqT, nqN;
  tcQOrd := {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
  FOR nqI FROM 0 TO tcQN - 2 DO
    nqN := tcQN - nqI;                     // tcQN, tcQN - 1, ..., 2
    nqJ := IP(RANDOM * nqN) + 1;           // 1 .. nqN
    IF nqJ > nqN THEN nqJ := nqN; END;
    nqT := tcQOrd(nqN);
    tcQOrd(nqN) := tcQOrd(nqJ);
    tcQOrd(nqJ) := nqT;
  END;
  tcQPos := 1; tcQRight := 0; tcQAsk := 0;
END;

// A quiz frame.  qwQ: the case (from tcQ).  qwSt: 0 asking (the
// keyboard cursor is a red frame only, so it gives nothing away),
// 1 wrong (the tapped cell red, the right one outlined in green,
// the reason in red), 2 right (the cell green, the reason in green),
// 3 a flash's "off" frame.  qwR / qwD: the cell answered.
tcQuizDraw(qwQ, qwSt, qwR, qwD)
BEGIN
  LOCAL qwX, qwY, qwS, qwN;
  tcTblBase();
  TEXTOUT_P(qwQ(4), G1, 4, 2, 1, #000000h, 312);
  TEXTOUT_P(qwQ(5), G1, 4, 14, 1, #000000h, 312);
  TEXTOUT_P(qwQ(6), G1, 4, 26, 1, #000000h, 312);
  qwN := tcQPos - 1;                         // the case just answered
  IF qwSt == 0 THEN
    qwN := tcQPos;                           // the case being asked
    qwX := 54 + (tcDes - tcOff - 1) * 53;
    qwY := tcTY0 + (tcRow - 1) * tcRH;
    tcBox2(G1, qwX, qwY, qwX + 52, qwY + tcRH - 1, tcRED);
    TEXTOUT_P("tap the test, ⇆⇅ + Ent = answer, Back = menu", G1, 6, 205, 1, tcFOOT, 308);
  END;
  IF qwSt == 1 THEN
    tcCellDraw(G1, qwR, qwD, 3);
    qwX := 54 + (qwQ(2) - tcOff - 1) * 53;
    qwY := tcTY0 + (qwQ(1) - 1) * tcRH;
    tcBox2(G1, qwX, qwY, qwX + 52, qwY + tcRH - 1, tcGRN);
    tcBox2(G1, qwX + 2, qwY + 2, qwX + 50, qwY + tcRH - 3, tcGRN);
    TEXTOUT_P(qwQ(7), G1, 6, 181, 1, tcRED, 308);
    TEXTOUT_P("tap green = its details, Ent or tap = next", G1, 6, 205, 1, tcFOOT, 308);
  END;
  IF qwSt == 2 THEN
    tcCellDraw(G1, qwR, qwD, 2);
    TEXTOUT_P(qwQ(7), G1, 6, 181, 1, tcGRN, 308);
  END;
  IF qwSt == 3 THEN tcCellDraw(G1, qwR, qwD, 0); END;
  qwS := "Score = " + STRING(tcQRight, 2, 0) + " of " + STRING(tcQAsk, 2, 0) + ", case " + STRING(qwN, 2, 0) + " of " + STRING(tcQN, 2, 0);
  TEXTOUT_P(qwS, G1, 6, 193, 1, #000000h, 308);
  tcShow();
END;

// Flash the answered cell three times (green or red), ending on it.
tcQFlash(qfQ, qfSt, qfR, qfD)
BEGIN
  LOCAL qfI;
  FOR qfI FROM 1 TO 3 DO
    tcQuizDraw(qfQ, qfSt, qfR, qfD);
    tcPause(110);
    tcQuizDraw(qfQ, 3, qfR, qfD);
    tcPause(90);
  END;
  tcQuizDraw(qfQ, qfSt, qfR, qfD);
END;

// The end of a round: the score and the usual traps.  Returns -1 to
// play again (a new round is ready), 0 for the menu, or 1 / 2 / 9
// from a soft key.
tcQEnd()
BEGIN
  LOCAL qeK, qeSa, qeRet, qeS;
  qeRet := -2;
  WHILE qeRet == -2 DO
    DIMGROB_P(G1, 320, 240, #FFFFFFh);
    RECT_P(G1, 4, 4, 315, 214, tcORNG, tcLORN);
    TEXTOUT_P("Round over", G1, 12, 10, 2, tcORNG);
    qeS := "You picked the right test " + STRING(tcQRight, 2, 0) + " times out of " + STRING(tcQAsk, 2, 0) + ".";
    TEXTOUT_P(qeS, G1, 12, 36, 1, #000000h, 296);
    qeS := "Back to the Guide: scale first, then design.";
    IF tcQRight >= 14 THEN qeS := "Good.  Look again at the ones you missed."; END;
    IF tcQRight >= 18 THEN qeS := "Excellent: you read designs like an analyst."; END;
    TEXTOUT_P(qeS, G1, 12, 52, 1, tcGRN, 296);
    TEXTOUT_P("The usual traps:", G1, 12, 76, 1, tcGRAY);
    TEXTOUT_P("- the same people measured twice is a", G1, 12, 90, 1, tcBLUE, 296);
    TEXTOUT_P("  PAIRED design, not two groups;", G1, 12, 102, 1, tcBLUE, 296);
    TEXTOUT_P("- Likert ratings are ordinal: use ranks;", G1, 12, 116, 1, tcMAG, 296);
    TEXTOUT_P("- yes/no outcomes are nominal: counts;", G1, 12, 130, 1, tcORNG, 296);
    TEXTOUT_P("- time to an event, with some people", G1, 12, 144, 1, tcGRN, 296);
    TEXTOUT_P("  event-free at the end, is survival.", G1, 12, 156, 1, tcGRN, 296);
    TEXTOUT_P("Ent or tap = new round, Back = menu", G1, 12, 197, 1, tcFOOT, 296);
    tcShow();
    qeK := tcWaitAny();
    IF qeK == -1 AND tcTY >= 218 THEN
      qeSa := tcSoftAct(tcSlot(tcTX));
      IF qeSa == 3 THEN tcQNew(); qeRet := -1; END;
      IF qeSa == 0 OR qeSa == 1 OR qeSa == 2 OR qeSa == 9 THEN qeRet := qeSa; END;
    ELSE
      IF qeK == 4 THEN qeRet := 0; END;
      IF qeK == 30 OR qeK == -1 THEN tcQNew(); qeRet := -1; END;
    END;
  END;
  RETURN qeRet;
END;

// The quiz's loop.  The round (order, place, score) lives in
// file-level variables, so leaving for the Guide or the Table and
// coming back carries on where it was.  Returns 0 for the menu, or
// 1 / 2 / 9 from a soft key.
tcQuiz()
BEGIN
  LOCAL qzRet, qzK, qzSt, qzQ, qzR, qzD;
  LOCAL qzSa, qzX, qzY, qzOk, qzDr, qzC;
  LOCAL qzCol;
  tcGeom(1);
  tcOff := 1; tcHiR := 0; tcHiC := 0;      // Glantz's five columns: every case is in them
  IF tcDes < 2 THEN tcDes := 2; END;
  IF tcQPos < 1 THEN tcQNew(); END;
  qzRet := -1; qzSt := 0; qzR := 1; qzD := 2;
  WHILE qzRet == -1 DO
    IF qzSt == 0 AND tcQPos > tcQN THEN
      qzRet := tcQEnd();
    ELSE
      IF qzSt == 0 THEN tcQCur := tcQOrd(tcQPos); END;
      qzQ := tcQ(tcQCur);
      tcQuizDraw(qzQ, qzSt, qzR, qzD);
      qzK := tcWaitAny();
      qzC := 0;                     // the cell tapped: row * 10 + design, 0 = none
      IF qzK == -1 THEN
        qzX := tcTX; qzY := tcTY;
        IF qzY >= 218 THEN
          qzK := -2;                // a soft key is never an answer
          qzSa := tcSoftAct(tcSlot(qzX));
          IF qzSa == 0 THEN qzK := 4; END;
          IF qzSa > 0 AND qzSa <> 3 THEN qzRet := qzSa; END;
        ELSE
          IF qzX >= 54 AND qzY >= tcTY0 AND qzY < tcTY0 + 4 * tcRH THEN
            qzCol := IP((qzX - 54) / 53) + 1;
            IF qzCol <= 5 THEN
              qzC := (IP((qzY - tcTY0) / tcRH) + 1) * 10 + qzCol + tcOff;
            END;
          END;
        END;
      END;
      IF qzRet == -1 THEN
        IF qzSt == 0 THEN
          // asking: the arrows move the red frame, Enter answers with it
          IF qzK == 7 AND tcDes > 2 THEN tcDes := tcDes - 1; END;
          IF qzK == 8 AND tcDes < 6 THEN tcDes := tcDes + 1; END;
          IF qzK == 2 AND tcRow > 1 THEN tcRow := tcRow - 1; END;
          IF qzK == 12 AND tcRow < 4 THEN tcRow := tcRow + 1; END;
          IF qzK == 30 THEN qzC := tcRow * 10 + tcDes; END;
          IF qzK == 4 THEN qzRet := 0; END;
          IF qzC > 0 THEN
            qzR := IP(qzC / 10); qzD := qzC - qzR * 10;
            tcRow := qzR; tcDes := qzD;
            tcQPos := tcQPos + 1; tcQAsk := tcQAsk + 1;
            qzOk := 0;
            IF qzR == qzQ(1) AND qzD == qzQ(2) THEN qzOk := 1; END;
            IF qzC == qzQ(3) THEN qzOk := 1; END;      // the also-accepted cell
            IF qzOk == 1 THEN
              tcQRight := tcQRight + 1;
              tcQFlash(qzQ, 2, qzR, qzD);
              tcPause(600);
              qzDr := tcDetails(tcIdFor(qzR, qzD), qzR, qzD);
              IF qzDr > 0 AND qzDr <> 3 THEN qzRet := qzDr; END;
            ELSE
              tcQFlash(qzQ, 1, qzR, qzD);
              qzSt := 1;
            END;
          END;
        ELSE
          // the wrong answer is on the screen: the green cell opens
          // the right test; Enter or any other tap goes on
          IF qzC == qzQ(1) * 10 + qzQ(2) THEN
            qzDr := tcDetails(tcIdFor(qzQ(1), qzQ(2)), qzQ(1), qzQ(2));
            IF qzDr > 0 AND qzDr <> 3 THEN qzRet := qzDr; END;
            qzSt := 0;
          ELSE
            IF qzK == 4 THEN qzRet := 0; END;
            IF qzK == 30 OR qzK == -1 THEN qzSt := 0; END;
          END;
        END;
      END;
    END;
  END;
  RETURN qzRet;
END;


// ------------------------------------------------------------
// HELP, INIT, MAIN
// ------------------------------------------------------------

// Help page 1: the two questions.
tcHelp1()
BEGIN
  TEXTOUT_P("Choosing a test: two questions", G1, 12, 8, 2, tcBLUE);
  TEXTOUT_P("1. What kind of number is the OUTCOME?", G1, 12, 30, 1, #000000h);
  TEXTOUT_P("   Interval/ratio: measurements, about", G1, 12, 42, 1, tcBLUE);
  TEXTOUT_P("     normal -> t tests, ANOVA, regression.", G1, 12, 54, 1, tcBLUE);
  TEXTOUT_P("   Nominal: categories -> proportions,", G1, 12, 66, 1, tcORNG);
  TEXTOUT_P("     chi-square, odds ratios.", G1, 12, 78, 1, tcORNG);
  TEXTOUT_P("   Ordinal, or skewed, or tiny n -> ranks:", G1, 12, 90, 1, tcMAG);
  TEXTOUT_P("     Mann-Whitney, Kruskal-Wallis ...", G1, 12, 102, 1, tcMAG);
  TEXTOUT_P("   Survival: time to event, some censored.", G1, 12, 114, 1, tcGRN);
  TEXTOUT_P("2. What is the DESIGN?  Different people", G1, 12, 130, 1, #000000h);
  TEXTOUT_P("   per group, or the same people again?", G1, 12, 142, 1, #000000h);
  TEXTOUT_P("   How many groups?  Two variables linked?", G1, 12, 154, 1, #000000h);
  TEXTOUT_P("The table is from Glantz's Primer of", G1, 12, 170, 1, tcGRAY);
  TEXTOUT_P("Biostatistics, with a one-sample column.", G1, 12, 182, 1, tcGRAY);
  TEXTOUT_P("tap or Ent = page 2, Back = menu", G1, 12, 198, 1, tcFOOT);
END;

// Help page 2: the gestures, the keys and the quiz.
tcHelp2()
BEGIN
  TEXTOUT_P("Touch, keys and the quiz", G1, 12, 8, 2, tcBLUE);
  TEXTOUT_P("Guide: tap a scale, then a design; the", G1, 12, 30, 1, tcBLUE);
  TEXTOUT_P("  orange path ends at the test.  Tap the", G1, 12, 42, 1, tcBLUE);
  TEXTOUT_P("  test (or Ent) for its details page.", G1, 12, 54, 1, tcBLUE);
  TEXTOUT_P("  Back = up a level; ⇆⇅ and Ent work too.", G1, 12, 66, 1, tcBLUE);
  TEXTOUT_P("Table: tap a cell to select it, tap it", G1, 12, 82, 1, tcMAG);
  TEXTOUT_P("  again (or Ent) for the details.  Tap a", G1, 12, 94, 1, tcMAG);
  TEXTOUT_P("  row name or a heading to light it up", G1, 12, 106, 1, tcMAG);
  TEXTOUT_P("  (+ = row, - = column).  The corner key", G1, 12, 118, 1, tcMAG);
  TEXTOUT_P("  shows the one-sample column.", G1, 12, 130, 1, tcMAG);
  TEXTOUT_P("Hatched cells: not on the Prime (R, JMP).", G1, 12, 146, 1, tcGRAY);
  TEXTOUT_P("Quiz: read the case, tap its test.  Green", G1, 12, 162, 1, tcGRN);
  TEXTOUT_P("  = right; red = wrong, and the right", G1, 12, 174, 1, tcGRN);
  TEXTOUT_P("  cell is outlined in green.", G1, 12, 186, 1, tcGRN);
  TEXTOUT_P("tap or Ent = menu, Left = page 1", G1, 12, 200, 1, tcFOOT);
END;

// The help pages.  Returns 0 for the menu, or 1 / 2 / 3 / 9 from a
// soft key.
tcHelp()
BEGIN
  LOCAL hpP, hpK, hpRet, hpSa;
  hpP := 1; hpRet := -1;
  WHILE hpRet == -1 DO
    DIMGROB_P(G1, 320, 240, #FFFFFFh);
    RECT_P(G1, 4, 4, 315, 214, tcORNG, tcLORN);
    IF hpP == 1 THEN tcHelp1(); ELSE tcHelp2(); END;
    tcShow();
    hpK := tcWaitAny();
    IF hpK == -1 AND tcTY >= 218 THEN
      hpSa := tcSoftAct(tcSlot(tcTX));
      IF hpSa >= 0 THEN hpRet := hpSa; END;
    ELSE
      IF hpK == 4 THEN hpRet := 0; END;
      IF hpK == 7 THEN hpP := 1; END;
      IF hpK == 8 OR hpK == 30 OR hpK == -1 THEN
        IF hpP == 1 THEN hpP := 2; ELSE hpRet := 0; END;
      END;
    END;
  END;
  RETURN hpRet;
END;

tcInit()
BEGIN
  tcBLUE := #003CC8h; tcMAG := #C800C8h; tcORNG := #FF4500h;
  tcGRN := #008C3Ch; tcGRAY := #5A5A5Ah; tcFOOT := #1E90FFh;
  tcLBLU := #EBF2FFh; tcLMAG := #FAEBFAh; tcLORN := #FFF0DCh;
  tcLGRN := #E8F6EBh; tcLGRY := #F0F0F0h; tcDODG := #1E90FFh;
  tcLDOD := #E6F3FFh; tcRED := #FF0000h; tcLRED := #FFEBEBh;
  tcHATCH := #B4B4B4h; tcPALE := #A0A0A0h;
  tcRow := 1; tcDes := 2; tcOff := 1; tcHiR := 0; tcHiC := 0;
  tcGS := 1; tcGD := 2;
  tcTX := -1; tcTY := -1;
  tcHN := 0;
  tcHX1 := {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  tcHY1 := {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  tcHX2 := {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  tcHY2 := {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  tcHId := {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  // 1 = the test's details say "Not on the Prime" (tests 4, 9, 11-14,
  // 16-20, 23, 24): these cells are hatched.  Checked against tcDetail
  // on the PC.  beta 2a (T3): test 18, the before/after "(none)"
  // survival cell, says so on its fourth line and is hatched like its
  // twin, test 19.
  tcNotP := {0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1};
  tcCK := -1;                                // G2 holds no table yet
  tcQN := 20; tcQPos := 0; tcQCur := 1; tcQRight := 0; tcQAsk := 0;
  tcQOrd := {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
  tcGeom(0);
  TC_TEST := "";
END;

EXPORT TestChooser()
BEGIN
  LOCAL mnI, mnMode;
  tcInit();
  // mnMode: 0 the menu, 1 the Guide, 2 the Table, 3 the Quiz,
  // 4 Help, 9 quit.  Each screen returns the next mode, so a soft
  // key can jump straight from one screen to another.
  mnMode := 0;
  WHILE mnMode <> 9 DO
    IF mnMode == 0 THEN
      mnI := 0;
      CHOOSE(mnI, "Which statistical test?", {
        "Guide me: the flowchart",
        "Browse the table (Glantz)",
        "Quiz: which test fits?",
        "Help",
        "Quit"});
      mnMode := 9;                              // Quit, or Esc on the menu
      IF mnI >= 1 AND mnI <= 4 THEN mnMode := mnI; END;
    ELSE
      IF mnMode == 1 THEN
        mnMode := tcGuide();
      ELSE
        IF mnMode == 2 THEN
          mnMode := tcTable();
        ELSE
          IF mnMode == 3 THEN mnMode := tcQuiz(); ELSE mnMode := tcHelp(); END;
        END;
      END;
    END;
  END;
  // Hand the summary back as the program's value: Home shows it in
  // the history and nothing is left open.  TC_TEST keeps the name.
  IF DIM(TC_TEST) == 0 THEN RETURN "TestChooser: no test looked at"; END;
  RETURN "TestChooser: " + TC_TEST;
END;
