-- MathDrill.mesa 
--   Edited by Sweet, May 14, 1981  11:46 PM

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

MathDrill: PROGRAM
  IMPORTS Inline, IODefs, 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;

  PointsToMicas: PROC [points: CARDINAL] RETURNS [Mica] =
    {RETURN [Inline.LongDiv[Inline.LongMult[points, MicasPerInch],72]]};

  maxSum: CARDINAL ← 5;
  minVal: CARDINAL ← 1;
  plusNum: CARDINAL ← 2;
  plusDen: CARDINAL ← 3;

  Prob: TYPE = RECORD [a,b: [0..16), op: CHARACTER];

  HistSize: CARDINAL = 6;
  history: ARRAY [0..HistSize) OF Prob ← ALL[[0,0,0C]];
  hp: CARDINAL ← 0;

  Problem: PROC RETURNS [a,b: CARDINAL, op: CHARACTER] =
    BEGIN
    trial: Prob;
    DO
      trial ← TrialProblem[];
      FOR i: CARDINAL IN [0..HistSize) DO
        IF history[i] = trial THEN EXIT;
	REPEAT
	  FINISHED => GO TO ok;
        ENDLOOP;
      REPEAT
	ok => NULL;
      ENDLOOP;
    history[hp] ← trial; hp ← (hp+1) MOD HistSize;
    RETURN [trial.a, trial.b, trial.op];
    END;
  
  NonZeroTrialProblem: PROC RETURNS [trial: Prob] =
    BEGIN
    trial.op ← IF Random.InRange[1, plusDen] <= plusNum THEN '+ ELSE Minus;
    trial.a ← Random.InRange[2 * minVal,maxSum];
    trial.b ← Random.InRange[minVal,trial.a-minVal];
    IF trial.op = '+ THEN trial.a ← trial.a-trial.b;
    END;

  TrialProblem: PROC RETURNS [trial: Prob] =
    BEGIN
    IF minVal = 0 THEN RETURN[ZeroTrialProblem[]]
    ELSE RETURN [NonZeroTrialProblem[]];
    END;

  w1: CARDINAL ← 2;
  w2: CARDINAL ← 4;
  w3: CARDINAL ← 3;

  bigNum: CARDINAL ← 2;
  bigDen: CARDINAL ← 3;
  minBig: CARDINAL ← 5;

  ZeroTrialProblem: PROC RETURNS [trial: Prob] =
    BEGIN
    total, a, b: CARDINAL;
    trial.op ← IF Random.InRange[1, plusDen] <= plusNum THEN '+ ELSE Minus;
    total ← IF Random.InRange[1, bigDen] <= bigNum THEN Random.InRange[minBig, maxSum]
      ELSE Random.InRange[1,maxSum];
    a ← IF total = 0 OR Random.InRange[1, w2*total] <= w3 THEN 0 
      ELSE Random.InRange[1, total-1];
    b ← total - a;
    IF Random.InRange[1,2] = 1 THEN {t: CARDINAL = a; a ← b; b ← t};
    IF trial.op = '+ THEN {trial.a ← a; trial.b ← b}
    ELSE {trial.a ← total; trial.b ← b};
    END;

  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;

  LineY: PROC [box: POINTER TO MBox, line, of: CARDINAL, lead: Mica ← 0] 
    RETURNS [Mica] =
    BEGIN
    h: Mica = CharHeight;
    bottom: Mica = (box.h- of*h - (of-1)*lead)/2;
    RETURN [box.y + bottom + (line-1)*(h+lead)];
    END;

  CenterLine: PROC [s: STRING, box: POINTER TO MBox, line, of: CARDINAL, lead: Mica ← 0] =
    BEGIN
    w: Mica = StringWidth[s];
    y: Mica = LineY[box: box, line: line, of: of, lead: lead];
    x: Mica = box.x + (box.w - w)/2;
    PutText[pfd, s, x, y];
    END;

  RJLine: PROC [s: STRING, box: POINTER TO MBox, line, of: CARDINAL, lead: Mica ← 0] =
    BEGIN
    w: Mica = StringWidth[s];
    y: Mica = LineY[box: box, line: line, of: of, lead: lead];
    x: Mica = box.x + (box.w - w);
    PutText[pfd, s, x, y];
    END;

  LJLine: PROC [s: STRING, box: POINTER TO MBox, line, of: CARDINAL, lead: Mica ← 0] =
    BEGIN
    y: Mica = LineY[box: box, line: line, of: of, lead: lead];
    PutText[pfd, s, box.x, y];
    END;

  DomLine: PROC [c: CHARACTER, n: CARDINAL, box: POINTER TO MBox, y: Mica] =
    BEGIN
    s: STRING ← [1];
    cw: Mica = CharWidth[c];
    w: Mica = n*cw + (n-1)*InterCharWidth;
    x: Mica ← box.x + (box.w - w)/2;
    s.length ← 1; s[0] ← c;
    THROUGH [0..n) DO
      PutText[pfd, s, x, y];
      x ← x + cw + InterCharWidth;
      ENDLOOP;
    END;

  DrawBorder: PROC [box: POINTER TO MBox] =
    BEGIN
    PutRectangle[p: pfd,
      xstart: box.x, ystart: box.y, xlen: box.w + LineWidth, ylen: LineWidth];
    PutRectangle[p: pfd,
      xstart: box.x, ystart: box.y, xlen: LineWidth, ylen: box.h];
    PutRectangle[p: pfd,
      xstart: box.x + box.w, ystart: box.y, xlen: LineWidth, ylen: box.h];
    PutRectangle[p: pfd,
      xstart: box.x, ystart: box.y + box.h,
      xlen: box.w + LineWidth, ylen: LineWidth];
    END;

  Mbw: Mica ← (3*MicasPerInch)/4;
  Mbh: Mica ← (3*MicasPerInch)/4;
  Mbs: Mica ← MicasPerInch/4;
  Mch: Mica ← (3*MicasPerInch)/8;
  MPb: Mica ← PointsToMicas[2];
  InterCharWidth: Mica ← PointsToMicas[3];

  Pattern: TYPE = RECORD [lines: CARDINAL, line: ARRAY [1..5] OF CARDINAL];

  pat: ARRAY [0..10] OF Pattern ← [
    [0,],
    [1,[1,,,,]],
    [1,[2,,,,]],
    [2,[2,1,,,]],
    [2,[2,2,,,]],
    [2,[3,2,,,]],
    [3,[3,2,1,,]],
    [3,[3,2,2,,]],
    [3,[3,3,2,,]],
    [3,[4,3,2,,]],
    [4,[4,3,2,1,]]];

  Domino: PROC [c: CHARACTER, n: CARDINAL, box: POINTER TO MBox] =
    BEGIN
    p: Pattern = pat[n];
    DrawBorder[box];
    MathFont[];
    FOR i: CARDINAL IN [1..p.lines] DO
      DomLine[
	c: c,
        n: p.line[i],
        box: box,
        y: LineY[box: box, line: i, of: p.lines, lead: MPb]];
      ENDLOOP;
    END;

  DomChar: ARRAY [0..4) OF CHARACTER ← ['1, '2, '8, '0];

  GraphicNumberSentence: PROC [x,y: Mica] RETURNS [Mica] =
    BEGIN
    ns: STRING ← [2];
    box: MBox;
    a,b: CARDINAL;
    op: CHARACTER;
    dc: CHARACTER ← DomChar[Random.InRange[0,3]];
    yDom: Mica = y - Mbh;
    yText: Mica = yDom - Mch;

    [a, b, op] ← Problem[];
    box ← [x: x, y: yDom, w: Mbw, h: Mbh];
    Domino[dc, a, @box];
    box.x ← box.x + Mbw + Mbs;
    Domino[dc, b, @box];
    TextFont[];
    box ← [x: x, y: yText, w: Mbw, h: Mch];
    ns.length ← 0;
    String.AppendDecimal[ns, a];
    CenterLine[s: ns, box: @box, line: 1, of: 1];
    box ← [x: x + Mbw, y: yText, w: Mbs, h: Mch];
    ns.length ← 1;
    ns[0] ← op;
    CenterLine[s: ns, box: @box, line: 1, of: 1];
    ns.length ← 0;
    String.AppendDecimal[ns, b];
    box ← [x: x + Mbw + Mbs, y: yText, w: Mbw, h: Mch];
    CenterLine[s: ns, box: @box, line: 1, of: 1];
    box ← [x: x + 2*Mbw + Mbs, y: yText, w: Mbs, h: Mch];
    CenterLine[s: "="L, box: @box, line: 1, of: 1];
    PutRectangle[p: pfd,
      xstart: x + 2*Mbw + 2*Mbs,
      ystart: yText,
      xlen: M12,
      ylen: P1];
    IF answers THEN
      BEGIN
      c: CARDINAL ← IF op = '+ THEN a+b ELSE a-b;
      ns.length ← 0;
      AnswerFont[];
      String.AppendDecimal[ns, c];
      box.x ← box.x + Mbs; box.w ← M12;
      CenterLine[s: ns, box: @box, line: 1, of: 1];
      TextFont[];
      END;
    RETURN[y - (3*MicasPerInch)/2];
    END;

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

  VertNumberSentence: PROC [x,y: Mica] RETURNS [Mica] =
    BEGIN
    box: MBox;
    a,b: CARDINAL;
    op: CHARACTER;
    ns: STRING ← [3];

    [a, b, op] ← Problem[];
    box ← [x: x, y: y-M12+CharHeight + 2*P2, w: M12, h: CharHeight];
    ns.length ← 0;
    String.AppendDecimal[ns, a];
    RJLine[s: ns, box: @box, line: 1, of: 1];
    box.y ← box.y - CharHeight - P2;
    ns.length ← 0;
    String.AppendChar[ns, op];
    String.AppendDecimal[ns, b];
    RJLine[s: ns, box: @box, line: 1, of: 1];
    PutRectangle[p: pfd,
      xstart: x,
      ystart: y - M12 - P2,
      xlen: M12,
      ylen: P2];
    IF answers THEN
      BEGIN
      c: CARDINAL ← IF op = '+ THEN a+b ELSE a-b;
      ns.length ← 0;
      AnswerFont[];
      String.AppendDecimal[ns, c];
      box.y ← y - M12 - 3*P2 - CharHeight;
      RJLine[s: ns, box: @box, line: 1, of: 1];
      TextFont[];
      END;
    RETURN [y - MicasPerInch];
    END;

  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] RETURNS [Mica] =
    BEGIN
    box: MBox;
    a,b: CARDINAL;
    op: CHARACTER;
    ns: STRING ← [3];

    [a, b, op] ← Problem[];
    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;

  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;

  FirstDoPage: PROC =
    BEGIN
    x, y: Mica;
    x ← MicasPerInch;
    y ← 10*MicasPerInch;
    THROUGH [0..6) DO y ← GraphicNumberSentence[x, y]; ENDLOOP;
    x ← 4*MicasPerInch;
    y ← 10*MicasPerInch;
    PutRectangle[p: pfd,
      xstart: x - M14,
      ystart: MicasPerInch,
      xlen: P1,
      ylen: 9*MicasPerInch];
    THROUGH [0..9) DO 
      [] ← VertNumberSentence[x, y];
      y ← VertNumberSentence[x + MicasPerInch, y];
      ENDLOOP;
    x ← x + 2*MicasPerInch;
    y ← 10*MicasPerInch;
    PutRectangle[p: pfd,
      xstart: x - M14,
      ystart: MicasPerInch,
      xlen: P1,
      ylen: 9*MicasPerInch];
    THROUGH [0..18) DO y ← HorizNumberSentence[x, y]; ENDLOOP;
    END;

  DoPage: PROC =
    BEGIN
    x, y: Mica;
    AnswerFont[];
    TextFont[]; -- to make sure font is correctly set
    x ← MicasPerInch;
    y ← 10*MicasPerInch;
    THROUGH [0..9) DO 
      [] ← VertNumberSentence[x, y];
      [] ← VertNumberSentence[x + MicasPerInch, y];
      y ← VertNumberSentence[x + 2*MicasPerInch, y];
      ENDLOOP;
    x ← x+ 3*MicasPerInch; 
    y ← 10*MicasPerInch;
    PutRectangle[p: pfd,
      xstart: x - (2*M14)/3,
      ystart: MicasPerInch,
      xlen: P1,
      ylen: 9*MicasPerInch];
    THROUGH [0..18) DO 
      [] ← HorizNumberSentence[x, y];
      y ← HorizNumberSentence[x + 2*MicasPerInch, y];
      ENDLOOP;
    END;

  pageCount: CARDINAL ← 3;

  Driver: PROC =
    BEGIN
    state: Random.State;
    BEGIN OPEN IODefs;
    WriteString["min: "L];
    minVal ← ReadNumber[minVal, 10];
    WriteChar[CR];
    WriteString["max: "L];
    maxSum ← ReadNumber[maxSum, 10];
    WriteString["pages: "L];
    pageCount ← ReadNumber[pageCount, 10];
    END;
    DigestFonts[];
    InitPressFileDescriptor[pfd, "Math.drill"L];
    answers ← FALSE;
    Random.ReadState[@state];
    THROUGH [0..pageCount) DO DoPage[]; WritePage[pfd]; ENDLOOP;
    ClosePressFile[pfd];
    Random.WriteState[@state];
    history ← ALL[[0,0,0C]];
    InitPressFileDescriptor[pfd, "Math.key"L];
    answers ← TRUE;
    THROUGH [0..pageCount) DO DoPage[]; WritePage[pfd]; ENDLOOP;
    ClosePressFile[pfd];
    END;

  Driver[];
  END.