-- MathQuiz.mesa 
--   Edited by Sweet, February 24, 1981  10:34 PM

DIRECTORY
  Ascii,
  Inline,
  PressDefs,
  PressUtilities,
  Random,
  String;

MathQuiz: PROGRAM
  IMPORTS Inline, PressDefs, PressUtilities, Random, String =
  BEGIN OPEN PressDefs;

  pfdBody: PressFileDescriptor;
  pfd: POINTER TO PressFileDescriptor = @pfdBody;
  Mica: TYPE = CARDINAL;
  MBox: TYPE = RECORD [x,y,w,h: Mica];
  LineWidth: Mica ← 30;
  CharHeight: Mica;
  CharWidth: POINTER TO ARRAY CHARACTER OF Mica;
  MathCharWidth: ARRAY CHARACTER OF Mica;
  TextCharWidth: ARRAY CHARACTER OF Mica;
  AnswerCharWidth: ARRAY CHARACTER OF Mica;
  MathCharHeight: Mica;
  TextCharHeight: Mica;
  AnswerCharHeight: Mica;
  Minus: CHARACTER = Ascii.ControlX;

  answers: BOOLEAN ← FALSE;

  Sort: PUBLIC PROCEDURE [
      a: DESCRIPTOR FOR ARRAY OF UNSPECIFIED, 
      Greater: PROC[UNSPECIFIED, UNSPECIFIED] RETURNS [BOOLEAN]] =
    BEGIN
    n: CARDINAL = LENGTH[a];
    i: CARDINAL;
    temp: CARDINAL;
    SiftUp: PROC [l, u: CARDINAL] =
      BEGIN
      s: CARDINAL;
      key: CARDINAL ← a[l-1];
      DO
        s ← l*2;
        IF s > u THEN EXIT;
        IF s < u AND Greater[a[s+1-1], a[s-1]] THEN s ← s+1;
        IF Greater[key, a[s-1]] THEN EXIT;
        a[l-1] ← a[s-1];
        l ← s;
        ENDLOOP;
      a[l-1] ← key;
      END;
    FOR i DECREASING IN [2..n/2] DO SiftUp[i, n]; ENDLOOP;
    FOR i DECREASING IN [2..n] DO
      SiftUp[1, i];
      temp ← a[1-1];
      a[1-1] ← a[i-1];
      a[i-1] ← temp;
      ENDLOOP;
    END;

  iRandom: ARRAY [0..32) OF CARDINAL;

  RandomizeI: PROC [iMax: CARDINAL] =
    BEGIN
    r:  ARRAY [0..32) OF CARDINAL;
    rGreater: PROC [x, y: CARDINAL] RETURNS [BOOLEAN] =
      {RETURN [r[x] > r[y]]};
    i: CARDINAL;
    FOR i IN [0..iMax) DO
      r[i] ← Random.InRange[0, 1000];
      iRandom[i] ← i;
      ENDLOOP;
    Sort[DESCRIPTOR[@iRandom, iMax], rGreater ];
    END;
      
  PointsToMicas: PROC [points: CARDINAL] RETURNS [Mica] =
    {RETURN [Inline.LongDiv[Inline.LongMult[points, MicasPerInch],72]]};

  StringWidth: PROC [s: STRING] RETURNS [l: Mica] =
    BEGIN
    l ← 0;
    FOR i: CARDINAL IN [0..s.length) DO
      l ← l + CharWidth[s[i]];
      ENDLOOP;
    END;


  P1: Mica = PointsToMicas[1];
  P2: Mica = PointsToMicas[2];
  M1: Mica = MicasPerInch;
  M12: Mica = MicasPerInch/2;
  M14: Mica = MicasPerInch/4;
  M34: Mica = (3*MicasPerInch)/4;
  M38: Mica = (3*MicasPerInch)/8;

  CenterNum: PROC [n: CARDINAL, box: POINTER TO MBox] =
    BEGIN
    w: Mica;
    ns: STRING ← [2];
    String.AppendDecimal[ns, n];
    w ← StringWidth[ns];
    PutText[pfd, ns, box.x + (box.w-w)/2, box.y + (box.h-CharHeight)/2];
    END;

  CenterChar: PROC [c: CHARACTER, box: POINTER TO MBox] =
    BEGIN
    w: Mica;
    ns: STRING ← [2];
    ns.length ← 1; ns[0] ← c;
    w ← StringWidth[ns];
    PutText[pfd, ns, box.x + (box.w-w)/2, box.y + (box.h-CharHeight)/2];
    END;

  HorizNumberSentence: PROC [x,y: Mica, a, b: CARDINAL] RETURNS [Mica] =
    BEGIN
    box: MBox;
    op: CHARACTER ← '+;

    TextFont[];
    box ← [x: x, y: y-M38, w: M38, h: M38];
    CenterNum[a, @box];
    box.x ← box.x + M38; box.w ← M14;
    CenterChar[op, @box];
    box.x ← box.x + M14; box.w ← M38;
    CenterNum[b, @box];
    box.x ← box.x + M38; box.w ← M14;
    CenterChar['=, @box];
    PutRectangle[p: pfd,
      xstart: box.x + M14,
      ystart: y-M38,
      xlen: M12,
      ylen: P1];
    IF answers THEN
      BEGIN
      c: CARDINAL ← IF op = '+ THEN a+b ELSE a-b;
      AnswerFont[];
      box.x ← box.x + M14; box.w ← M12;
      CenterNum[c, @box];
      TextFont[];
      END;
    RETURN[y - M12];
    END;

  margin: Mica = (3*M1 + M14)/4;
  pwidth: Mica = M1+M34;

  DoNumber: PROC [n: CARDINAL, y: Mica] RETURNS [nextY: Mica] =
    BEGIN
    count: CARDINAL ← 0;
    x: Mica ← margin;
    RandomizeI[11-n];
    FOR i: CARDINAL IN [0..11-n) DO
      IF count = 3 THEN {count ← 0; x ← margin; y ← nextY};
      nextY ← HorizNumberSentence[x, y, n, iRandom[i]];
      x ← x + pwidth + margin;
      count ← count + 1;
      ENDLOOP;
    END;

  DoPage: PROC [first, last: CARDINAL] =
    BEGIN
    y: Mica ← 10*M1;
    FOR i: CARDINAL IN [first..last) DO
      y ← DoNumber[i, y];
      y ← y - M12;
      PutRectangle[p: pfd,
        xstart: margin,
        ystart: y,
        xlen: 8*M1+M12-2*margin,
        ylen: P1];
      y ← y - M12;
      ENDLOOP;
    y ← DoNumber[last, y];
    WritePage[pfd];
    END;

  TextFont: PROC =
    BEGIN
    SetFont[p: pfd, Name: "Helvetica", PointSize: 18, Face: 2];
    CharHeight ← TextCharHeight;
    CharWidth ← @TextCharWidth;
    END;

  AnswerFont: PROC =
    BEGIN
    SetFont[p: pfd, Name: "Helvetica", PointSize: 14, Face: 1];
    CharHeight ← AnswerCharHeight;
    CharWidth ← @AnswerCharWidth;
    END;

  MathFont: PROC =
    BEGIN
    SetFont[p: pfd, Name: "Math", PointSize: 10, Face: 0];
    CharHeight ← MathCharHeight;
    CharWidth ← @MathCharWidth;
    END;

  DigestFonts: PROC =
    BEGIN
    [] ← PressUtilities.FindFontWidths[
      family: "Helvetica"L,
      points: 18,
      weight: bold,
      slope: regular,
      widths: LOOPHOLE[@TextCharWidth]];
    TextCharHeight ← PointsToMicas[18];
    [] ← PressUtilities.FindFontWidths[
      family: "Helvetica"L,
      points: 14,
      weight: medium,
      slope: italic,
      widths: LOOPHOLE[@AnswerCharWidth]];
    AnswerCharHeight ← PointsToMicas[14];
    [] ← PressUtilities.FindFontWidths[
      family: "Math"L,
      points: 10,
      weight: medium,
      slope: regular,
      widths: LOOPHOLE[@MathCharWidth]];
    MathCharHeight ← PointsToMicas[10];
    END;

  DoQuiz: PROC =
    BEGIN
    DoPage[0,2];
    DoPage[3,6];
    DoPage[7,10];
    END;

  Driver: PROC =
    BEGIN
    state: Random.State;
    DigestFonts[];
    InitPressFileDescriptor[pfd, "Math.drill"L];
    answers ← FALSE;
    Random.ReadState[@state];
    DoQuiz[];
    ClosePressFile[pfd];
    Random.WriteState[@state];
    InitPressFileDescriptor[pfd, "Math.key"L];
    answers ← TRUE;
    DoQuiz[];
    ClosePressFile[pfd];
    END;

  Driver[];
  END.