-- MathBox.mesa 
--   Edited by Sweet, 15-May-81 16:52:37

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

MathBox: 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;
  TextCharWidth: ARRAY CHARACTER OF Mica;
  AnswerCharWidth: ARRAY CHARACTER OF Mica;
  TextCharHeight: Mica;
  AnswerCharHeight: Mica;
  Minus: CHARACTER = Ascii.ControlX;

  squareDim: Mica = M12;

  answers: BOOLEAN ← FALSE;

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

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

  p1: CARDINAL ← 2;
  p2: CARDINAL ← 3;

  Problem: PROC RETURNS [op: CHARACTER, a,b, c, d: CARDINAL] =
    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.op, trial.a, trial.b, trial.c, trial.d];
    END;
  

  TrialProblem: PROC RETURNS [trial: Prob] =
    BEGIN
    IF Random.InRange[1, p2] <= p1 THEN RETURN[PlusTrialProblem[]]
    ELSE RETURN[MinusTrialProblem[]];
    END;

  PlusTrialProblem: PROC RETURNS [trial: Prob] =
    BEGIN
    total, sum2: CARDINAL;
    total ← Random.InRange[4, max];
    sum2 ← Random.InRange[2, total-2];
    trial.op ← '+;
    [trial.a, trial.b] ← SubProblem[total-sum2];
    [trial.c, trial.d] ← SubProblem[sum2];
    END;

  MinusTrialProblem: PROC RETURNS [trial: Prob] =
    BEGIN
    a, b, c, d: INTEGER;
    a ← Random.InRange[4,max];
    b ← IF Random.InRange[0, 2*max] = 0 THEN 0 ELSE Random.InRange[1, a-1];
    c ← IF Random.InRange[0, 2*max] = 0 THEN 0 ELSE Random.InRange[1, a-1];
    d ← Random.InRange[MAX[0, b+c-a], MIN[b, c]];
    RETURN[[Minus, a, b, c, d]];
    END;

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

  SubProblem: PROC [total: CARDINAL] RETURNS [a, b: CARDINAL] =
    BEGIN
    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};
    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;

  DrawBorder: PROC [box: POINTER TO MBox, lw: Mica] =
    BEGIN
    PutRectangle[p: pfd,
      xstart: box.x, ystart: box.y, xlen: box.w + lw, ylen: lw];
    PutRectangle[p: pfd,
      xstart: box.x, ystart: box.y, xlen: lw, ylen: box.h];
    PutRectangle[p: pfd,
      xstart: box.x + box.w, ystart: box.y, xlen: lw, ylen: box.h];
    PutRectangle[p: pfd,
      xstart: box.x, ystart: box.y + box.h,
      xlen: box.w + lw, ylen: lw];
    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 + P2];
    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 + P2];
    END;

  DoMatrix: PROC [x,y: Mica] =
    BEGIN
    box: MBox;
    op: STRING ← [1];
    a, b, c, d: CARDINAL;
    op.length ← 1;

    [op[0], a, b, c, d] ← Problem[];
    TextFont[];
    PutText[pfd, op, x-CharWidth['+], y];
    box ← [x: x, y: y-squareDim, w: squareDim, h: squareDim];
    DrawBorder[@box, LineWidth];
    CenterNum[a, @box];
    box.x ← box.x + squareDim; 
    DrawBorder[@box, LineWidth];
    CenterNum[b, @box];
    box.x ← box.x + squareDim; 
    DrawBorder[@box, LineWidth];
    IF answers THEN
      BEGIN
      AnswerFont[];
      CenterNum[(IF op[0] = '+ THEN a+b ELSE a-b), @box];
      TextFont[];
      END;
    box.x ← x; box.y ← box.y - squareDim;
    DrawBorder[@box, LineWidth];
    CenterNum[c, @box];
    box.x ← box.x + squareDim; 
    DrawBorder[@box, LineWidth];
    CenterNum[d, @box];
    box.x ← box.x + squareDim; 
    DrawBorder[@box, LineWidth];
    IF answers THEN
      BEGIN
      AnswerFont[];
      CenterNum[(IF op[0] = '+ THEN c+d ELSE c-d), @box];
      TextFont[];
      END;
    box.x ← x; box.y ← box.y - squareDim;
    DrawBorder[@box, LineWidth];
    IF answers THEN
      BEGIN
      AnswerFont[];
      CenterNum[(IF op[0] = '+ THEN a+c ELSE a-c), @box];
      TextFont[];
      END;
    box.x ← box.x + squareDim; 
    DrawBorder[@box, LineWidth];
    IF answers THEN
      BEGIN
      AnswerFont[];
      CenterNum[(IF op[0] = '+ THEN b+d ELSE b-d), @box];
      TextFont[];
      END;
    box.x ← box.x + squareDim; 
    DrawBorder[@box, 2*LineWidth];
    IF answers THEN
      BEGIN
      AnswerFont[];
      CenterNum[(IF op[0] = '+ THEN a+b+c+d ELSE a-b-c+d), @box];
      TextFont[];
      END;
    END;

  squaresAcross: CARDINAL = 3;
  squaresDown: CARDINAL = 4;

  DoPage: PROC =
    BEGIN
    x, y: Mica;
    boxDim: Mica = 3*squareDim;
    extraX: Mica = (6*M1+M12 - squaresAcross*boxDim)/(squaresAcross-1);
    extraY: Mica = (9*M1 - squaresDown*boxDim)/(squaresDown-1);
    y ← 10*M1;
    THROUGH [0..squaresDown) DO
      x ← M1;
      THROUGH [0..squaresAcross) DO
        DoMatrix[x, y];
        x ← x + boxDim + extraX;
        ENDLOOP;
      y ← y - boxDim - extraY;
      ENDLOOP;
    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;

  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];
    END;

  max: INTEGER ← 12;
  pages: CARDINAL ← 3;

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

  Driver[];
  END.