-- MakePuzzle.mesa -- Edited by Sweet, 21-Sep-82 9:59:19 DIRECTORY Ascii, Inline, IODefs, PressDefs, PressUtilities, Random, Segments, Storage, Streams, String; MakePuzzle: PROGRAM IMPORTS Inline, IODefs, PressDefs, PressUtilities, Random, Segments, Storage, Streams, String = BEGIN OPEN PressDefs; pfdBody: PressFileDescriptor; pfd: POINTER TO PressFileDescriptor = @pfdBody; Mica: TYPE = CARDINAL; MBox: TYPE = RECORD [x,y,w,h: Mica]; P3: Mica = PointsToMicas[3]; M1: Mica = MicasPerInch; M12: Mica = MicasPerInch/2; M14: Mica = MicasPerInch/4; M34: Mica = (3*MicasPerInch)/4; M38: Mica = (3*MicasPerInch)/8; bw: Mica; PointsToMicas: PROC [points: CARDINAL] RETURNS [Mica] = {RETURN [Inline.LongDiv[Inline.LongMult[points, MicasPerInch],72]]}; CenterChar: PROC [c: CHARACTER, box: POINTER TO MBox] = BEGIN w: Mica; ns: STRING ← [2]; ns.length ← 1; ns[0] ← c; w ← CharWidth[c]; PutText[pfd, ns, box.x + (box.w-w)/2, box.y + P3 + (box.h-CharHeight)/2]; END; 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; jRandom: ARRAY [0..32) OF CARDINAL; RandomizeRows: PROC = 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..nRows) DO r[i] ← Random.InRange[0, 1000]; iRandom[i] ← i; ENDLOOP; Sort[DESCRIPTOR[@iRandom, nRows], rGreater ]; END; RandomizeColumns: PROC = 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..nCols) DO r[i] ← Random.InRange[0, 1000]; jRandom[i] ← nCols-1-i; ENDLOOP; Sort[DESCRIPTOR[@jRandom, nCols], rGreater ]; END; nRows, nCols: CARDINAL ← 20; textX, textY: Mica; ComputeMargins: PROC = BEGIN rMax, cMax: Mica; cMax ← (13*M1)/2*nCols; rMax ← (9*M1)/nRows; bw ← MIN [rMax, cMax, M38]; bottomMargin ← 10*M1 - nRows*bw; leftMargin ← ((17*M1)/2 - nCols * bw)/2; END; Alpha: TYPE = CHARACTER ['a..'z]; -- from Gaines, Cryptanalysis CharWeight: ARRAY Alpha OF CARDINAL = [ -- a, b, c, d, e, f, g, h, i, j, k, l, m, 805, 162, 320, 365,1231, 228, 161, 514, 718, 10, 52, 403, 225, -- n, o, p, q, r, s, t, u, v, w, x, y, z 719, 794, 229, 20, 603, 659, 959, 310, 93, 203, 20, 188, 9]; totalWeight: CARDINAL; RandomChar: PROC RETURNS [ch: Alpha] = BEGIN choice: CARDINAL ← Random.InRange[1, totalWeight]; FOR ch IN Alpha DO IF choice < CharWeight[ch] THEN { ch ← ch + ('A - 'a); RETURN}; choice ← choice - CharWeight[ch]; ENDLOOP; END; Mode: TYPE = {horiz, vert, slopedown, slopeup, revhoriz, revvert, revslopedown, revslopeup}; modeWeight: ARRAY Mode OF CARDINAL ← [30, 20, 7, 7, 0, 0, 0, 0]; totalmodeWeight: CARDINAL; RandomMode: PROC RETURNS [m: Mode] = BEGIN choice: CARDINAL ← Random.InRange[0, totalmodeWeight-1]; FOR m IN Mode DO IF choice < modeWeight[m] THEN RETURN; choice ← choice - modeWeight[m]; ENDLOOP; END; Match: PROC [s: STRING, r, c: CARDINAL, mode: Mode] RETURNS [BOOLEAN] = BEGIN j: CARDINAL; ch: CHARACTER; SELECT mode FROM horiz => { IF c + s.length >= nCols THEN RETURN[FALSE]; FOR j IN [0..s.length) DO IF (ch ← node[r][c+j]) # 0C AND ch # s[j] THEN RETURN[FALSE]; ENDLOOP; FOR j IN [0..s.length) DO node[r][c+j] ← s[j]; ENDLOOP; RETURN[TRUE]}; vert => { IF r + s.length >= nRows THEN RETURN[FALSE]; FOR j IN [0..s.length) DO IF (ch ← node[r+j][c]) # 0C AND ch # s[j] THEN RETURN[FALSE]; ENDLOOP; FOR j IN [0..s.length) DO node[r+j][c] ← s[j]; ENDLOOP; RETURN[TRUE]}; slopedown => { IF r + s.length >= nRows OR c + s.length >= nCols THEN RETURN[FALSE]; FOR j IN [0..s.length) DO IF (ch ← node[r+j][c+j]) # 0C AND ch # s[j] THEN RETURN[FALSE]; ENDLOOP; FOR j IN [0..s.length) DO node[r+j][c+j] ← s[j]; ENDLOOP; RETURN[TRUE]}; slopeup => { IF r < s.length-1 OR c + s.length >= nCols THEN RETURN[FALSE]; FOR j IN [0..s.length) DO IF (ch ← node[r-j][c+j]) # 0C AND ch # s[j] THEN RETURN[FALSE]; ENDLOOP; FOR j IN [0..s.length) DO node[r-j][c+j] ← s[j]; ENDLOOP; RETURN[TRUE]}; revhoriz => { IF c < s.length-1 THEN RETURN[FALSE]; FOR j IN [0..s.length) DO IF (ch ← node[r][c-j]) # 0C AND ch # s[j] THEN RETURN[FALSE]; ENDLOOP; FOR j IN [0..s.length) DO node[r][c-j] ← s[j]; ENDLOOP; RETURN[TRUE]}; revvert => { IF r < s.length-1 THEN RETURN[FALSE]; FOR j IN [0..s.length) DO IF (ch ← node[r-j][c]) # 0C AND ch # s[j] THEN RETURN[FALSE]; ENDLOOP; FOR j IN [0..s.length) DO node[r-j][c] ← s[j]; ENDLOOP; RETURN[TRUE]}; revslopedown => { IF r < s.length-1 OR c < s.length-1 THEN RETURN[FALSE]; FOR j IN [0..s.length) DO IF (ch ← node[r-j][c-j]) # 0C AND ch # s[j] THEN RETURN[FALSE]; ENDLOOP; FOR j IN [0..s.length) DO node[r-j][c-j] ← s[j]; ENDLOOP; RETURN[TRUE]}; revslopeup => { IF r + s.length-1 >= nRows OR c < s.length-1 THEN RETURN[FALSE]; FOR j IN [0..s.length) DO IF (ch ← node[r+j][c-j]) # 0C AND ch # s[j] THEN RETURN[FALSE]; ENDLOOP; FOR j IN [0..s.length) DO node[r+j][c-j] ← s[j]; ENDLOOP; RETURN[TRUE]}; ENDCASE => ERROR; END; AddWord: PROC [s: STRING] = BEGIN tried: ARRAY Mode OF BOOLEAN ← ALL[FALSE]; key: STRING ← [40]; i, j: CARDINAL; mode: Mode ← RandomMode[]; AddInMode: PROC RETURNS [BOOLEAN] = BEGIN FOR r: CARDINAL IN [0..nRows) DO i ← iRandom[r]; FOR c: CARDINAL IN [0..nCols) DO j ← jRandom[c]; IF Match[key, i, j, mode] THEN RETURN[TRUE]; ENDLOOP; ENDLOOP; RETURN[FALSE]; END; RandomizeRows[]; RandomizeColumns[]; FOR i IN [0..s.length) DO key[i] ← (IF s[i] IN ['a..'z] THEN s[i] + ('A - 'a) ELSE s[i]); ENDLOOP; key.length ← s.length; IF AddInMode[] THEN RETURN; mode ← vert; IF modeWeight[mode] # 0 AND AddInMode[] THEN RETURN; mode ← horiz; IF modeWeight[mode] # 0 AND AddInMode[] THEN RETURN; FOR mode IN [slopedown..revslopeup] DO IF modeWeight[mode] # 0 AND AddInMode[] THEN RETURN; ENDLOOP; SIGNAL CantDoIt; END; CantDoIt: SIGNAL = CODE; leftMargin, bottomMargin: Mica; node: POINTER TO ARRAY [0..32) OF PACKED ARRAY [0..32) OF CHARACTER; badData: SIGNAL = CODE; ReadDataFile: PROC [in: Streams.Handle] = BEGIN dataEnd: BOOLEAN ← FALSE; name: STRING = [40]; number: STRING = [20]; ch: CHARACTER; GetToken: PROC [token: STRING, spaceOK: BOOLEAN ← FALSE] = BEGIN ENABLE Streams.End[] => {dataEnd ← TRUE; GO TO done}; token.length ← 0; IF dataEnd THEN RETURN; WHILE ch = Ascii.CR OR (~spaceOK AND ch = Ascii.SP) DO ch ← Streams.GetChar[in]; ENDLOOP; IF ch = '; THEN {dataEnd ← TRUE; RETURN}; WHILE (ch IN ['A..'Z]) OR (ch IN ['a..'z]) OR (ch IN ['0..'9]) OR (spaceOK AND ch = Ascii.SP) DO String.AppendChar[token, ch]; ch ← Streams.GetChar[in]; ENDLOOP; IF token.length = 0 AND ~dataEnd THEN SIGNAL badData; IF ch = '; THEN dataEnd ← TRUE; EXITS done => RETURN; END; BEGIN ENABLE { badData => GO TO badFormat; String.StringBoundsFault => GO TO tooLong; String.InvalidNumber => GO TO badNumber}; nRows ← nCols ← 0; nS ← 0; ch ← Streams.GetChar[in ! Streams.End[] => GO TO done]; GetToken[number]; nRows ← String.StringToDecimal[number]; IF nRows > 32 THEN GO TO tooManyRows; GetToken[number]; nCols ← String.StringToDecimal[number]; IF nCols > 32 THEN GO TO tooManyRows; DO GetToken[name, TRUE]; IF name.length = 0 THEN EXIT; IF nS = 40 THEN GO TO tooManyStrings; input[nS] ← Storage.CopyString[name]; sortedInput[nS] ← NoBlanks[name]; nS ← nS + 1; ENDLOOP; EXITS tooLong => { IODefs.WriteString["token too long at "L]; WriteLongNumber[Streams.GetIndex[in]]}; tooManyRows => { IODefs.WriteString["max of 32 rows or columns"L]}; tooManyStrings => { IODefs.WriteString["max of 40 words"L]}; badNumber => { IODefs.WriteString["invalid number at "L]; WriteLongNumber[Streams.GetIndex[in]]}; badFormat => { IODefs.WriteString["invalid format at "L]; WriteLongNumber[Streams.GetIndex[in]]}; done => NULL; END; END; input, sortedInput: ARRAY [0..40] OF STRING; nS: CARDINAL ← 0; NoBlanks: PROC [s: STRING] RETURNS [ns: STRING] = BEGIN ns ← Storage.String[s.length]; FOR i: CARDINAL IN [0..s.length) DO IF s[i] # Ascii.SP THEN String.AppendChar[ns, s[i]]; ENDLOOP; END; FreeData: PROC = { FOR i: CARDINAL IN [0..nS) DO Storage.Free[input[i]]; Storage.Free[sortedInput[i]] ENDLOOP; nS ← 0}; GenPuzzle: PROC = BEGIN ComputeMargins[]; node↑ ← ALL[ALL[0C]]; FOR i: CARDINAL IN [0..nS) DO AddWord[sortedInput[i]]; ENDLOOP; END; GeneratePuzzle: PROC = BEGIN GenPuzzle[ ! CantDoIt => {IODefs.WriteChar['?]; RETRY}]; IODefs.WriteChar['!]; END; PrintWords: PROC = BEGIN Helvetica18[]; textX ← M1; textY ← bottomMargin - M1; FOR i: CARDINAL IN [0..nS) DO PutText[pfd, input[i], textX, textY]; textY ← textY - CharHeight - P3; IF textY < M1 THEN {textX ← textX + M1 + 3*M1/4; textY ← bottomMargin - M1}; ENDLOOP; END; WriteLongNumber: PROC [ln: LONG CARDINAL] = BEGIN ls: STRING = [20]; String.AppendLongNumber[ls, ln, 10]; IODefs.WriteString[ls]; END; CharHeight: Mica; CharWidth: POINTER TO ARRAY CHARACTER OF Mica; HCharWidth: ARRAY CHARACTER OF Mica; H14CharWidth: ARRAY CHARACTER OF Mica; Helvetica18: PROC = BEGIN SetFont[p: pfd, Name: "Helvetica", PointSize: 18, Face: 2]; CharWidth ← @HCharWidth; CharHeight ← PointsToMicas[18]; END; Helvetica14: PROC = BEGIN SetFont[p: pfd, Name: "Helvetica", PointSize: 14, Face: 2]; CharWidth ← @HCharWidth; CharHeight ← PointsToMicas[14]; END; DigestFonts: PROC = BEGIN [] ← PressUtilities.FindFontWidths[ family: "Helvetica"L, points: 18, weight: bold, slope: regular, widths: LOOPHOLE[@HCharWidth]]; [] ← PressUtilities.FindFontWidths[ family: "Helvetica"L, points: 14, weight: bold, slope: regular, widths: LOOPHOLE[@H14CharWidth]]; CharHeight ← PointsToMicas[18]; END; WriteMatrix: PROC = BEGIN box: MBox ← [ x: leftMargin, y: bottomMargin + (nRows-1)*bw, w: bw, h: bw]; Helvetica18[]; FOR r: CARDINAL IN [0..nRows) DO box.x ← leftMargin; FOR c: CARDINAL IN [0..nCols) DO ch: CHARACTER = node[r][c]; CenterChar[(IF ch = 0C THEN RandomChar[] ELSE ch), @box]; box.x ← box.x + bw; ENDLOOP; box.y ← box.y - bw; ENDLOOP; END; nP: CARDINAL; OneOrMore: PROC [inputFile: STRING] = BEGIN OPEN IODefs; Shorter: PROC [s1, s2: STRING] RETURNS [BOOLEAN] = { RETURN[s1.length < s2.length]}; Alpha: PROC [s1, s2: STRING] RETURNS [BOOLEAN] = { RETURN[String.CompareStrings[s1, s2] > 0]}; in: Streams.Handle ← NIL; node↑ ← ALL[ALL[0C]]; in ← Streams.NewStream [inputFile, Streams.Read ! Segments.FileNameProblem[] => GO TO notFound]; DO ReadDataFile[in]; IF nRows = 0 THEN EXIT; Sort[DESCRIPTOR[BASE[sortedInput], nS], Shorter]; THROUGH [0..nP) DO GeneratePuzzle[]; Sort[DESCRIPTOR[BASE[input], nS], Alpha]; PrintWords[]; WriteMatrix[]; WritePage[pfd]; ENDLOOP; FreeData[]; ENDLOOP; IF in # NIL THEN Streams.Destroy[in]; EXITS notFound => { IODefs.WriteString[inputFile]; IODefs.WriteLine[" not found."L]}; END; Yes: PROC RETURNS [BOOLEAN] = BEGIN OPEN IODefs; SELECT ReadChar[] FROM 'y, 'Y, CR => {WriteLine["yes"]; RETURN[TRUE]}; ENDCASE => {WriteLine["no"]; RETURN[FALSE]}; END; Driver: PROC = BEGIN OPEN IODefs; inputFile: STRING ← [40]; ws: Streams.Handle; modeName: ARRAY Mode OF STRING ← [ "left to right"L, "top to bottom"L, "upper left to lower right"L, "lower left to upper right"L, "right to left"L, "bottom to top"L, "lower right to upper left"L, "upper right to lower left"L]; node ← Storage.Words[32*32]; WriteString["New weights? "]; IF Yes[] THEN BEGIN ws ← Streams.NewStream["puzzle.weights"L, Streams.Write]; FOR m: Mode IN Mode DO modeWeight[m] ← 0; WriteString[modeName[m]]; WriteString[": "L]; modeWeight[m] ← ReadDecimal[ ! String.InvalidNumber => CONTINUE]; Streams.PutWord[ws, modeWeight[m]]; WriteChar[CR]; ENDLOOP; Streams.Destroy[ws]; END ELSE BEGIN ws ← Streams.NewStream["puzzle.weights"L, Streams.Read ! Segments.FileNameProblem[] => GO TO noWeights]; FOR m: Mode IN Mode DO modeWeight[m] ← Streams.GetWord[ws ! Streams.End[] => GO TO badWeights]; ENDLOOP; Streams.Destroy[ws]; END; Init[]; DigestFonts[]; InitPressFileDescriptor[pfd, "Puzzle.press"L]; DO WriteString["Input data: "L]; inputFile.length ← 0; ReadID[inputFile]; WriteChar[CR]; IF inputFile.length = 0 THEN EXIT; WriteString["pages: "L]; nP ← ReadDecimal[]; WriteChar[CR]; OneOrMore[inputFile]; ENDLOOP; ClosePressFile[pfd]; Storage.FreeWords[node]; EXITS noWeights => WriteLine["No weights available"L]; badWeights => WriteLine["Insufficient weights"L]; END; Init: PROC = BEGIN totalWeight ← 0; FOR a: Alpha IN Alpha DO totalWeight ← totalWeight + CharWeight[a]; ENDLOOP; totalmodeWeight ← 0; FOR m: Mode IN Mode DO totalmodeWeight ← totalmodeWeight + modeWeight[m]; ENDLOOP; END; Driver[]; END.