program FourSquareTip; { JA2008 Computer Column by Cary Davids, March 2008 } { tip1 is even-length tip starting at pos. 1, tip2 is same starting at pos. 2 } { Compile with FreePascal: fpc foursquaretip } { Compile with GNU Pascal: gpc -o foursquaretip FourSquareTip.pas } { Ignore string-length warnings from GNU Pascal compilation } { See accompanying file SampleRunJA2008.txt } type frecord = record { letter frequency } fr1: integer; { frequency of 1st cipher letter in digram } fr2: integer; { frequency of 2nd cipher letter in digram } let: char { cipher letter } end; var str, str1, tippatstr1, tippatstr2, title, tip, n1str, n2str, tip1, tip2, alph, zero: string; i, j, len, nd, lt, len1, len2, lt1, lt2: integer; sqr, ct1, ct2: array[1..5, 1..5] of char; nsqr1, nsqr2: array[1..5,1..5] of real; Infile: text; freq: array[1..26] of frecord; function patstr (cstr: string): string; var i, j, ls: integer; dstr: string[255]; tpat: array[1..50] of integer; begin dstr := ''; { start with blank string } ls := length(cstr); { get length of string } for i := 1 to ls - 1 do for j := i + 1 to ls do if cstr[i] = cstr[j] then { found a duplicate } begin tpat[i] := j - i; { gives integer distance } break; { found one for this i, don't look further } end else tpat[i] := 0; { no duplicate found for this i } for i := 1 to ls - 1 do { ignore last position: it is 0 } if tpat[i] > 9 then dstr := concat(dstr, chr(tpat[i] + 55)) { letter } else dstr := concat(dstr, chr(tpat[i] + 48)); { number } patstr := dstr; { result returned by function } end; { of function patstr } procedure FauxEncipher(str1: string); { We will use plaintext squares as faux cipher squares to encipher the tip. Then we will drag } { the faux enciphered tip through the cipher, looking for matches. We want pattern matches } { between first letters of faux ciphertext digrams and real ciphertext digrams, as well as pattern } { matches between 2nd letters. dig1 is a tip digram, dig2 is the cipher of that tip digram. ctip1 is } { a string of the first letters of the enciphered tip digrams, ctip2 is a string of the 2nd letters } var i, j, k, kmax, r1, r2, c1, c2: integer; dig1, dig2: string[2]; ctip1, ctip2: string; begin dig2 := ' '; ctip1 := ''; ctip2 := ''; kmax := length(str1) div 2; { number of digrams } for k := 1 to kmax do begin dig1 := copy(str1, 2*k - 1, 2); { get tip digram } for i := 1 to 5 do for j := 1 to 5 do begin if dig1[1] = sqr[i, j] then begin r1 := i; { row number of first letter } c1 := j; { column number of first letter } end; if dig1[2] = sqr[i, j] then begin r2 := i; { row number of second letter } c2 := j; { column number of second letter } end; end; { of ij loop } dig2[1] := sqr[r1, c2]; { faux cipher digram, letter 1 } dig2[2] := sqr[r2, c1]; ctip1 := concat(ctip1, dig2[1]); { build first letter string } ctip2 := concat(ctip2, dig2[2]); write(dig2, ' '); end; { of k loop } writeln; tippatstr1 := patstr(ctip1); tippatstr2 := patstr(ctip2); writeln('First digram letters: ', ctip1, ', patstr: ', tippatstr1); writeln('Second digram letters: ', ctip2, ', patstr: ', tippatstr2); end; { of procedure FauxEncipher } Procedure DrawSquares(str1, str2: string); { enters knowing str1 in DragTip = tip1 or tip2, str2 = ciphertext match } var i, j, k, kmax, r1, r2, c1, c2, n: integer; dig1, dig2: string[2]; chisq: real; begin for i := 1 to 5 do for j := 1 to 5 do begin { start with empty squares } ct1[i, j] := '.'; ct2[i, j] := '.'; end; { Now populate ciphertext squares ct1 and ct2 } kmax := length(str1) div 2; { number of digrams } for k := 1 to kmax do begin dig1 := copy(str1, 2*k - 1, 2); { get tip digram } dig2 := copy(str2, 2*k - 1, 2); { get matching ct digram } for i := 1 to 5 do for j := 1 to 5 do begin if dig1[1] = sqr[i, j] then begin r1 := i; { row number of first pt letter } c1 := j; { column number of first pt letter } end; if dig1[2] = sqr[i, j] then begin r2 := i; { row number of second pt letter } c2 := j; { column number of second pt letter } end; end; { of ij loop } ct1[r1,c2] := dig2[1]; { first ct letter, goes in ct1 } ct2[r2,c1] := dig2[2]; { second ct letter, goes in ct2 } end; { of k loop } { Now draw squares } chisq := 0; n :=0; for i := 1 to 5 do begin for j := 1 to 5 do write(sqr[i,j],' '); write(' '); for j := 1 to 5 do begin write(ct1[i,j],' '); if ct1[i,j] <> '.' then for k := 1 to 26 do if ct1[i,j] = freq[k].let then begin chisq := chisq + (freq[k].fr1-nsqr1[i,j])*(freq[k].fr1-nsqr1[i,j])/nsqr1[i,j]; inc(n); end; end; { of j loop } for j := 1 to 5 do write(trunc(nsqr1[i,j]+0.5):2); writeln; end; { of i loop } writeln; for i := 1 to 5 do begin for j := 1 to 5 do begin write(ct2[i,j],' '); if ct2[i,j] <> '.' then for k := 1 to 26 do if ct2[i,j] = freq[k].let then begin chisq := chisq + (freq[k].fr2-(nsqr2[i,j]))*(freq[k].fr2-(nsqr2[i,j]))/nsqr2[i,j]; inc(n); end; end; { of j loop } write(' '); for j := 1 to 5 do write(sqr[i,j],' '); for j := 1 to 5 do write(trunc(nsqr2[i,j]+0.5):2); writeln; end; { if i loop } writeln('Chisq/DF =',chisq/(n-1):6:3,', N = ',n-1); end; { of procedure DrawSquares } Procedure DragTip(str1,str3:string); { str1 is tip1 or tip2, str3 is 'tip1' or 'tip2' } var str2, cstr1, cstr2, chk: string; i, ii, imax, ltip: integer; mflag: boolean; begin ltip := length(str1); imax := ltip div 2; chk := copy(zero,1,imax - 1); { get string of zeros as long as pattern string } mflag := true; { means didn't find match } for ii := 1 to nd - ltip + 1 do begin str2 := copy(str, 2*ii - 1, ltip); { grab piece of ciphertext } cstr1 := ''; cstr2 := ''; for i := 1 to imax do begin cstr1 := concat(cstr1, str2[2*i - 1]); { odd letters } cstr2 := concat(cstr2, str2[2*i]); { even letters } end; if not((tippatstr1=chk) and (tippatstr2=chk)) then { not both non-pattern } if (patstr(cstr1) = tippatstr1) and (patstr(cstr2) = tippatstr2) then begin { found match } writeln('Match found for ',str3,' at digram #', ii : 3, ': ', str2); DrawSquares(str1,str2); mflag := false; { found at least 1 match } end; end; { of ii loop } if mflag then writeln('No match found for ',str3); end; { of procedure DragTip } begin { main program } title := ' FOURSQUARE TIP PLACEMENT'; alph := 'ABCDEFGHIKLMNOPQRSTUVWXYZ'; zero := '0000000000000000'; { use to reject non-pattern words } n1str := '502508747775351251132400480420413402344753456302269635858486080043104168118'; n2str := '362537932540509183255381718146452252366834466433247477789605119062076152104'; { Set up plaintext square } for i := 1 to 5 do for j := 1 to 5 do sqr[i, j] := alph[j + 5 * (i - 1)]; { Set up normal frequency squares taken from digram frequencies n1str and n2str, per 1 digram } for i := 0 to 24 do begin { hundreds tens units } nsqr1[(i div 5)+1,(i mod 5)+1] := 0.01*(ord(n1str[3*i+1])-48 + 0.1*(ord(n1str[3*i+2])-48) + 0.01*(ord(n1str[3*i+3])-48)); nsqr2[(i div 5)+1,(i mod 5)+1] := 0.01*(ord(n2str[3*i+1])-48 + 0.1*(ord(n2str[3*i+2])-48) + 0.01*(ord(n2str[3*i+3])-48)); end; { Now get puzzle } write('ENTER THE FILE NAME ("-1" to quit): '); readln(str1); { read in filename } if str1 = '-1' then halt; assign(Infile, str1); { open Infile with title str1 } reset(Infile); readln(Infile, str); { read puzzle into str } readln(Infile, tip); { read in tip } len := length(str); if len mod 2 > 0 then begin writeln('Length of string not an even number'); halt; end; nd := len div 2; { number of digrams in cipher } lt := length(tip); { length of tip } Writeln(title, ' ', str1); Writeln('Tip= ', tip, ' # of digrams in cipher=', nd : 3); { Create Expected Frequency Counts for ct1 (nsqr1) and ct2 (nsqr2) for this con } for i := 1 to 5 do for j := 1 to 5 do begin nsqr1[i,j] := nd*nsqr1[i,j]; nsqr2[i,j] := nd*nsqr2[i,j]; end; { Initialize frequency records and column array } for i := 1 to 26 do begin freq[i].let := chr(64 + i); { initialize ciphertext alphabet A-Z } freq[i].fr1 := 0; { intialize frequency count } freq[i].fr2 := 0; { intialize frequency count } end; { Get puzzle frequency distribution } for i := 1 to nd do begin j := ord(str[2 * i - 1]) - 64; { get index into first character of digram } freq[j].fr1 := freq[j].fr1 + 1; { increment frequency count } j := ord(str[2 * i]) - 64; { get index into 2nd character of digram } freq[j].fr2 := freq[j].fr2 + 1; { increment frequency count } end; { Display observed frequency counts } write(' '); for i := 1 to 26 do write(' ', freq[i].let); writeln; write('1st letter:'); for i := 1 to 26 do write(freq[i].fr1 : 2); writeln; write('2nd letter:'); for i := 1 to 26 do write(freq[i].fr2 : 2); writeln; { Process Tip } if lt mod 2 > 0 then begin writeln('Length of tip not an even number'); tip1 := copy(tip, 1, lt - 1); { get even-numbered tip length starting with position 1 } tip2 := copy(tip, 2, lt - 1); { get even-numbered tip length starting with position 2 } end else begin tip1 := tip; { get even-numbered tip length starting with position 1 } tip2 := copy(tip, 2, lt - 2); { get even numbered tip - 2 length starting with position 2 } end; lt1 := length(tip1); lt2 := length(tip2); len1 := lt1 div 2; { half-length of tip1 and number of digrams } len2 := lt2 div 2; { half-length of tip2 } write('Tip1= '); for i := 1 to len1 do write(tip1[2 * i - 1], tip1[2 * i], ' '); { write out tip1 as digrams } writeln(' Length=', lt1 : 2); write('Tip2= '); for i := 1 to len2 do write(tip2[2 * i - 1], tip2[2 * i], ' '); { write out tip2 } writeln(' Length=', lt2 : 2); writeln; write('Faux encipherment of Tip1: '); FauxEncipher(Tip1); DragTip(Tip1,'Tip1'); writeln; write('Faux encipherment of Tip2: '); FauxEncipher(Tip2); DragTip(Tip2,'Tip2'); write('Press : '); readln; end.