-- MakeMaze.mesa 
--   Edited by Sweet, February 4, 1981  11:43 PM

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

MakeMaze: PROGRAM
  IMPORTS Inline, IODefs, PressDefs, PressUtilities, Random, Storage, StreamDefs, String =
  BEGIN OPEN PressDefs;

  pfdBody: PressFileDescriptor;
  pfd: POINTER TO PressFileDescriptor = @pfdBody;
  Mica: TYPE = CARDINAL;
  MBox: TYPE = RECORD [x,y,w,h: Mica];
  LineWidth: Mica ← 50;
  AnswerWidth: Mica ← 100;
  fudge: Mica;

  serial: CARDINAL;
  serialNumber: STRING = [10];

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

  DrawLine: PROC [x, y, w, h: Mica] =
    BEGIN
    IF w > h THEN w ← w + h;
    PutRectangle[p: pfd, xstart: x, ystart: y, xlen: w, ylen: h];
    END;

  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;

  answers: BOOLEAN;

  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..128) OF CARDINAL;
  jRandom: ARRAY [0..64) OF CARDINAL;

  RandomizeRows: PROC =
    BEGIN
    r:  ARRAY [0..128) OF CARDINAL;
    rGreater: PROC [x, y: CARDINAL] RETURNS [BOOLEAN] =
      {RETURN [r[x] > r[y]]};
    i: CARDINAL;
    FOR i IN [0..nRows) DO
      r[i] ← Random.InRange[0, 1000];
      iRandom[i] ← i;
      ENDLOOP;
    Sort[DESCRIPTOR[@iRandom, nRows], rGreater ];
    END;
      
  RandomizeColumns: PROC =
    BEGIN
    r:  ARRAY [0..64) OF CARDINAL;
    rGreater: PROC [x, y: CARDINAL] RETURNS [BOOLEAN] =
      {RETURN [r[x] > r[y]]};
    i: CARDINAL;
    FOR i IN [0..nCols) DO
      r[i] ← Random.InRange[0, 1000];
      jRandom[i] ← nCols-1-i;
      ENDLOOP;
--    Sort[DESCRIPTOR[@jRandom, nCols], rGreater ];
    END;
      

  nRows, nCols: CARDINAL ← 20;
  start, stop: CARDINAL;
  Direction: TYPE = {n, e, s, w};
  PathRec: TYPE = RECORD [i, j: [0..256), link: POINTER TO PathRec];
  thisPath: POINTER TO PathRec ← NIL;

  Step: PROC [dir: Direction] =
    BEGIN
    node[i][j].path ← first;
    IF ~node[i][j].this THEN {
      tn: POINTER TO PathRec ← Storage.Node[SIZE[PathRec]];
      tn↑ ← [i: i, j: j, link: thisPath];
      thisPath ← tn;
      node[i][j].this ← TRUE;
      IF i < nRows/2 THEN upperHalf ← upperHalf + 1
      ELSE lowerHalf ← lowerHalf + 1};
    SELECT dir FROM
      n => {
	node[i][j].n ← TRUE;
	node[i-1][j].s ← TRUE;
	i ← i-1};
      e => {
	node[i][j].e ← TRUE;
	IF j # nCols - 1 THEN node[i][j+1].w ← TRUE;
	j ← j+1};
      s => {
	node[i][j].s ← TRUE;
	node[i+1][j].n ← TRUE;
	i ← i+1};
      w => {
	node[i][j].w ← TRUE;
	node[i][j-1].e ← TRUE;
	j ← j-1};
      ENDCASE;
    END;

  SolveStep: PROC =
    BEGIN
    dir: Direction;
    node[i][j].dead ← TRUE;
    SELECT TRUE FROM
      node[i][j].n AND ~node[i-1][j].dead AND node[i-1][j].path => {
	dir ← n; i ← i-1};
      node[i][j].e AND 
          (j = nCols-1 OR (~node[i][j+1].dead AND node[i][j+1].path)) => {
	dir ← e; j ← j+1};
      node[i][j].s AND ~node[i+1][j].dead AND node[i+1][j].path => {
	dir ← s; i ← i+1};
      node[i][j].w AND ~node[i][j-1].dead AND node[i][j-1].path => {
	dir ← w; j ← j-1};
      ENDCASE;
    SolveDraw[dir];
    END;
  

  SolveDraw: PROC [dir: Direction] =
    BEGIN
    x, y: Mica;
    x ← j * boxMicas + leftMargin + fudge;
    y ← (nRows - 1 - i) * boxMicas + bottomMargin + fudge;
    SELECT dir FROM
      n => DrawLine[x: x, y: y- boxMicas, w: AnswerWidth, h: boxMicas];
      s => DrawLine[x: x, y: y, w: AnswerWidth, h: boxMicas];
      e => DrawLine[x: x- boxMicas, y: y, w: boxMicas, h: AnswerWidth];
      w => DrawLine[x: x, y: y, w: boxMicas, h: AnswerWidth];
      ENDCASE;
    END;
    
  Decide: PROC RETURNS [dir: Direction] = 
    BEGIN
    choice: ARRAY [0..12] OF Direction;
    nC: CARDINAL ← 0;
    AddC: PROC [d: Direction] = {choice[nC] ← d; nC ← nC + 1};
    canE, canW, canN, canS, mtE, mtW, mtN, mtS: BOOLEAN ← FALSE;

    IF first AND Right[] THEN AddC[e];

    IF ~Left[] AND
      ~(first AND (Top[] OR Bottom[])) AND
      ~node[i][j-1].this THEN {
	canW ← TRUE;
	IF ~first AND ~node[i][j-1].done THEN mtW ← TRUE};

    IF ~Right[] AND
      ~(~first AND (Top[] OR Bottom[])) AND
      ~node[i][j+1].this THEN {
	canE ← TRUE;
	IF ~first AND ~node[i][j+1].done THEN mtE ← TRUE};

    IF ~Top[] AND
      ~(first AND Left[] AND start < i) AND
      ~(~first AND ((Left[] AND start > i) OR (Right[] AND stop > i))) AND
      ~node[i-1][j].this THEN {
        canN ← TRUE;
	IF ~first AND ~node[i-1][j].done THEN mtN ← TRUE};

    IF ~Bottom[] AND
      ~(first AND Left[] AND start > i) AND
      ~(~first AND ((Left[] AND start < i) OR (Right[] AND stop < i))) AND
      ~node[i+1][j].this THEN {
        canS ← TRUE;
	IF ~first AND ~node[i+1][j].done THEN mtS ← TRUE};

    IF canN THEN AddC[n];
    IF canE THEN AddC[e];
    IF canS THEN AddC[s];
    IF canW THEN AddC[w];
    
    IF first THEN {
      IF canN AND 10*lowerHalf > 12*upperHalf THEN AddC[n];
      IF canS AND 10*upperHalf > 12*lowerHalf THEN AddC[s]}
    ELSE {
      IF mtN THEN AddC[n];
      IF mtS THEN AddC[s];
      IF mtE AND ~mtW THEN AddC[e];
      IF mtW THEN AddC[w]};

    IF nC = 0 THEN {
      node[i][j].dead ← TRUE;
      SELECT TRUE FROM
	node[i][j].w AND ~node[i][j-1].dead => {
	  Step[w]; RETURN [Decide[]]};
	node[i][j].n AND ~node[i-1][j].dead => {
	  Step[n]; RETURN [Decide[]]};
	node[i][j].s AND ~node[i+1][j].dead => {
	  Step[s]; RETURN [Decide[]]};
	node[i][j].e AND ~node[i][j+1].dead => {
	  Step[e]; RETURN [Decide[]]};
        ENDCASE;
      SIGNAL Trapped};

    RETURN [choice[Random.InRange[0,nC-1]]];
    END;
    
  upperHalf, lowerHalf: CARDINAL;

  Walk: PROC =
    BEGIN
    dir: Direction;
    thisPath ← NIL;
    DO
      dir ← Decide[];
      IF first AND dir = e AND Right[] THEN {
	Step[e]; stop ← i; EXIT};
      Step[dir];
      IF node[i][j].done THEN EXIT;
      ENDLOOP;
    WHILE thisPath # NIL DO
      next: POINTER TO PathRec = thisPath.link;
      node[thisPath.i][thisPath.j].done ← TRUE;
      node[thisPath.i][thisPath.j].this ← FALSE;
      Storage.Free[thisPath];
      thisPath ← next;
      ENDLOOP;
    END;

  GenerateMaze: PROC =
    BEGIN
    DO
      GenMaze[ 
        !Trapped => {
	  IODefs.WriteLine[IF first THEN "no path" ELSE "no fill"];
	  LOOP}];
      EXIT;
      ENDLOOP;

    IF answers THEN 
      FOR r: CARDINAL IN [0..nRows) DO
        FOR c: CARDINAL IN [0..nCols) DO
          astream.put[astream, node[r][c]];
          ENDLOOP;
        ENDLOOP;
    END;

  
  ComputeMargins: PROC =
    BEGIN
    rMax, cMax: Mica;

    cMax ← (13*M1)/2*nCols;
    rMax ← regionSize/nRows;
    boxMicas ← MIN [rMax, cMax, M38];
    fudge ← (boxMicas + LineWidth - AnswerWidth)/2;

    bottomMargin ← (regionSize - nRows*boxMicas)/2 + regionStart;
    leftMargin ← ((17*M1)/2 - nCols * boxMicas)/2;
    END;
    
  GenMaze: PROC =
    BEGIN
    ComputeMargins[];

    FOR r: CARDINAL IN [0..nRows) DO
      FOR c: CARDINAL IN [0..nCols) DO
	node[r][c] ← [];
	ENDLOOP;
      ENDLOOP;
    i ← start ← Random.InRange[0,nRows-1];
    j ← 0; upperHalf ← lowerHalf ← 0;
    first ← TRUE;
    node[i][j].w ← TRUE;
    Walk[];
    IODefs.WriteLine["path"];
    RandomizeRows[];
      RandomizeColumns[];
    first ← FALSE;
    FOR r: CARDINAL IN [0..nRows) DO
      FOR c: CARDINAL IN [0..nCols) DO
	i ← iRandom[r]; j ← jRandom[c];
	IF ~node[i][j].done THEN Walk[];
	ENDLOOP;
      ENDLOOP;
    END;

  boxMicas, leftMargin, bottomMargin: Mica;

  WriteMaze: PROC =
    BEGIN
    r, c: CARDINAL;
    runStart, runStop: CARDINAL;
    BoxX: PROC [r, c: CARDINAL] RETURNS [Mica] = INLINE {
      RETURN[c * boxMicas + leftMargin]};
    BoxY: PROC [r, c: CARDINAL] RETURNS [Mica] = INLINE {
      RETURN[(nRows - 1 - r) * boxMicas + bottomMargin]};
        
    IODefs.WriteLine["pressing"];
    serialNumber.length ← 0;
    String.AppendDecimal[serialNumber, serial];
    PutText[pfd, serialNumber, leftMargin, bottomMargin + nRows*boxMicas + P2];
    -- draw outside
    DrawLine[
      x: BoxX[0,0], y: BoxY[0,0] + boxMicas, 
      w: nCols*boxMicas, h: LineWidth];
    DrawLine[
      x: BoxX[nRows-1,0], y: BoxY[nRows-1,0],
      w: nCols*boxMicas, h: LineWidth];
    IF start # 0 THEN DrawLine[
      x: BoxX[start,0], y: BoxY[start,0]+boxMicas,
      w: LineWidth, h:  start*boxMicas];
    IF start # nRows-1 THEN DrawLine[
      x: BoxX[nRows-1,0], y: BoxY[nRows-1,0],
      w: LineWidth, h:  (nRows-1-start)*boxMicas];
    IF stop # 0 THEN DrawLine[
      x: BoxX[stop,nCols-1] + boxMicas, y: BoxY[stop,nCols-1]+boxMicas,
      w: LineWidth, h:  stop*boxMicas];
    IF stop # nRows-1 THEN DrawLine[
      x: BoxX[nRows-1,nCols-1] + boxMicas, y: BoxY[nRows-1,nCols-1],
      w: LineWidth, h:  (nRows-1-stop)*boxMicas];
    -- do horizontals
    FOR r IN [0..nRows-1) DO
      c ← 0;
      WHILE c < nCols DO
	WHILE c < nCols AND node[r][c].s DO c ← c + 1 ENDLOOP;
	runStart ← c;
	WHILE c < nCols AND ~node[r][c].s DO c ← c + 1 ENDLOOP;
	runStop ← c;
	IF runStart = nCols THEN EXIT;
	DrawLine[
	  x: BoxX[r, runStart], y: BoxY[r, runStart],
	  w: (runStop-runStart)*boxMicas, h: LineWidth];
	ENDLOOP;
      ENDLOOP;
    -- do verticals
    FOR c IN (0..nCols) DO
      r ← 0;
      WHILE r < nRows DO
	WHILE r < nRows AND node[r][c].w DO r ← r + 1 ENDLOOP;
	runStart ← r;
	WHILE r < nRows AND ~node[r][c].w DO r ← r + 1 ENDLOOP;
	runStop ← r;
	IF runStart = nRows THEN EXIT;
	DrawLine[
	  x: BoxX[runStop-1, c], y: BoxY[runStop-1, c],
	  w: LineWidth, h: (runStop-runStart)*boxMicas];
	ENDLOOP;
      ENDLOOP;
    serial ← serial + 1;
    END;

  Top: PROC RETURNS [BOOLEAN] = INLINE {RETURN [i = 0]};
  Bottom: PROC RETURNS [BOOLEAN] = INLINE {RETURN [i = nRows-1]};
  Left: PROC RETURNS [BOOLEAN] = INLINE {RETURN [j = 0]};
  Right: PROC RETURNS [BOOLEAN] = INLINE {RETURN [j = nCols - 1]};

  i, j, nPages: CARDINAL;
  first: BOOLEAN;
  Trapped: SIGNAL = CODE;
  
  regionStart, regionSize: Mica;
  twoPer: BOOLEAN;

  Node: TYPE = RECORD [n, e, s, w, this, done, dead, path: BOOLEAN ← FALSE];

  node: POINTER TO ARRAY [0..128) OF PACKED ARRAY [0..64) OF Node;

  Yes: PROC RETURNS [BOOLEAN] =
    BEGIN OPEN IODefs;
    SELECT ReadChar[] FROM
      'y, 'Y, CR => {WriteLine["yes"]; RETURN[TRUE]};
      ENDCASE => {WriteLine["no"]; RETURN[FALSE]};
    END;
    
  Input: TYPE = RECORD [rows, cols, pages: CARDINAL, twoPer: BOOLEAN, link: POINTER TO Input ← NIL];
  
  firstInput, lastInput: POINTER TO Input ← NIL;
  
  Solve: PROC =
    BEGIN
    i ← start; j ← 0;
    SolveDraw[e];
    UNTIL j = nCols DO SolveStep[] ENDLOOP;
    END;

  CharHeight: Mica;
  CharWidth: POINTER TO ARRAY CHARACTER OF Mica;
  HCharWidth: ARRAY CHARACTER OF Mica;
  TRCharWidth: ARRAY CHARACTER OF Mica;

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

  TimesRoman: PROC =
    BEGIN
    SetFont[p: pfd, Name: "TimesRoman", PointSize: 18, Face: 2];
    CharWidth ← @TRCharWidth;
    END;

  DigestFonts: PROC =
    BEGIN
    [] ← PressUtilities.FindFontWidths[
      family: "Helvetica"L,
      points: 18,
      weight: bold,
      slope: regular,
      widths: LOOPHOLE[@HCharWidth]];
    [] ← PressUtilities.FindFontWidths[
      family: "TimesRoman"L,
      points: 18,
      weight: bold,
      slope: regular,
      widths: LOOPHOLE[@TRCharWidth]];
    CharHeight ← PointsToMicas[18];
    END;
    
  astream: StreamDefs.StreamHandle;
  
  Driver: PROC =
    BEGIN OPEN IODefs, StreamDefs;
    firstSerial: CARDINAL;
    state: Random.State;
    newInput: POINTER TO Input;
    sh: StreamHandle;
    node ← Storage.Words[SIZE[Node, 128*64]];
    DigestFonts[];
    InitPressFileDescriptor[pfd, "Maze.press"L];
    Helvetica[];
    WriteString["new randoms? "];
    IF Yes[] THEN {
      Random.ReadState[@state];
      sh ← NewWordStream["Random.state",ReadWriteAppend];
      [] ← WriteBlock[sh, @state, SIZE[Random.State]];
      sh.destroy[sh]};
    sh ← NewWordStream["Random.state", Read];
    [] ← ReadBlock[sh, @state, SIZE[Random.State]];
    sh.destroy[sh];
    Random.WriteState[@state];
    WriteString["answers? "];
    answers ← Yes[];
    IF answers THEN astream ← NewByteStream["Maze.temp$", ReadWriteAppend];
    WriteString["first serial number: "L];
    serial ← 1;
    serial ← ReadNumber[nRows , 10! String.InvalidNumber => CONTINUE];
    firstSerial ← serial;
    WriteChar[CR];

    DO
      DO
        WriteString["rows: "L];
        nRows ← ReadNumber[nRows , 10! String.InvalidNumber => GO TO done];
        IF nRows > 128 THEN {WriteLine[" at most 128"]; LOOP};
        EXIT;
        ENDLOOP;
      WriteChar[CR];
      IF nRows = 0 THEN EXIT;
      DO
        WriteString["cols: "L];
        nCols ← ReadNumber[nCols, 10];
        IF nCols > 64 THEN {WriteLine[" at most 64"]; LOOP};
        EXIT;
        ENDLOOP;
      WriteChar[CR];
      WriteString["pages: "L];
      nPages ← ReadNumber[nPages , 10! String.InvalidNumber =>
        {nPages ← 1; CONTINUE}];
      WriteChar[CR];
      WriteString["two per page? "];
      twoPer ← Yes[];
      IF twoPer THEN regionSize ← 4*M1+M12 ELSE regionSize ← 9*M1;
      newInput ← Storage.Node[SIZE[Input]];
      newInput↑ ← 
        [rows: nRows, cols: nCols, pages: nPages, twoPer: twoPer];
      IF lastInput = NIL THEN firstInput ← newInput
      ELSE lastInput.link ← newInput;
      lastInput ← newInput;
        THROUGH [0..nPages) DO
          regionStart ← IF twoPer THEN 5*M1 + M34 ELSE M1;
	  GenerateMaze[];
	  WriteMaze[];
	  IF twoPer THEN {
	    regionStart ← M34;
	    GenerateMaze[];
	    WriteMaze[]};
	  WritePage[pfd];
          ENDLOOP;
      REPEAT
        done => NULL;
      ENDLOOP;
    ClosePressFile[pfd];
    IF answers THEN
      BEGIN
      next: POINTER TO Input;
      serial ← firstSerial;
      astream.reset[astream];
      WriteChar[CR]; WriteLine["Pressing answers"];
      Random.WriteState[@state];
      InitPressFileDescriptor[pfd, "Maze-solutions.press"L];
      Helvetica[];
      FOR in: POINTER TO Input ← firstInput, next UNTIL in = NIL DO
        next ← in.link;
        nRows ← in.rows; nCols ← in.cols; nPages ← in.pages;
        twoPer ← in.twoPer;
        THROUGH [0..nPages) DO
          regionStart ← IF twoPer THEN 5*M1 + M34 ELSE M1;
	  RereadMaze[];
	  Solve[];
	  WriteMaze[];
	  IF twoPer THEN {
	    regionStart ← M34;
	    RereadMaze[];
	    Solve[];
	    WriteMaze[]};
	  WritePage[pfd];
          ENDLOOP;
        Storage.Free[in];
	ENDLOOP;
      ClosePressFile[pfd];
      astream.destroy[astream];
      END;
    Storage.FreeWords[node];
    END;
    
  RereadMaze: PROC =
    BEGIN
    FOR r: CARDINAL IN [0..nRows) DO
      FOR c: CARDINAL IN [0..nCols) DO
        node[r][c] ← astream.get[astream];
        ENDLOOP;
      ENDLOOP;
    start ← 0;
    UNTIL node[start][0].w DO start ← start + 1 ENDLOOP;
    stop ← 0;
    UNTIL node[stop][nCols-1].e DO stop ← stop + 1 ENDLOOP;
    ComputeMargins[];
    END;

  Driver[];
  END.