ソースコード 虫食い算パズルの解法
Turbo Pascal バージョン
by A. Udagawa
{ $r+} program mushi; uses dos, crt; {******************************************************************* OS-9 虫食い算パズルの解法 <かけ算> ------------------------------------------------------------- by A. Udagawa OS9/Basic09 ver 2.0 1988.7 OS9 -> MS.DOS/Quick Basic 1989.12 Quick Basic -> Turbo Pascal 1990.2 coded H.Takahashi ver 2.2 1992.12 long Integer(9桁まで) version *******************************************************************} const MAXMOJISU = 9; { 最大文字列数(被乗数ならびに乗数)} XHABA = MAXMOJISU * 2; { 格納用配列の横幅 } YHABA = 3 + MAXMOJISU; { の縦幅 } XA = XHABA - 1; { 配列用横サイズ = 17 } YA = YHABA - 1; { 縦サイズ = 11 } type _gyou = record { 1行の各種情報 } keta: integer; { 桁数 } head: integer; { 有効文字列の左端の位置 } tail: integer; { の右端の位置 } relcnt: integer; { リレーションが設定されている文字の数 } dfp: integer; { 最初に現れるリレーション文字位置 } vmin: longint; { この行の有効文字列がとりうる最小数値 } vmax: longint { 最大数値 } end; _inta10 = array[0..9] of boolean; _intaxa = array[0..XA] of integer; var s: array[0..YA, 0..XA] of string[2]; { キーボード入力された文字を正序化して格納 } v: array[0..YA, 0..XA] of integer; { 対応する文字の値を格納する配列 } relationtb: array[0..YA, 0..XA, 0..1] of integer; { 文字相互間の親子関係を(x,y)表示 } gyotbl: array[0..YA] of _gyou; { 各行の情報を格納 } ytail: integer; zsum: integer; zerocounter:array[0..XA] of integer; { 乗数行に含まれる初期値ゼロを記録 } lvlid: integer; { 入力時に指定する経過表示レベル } hour, min, sec, sec100: word; adtabs: _intaxa; usednumtb: _inta10; { 数字が使用済か否かを登録 } kanjsw: boolean; { 全角入力スイッチ } procedure chrchk( valu, zeroid:integer; hijyosu: longint; x: integer; var result: integer; var linked: boolean; var adtabs: _intaxa; var usednumtb: _inta10); { ======================================================= 数字の妥当性をチェック ------------------------------------------------------- hijyosu : 被乗数の値 valu : チェックする数値(この数値が妥当かどうか?) x : チェックする乗数行の文字位置 adtabs: 合計行の桁上がり数値保存テーブル usednumtb: result : このルーチンが返す値 0=チェクok linked : zeroid : ======================================================= } label 10, 20, 90, 99; var cyukan: array[0..XA] of integer; temp: longint; ws: string; py, i, j: integer; dx, dy: integer; sum, amari: integer; ch: char; st: string[1]; begin result := 99; { 初期値はエラー状態にしておく } py := XA - x + 2 - zerocounter[x]; { 中間行の y 位置を割り出す } if zeroid > 0 then goto 10; { 乗数桁 X がゼロでないので縦計チェックのみ } temp := hijyosu * valu; { 中間行の値 } { 1. 桁数チェック } if (temp < gyotbl[py].vmin) or (temp > gyotbl[py].vmax) then begin goto 99 { 桁エラー } end; str(temp, ws); { 中間行 temp の数値をセット } j:= 1; if gyotbl[py].relcnt = 0 then { この行に,親へのリンクの設定がない場合は } for i := gyotbl[py].head to gyotbl[py].tail do begin st := copy(ws, j, 1); v[py, i] := ord(st[1]) - 48; j := j + 1 end else begin { そうでなく} { 2. 中間行に定義文字がある場合の処理 } for i := gyotbl[py].head to gyotbl[py].tail do begin { 中間行の数値を cyukan にセット } st := copy(ws, j, 1); cyukan[i] := ord(st[1]) - 48; { 中間行の数値を cyukan にセット } j := j + 1 end; for i := gyotbl[py].dfp to gyotbl[py].tail do begin if relationtb[py, i, 0] < 90 then begin { 親が設定されているなら } dy := relationtb[py, i, 0]; { 親の座標を取り出し } dx := relationtb[py, i, 1]; if (dy = py) and (dx <> i) then begin { 同じ行の他の文字へリンク } if cyukan[dx] <> cyukan[i] then begin goto 99 { エラー } end end else if v[dy, dx] <> cyukan[i] then begin goto 99 end end else begin if relationtb[py, i, 0] = 90 then begin { 覆面文字のルートなら } if usednumtb[cyukan[i]] then begin goto 99 end; usednumtb[cyukan[i]] := TRUE end end; if gyotbl[py].relcnt = 1 then goto 20 end; {for} 20: for i := gyotbl[py].head to gyotbl[py].tail do begin v[py, i] := cyukan[i] end end; 10: sum := adtabs[x]; if py >= 2 then for i := 2 to py do begin sum := sum + v[i, x] end; amari := sum mod 10; adtabs[x - 1] := sum div 10; { 桁上がり数をセットする } { 3. 合計行の x 位置には数字の定義があるか? } if relationtb[ytail, x, 0] < 90 then begin dy := relationtb[ytail, x, 0]; dx := relationtb[ytail, x, 1]; if amari <> v[dy, dx] then begin goto 99 { 桁の合計が定義値と不一致 } end end else if relationtb[ytail, x, 0] = 90 then begin { 覆面文字ルート } if usednumtb[amari] then begin goto 99 end; usednumtb[amari] := TRUE end; v[ytail, x] := amari; if zeroid > 0 then goto 90 ; { 4. 最終行の場合は合計値の妥当性をチェック } if py = ytail - 1 then begin for i := x - 1 downto gyotbl[ytail].head do begin { 左半分のみチェック } sum := adtabs[i]; for j := 2 to ytail - 1 do sum := sum + v[j, i]; { 桁合計 } if i > 0 then adtabs[i - 1] := sum div 10; { 桁上がり数値をセット } amari := sum mod 10; if relationtb[ytail, i, 0] < 90 then begin dy := relationtb[ytail, i, 0]; dx := relationtb[ytail, i, 1]; if v[dy, dx] <> amari then goto 99 end else if relationtb[ytail, i, 0] = 90 then begin { 覆面文字ルート } if usednumtb[amari] then goto 99; usednumtb[amari] := TRUE end; v[ytail, i] := amari end; {for i} if v[ytail, gyotbl[ytail].head] = 0 then goto 99 end; 90: result := 0; 99: end; procedure fdisply (id: integer); { ================================================= 表 示 ル ー チ ン id = 0 処理結果出力 id <>0 入力式出力 ================================================ } var i,j,k: integer; begin if id = 0 then begin gettime(hour, min, sec, sec100); writeln('>> 解発見 ', hour:2, ':', min:2, ':',sec:2) end; for i := 0 to ytail do begin for k := 0 to XA do if (k < gyotbl[i].head) or (k > gyotbl[i].tail) then write(' ') else if id = 0 then write(v[i, k]:4) else write(' ', s[i, k]:2); writeln; if (i = 1) or (i = ytail - 1) then begin { 線を引く } for j := 1 to gyotbl[ytail].head do write(' '); for j := gyotbl[ytail].head to XA do write('----'); writeln end end {for i} end; procedure initset ( var adtabs: _intaxa; var usednumtb: _inta10); { ================================================== 計算前に各種テーブルをセット ================================================== } label 5, 10, 11; var indata: string; v1: longint; v2: longint; i,j,k, l,m: integer; x,y: integer; zerocnt, zerop: integer; code, digit: integer; inchar: char; st, s2: string[2]; procedure init_sub( k, m: integer ); var x, y: integer; begin if (relationtb[k, m, 0] = 99) and ((s[k, m] <> '* ') and (s[k, m] <> '*')) then begin { *以外は覆面文字とする } relationtb[k, m, 0] := 90; relationtb[k, m, 1] := 90; { 同一覆面文字のルート=90 } for y := 0 to ytail do for x := gyotbl[y].head to gyotbl[y].tail do if (relationtb[y, x, 0] = 99) and (s[y, x] = s[k, m]) and ((y <> k) or (x <> m)) then begin relationtb[y, x, 0] := k; relationtb[y, x, 1] := m end end {if} end; begin { テーブル初期化 } for i := 0 to 9 do { usednumtbの配列要素 0--9 は 0 から 9 までの数字を代表し} usednumtb[i] := FALSE; { その数字が既に使用されている場合は true 値をとる} for i := 0 to YA do begin { 行情報テーブルを初期化 } gyotbl[i].relcnt := 0; gyotbl[i].keta := 0; { 桁数 } gyotbl[i].head := 0; { 有効文字列の左端の位置 } gyotbl[i].tail := 0; { の右端の位置 } gyotbl[i].relcnt := 0; { リレーションが設定されている文字の数 } gyotbl[i].dfp := 0; { 最初に現れるリレーション文字位置 } gyotbl[i].vmin := 0; { この行の有効文字列がとりうる最小数値 } gyotbl[i].vmax := 0 { 最大数値 } end; { メイン処理 } writeln; writeln('=> 文字列を入力してください.', '( 虫食い文字は*です.終了はcrのみ )'); repeat for y := 0 to YA do { テーブルの初期化 } for x := 0 to XA do begin s[y, x] := ' '; { シンボル(ソースストリング)テーブル } v[y, x] := 0; { 値テーブル } relationtb[y, x, 0] := 99; { 同一文字間の親子関係(位置)記録テーブル } relationtb[y, x, 1] := 99 end; for i := 0 to XA do begin adtabs[i] := 0; zerocounter[i] := 0 end; x := 0; zerocnt := 0; zerop := XA + 1; zsum := 0; kanjsw := false; (* writeln; *) for y := 0 to YA do begin { 一行分の入力データの取り込み } write('> '); readln(indata); if y=0 then begin if ord(indata[1]) < $20 then begin l := length(indata) -1; indata := copy(indata, 2, l) end; if (ord(indata[1]) >= $80) and (ord(indata[1]) <= $9F) then kanjsw := TRUE; if (ord(indata[1]) >= $E0) and (ord(indata[1]) <= $FF) then kanjsw := TRUE; end; if indata = '' then begin { CR 入力なら } if y=0 then halt; if y - 1 <> ytail then begin write('==> 入力式の行数があいません.'); goto 11 end else begin ytail := y - 1 end; goto 10 end; { 作業テーブルにパラメーターをセット } if not kanjsw then { 半角の場合は } l := length(indata) else l := length(indata) div 2; { 全角の場合は } if (l > MAXMOJISU) and (y < 2 ) then begin write('==> 文字数が多すぎます.'); goto 11 end; gyotbl[y].keta := l; { 行テーブルに桁数登録 } v1 := 1; v2 := 9; for i := 2 to l do begin { 桁数に該当する有効数字を登録 } v1 := v1 * 10; v2 := v2 * 10 + 9 end; gyotbl[y].vmin := v1; { 桁数がとる最小値 } gyotbl[y].vmax := v2; { 桁数がとる最大値 } if (y < 2) or (y = ytail) then { 行位置が被乗数行,乗数行,積行の場合は } i := 0 { 桁末尾の空白文字数はゼロ } else begin { その他の場合は } i := y - 2; { 次に,乗数行にゼロがある場合について行位置の補正を行う } repeat zerop := zerop - 1; if adtabs[zerop] = 0 then goto 5; zerocnt := zerocnt + 1 until zerop < gyotbl[1].head; 5: i := i + zerocnt end; gyotbl[y].head := XA - l + 1 - i; { 有効文字列の左端位置 } gyotbl[y].tail := gyotbl[y].head + l - 1; { 有効文字列の右端位置 } { データをシンボルテーブルに右詰めに移す.} for x := 0 to l - 1 do if not kanjsw then s[y, gyotbl[y].head + x] := copy(indata, x + 1, 1) + ' ' else { 全角の場合は } s[y, gyotbl[y].head + x] := copy(indata, x*2+1, 2); if y = 1 then begin { 乗数行の場合のみゼロがあるかチェック? } for i := gyotbl[1].tail downto gyotbl[1].head do begin if (s[1, i] = '0 ') or (s[1, i] = '0') then begin adtabs[i] := 99; zsum := zsum + 1 end else { 乗数行の該当桁は,右から数えて既に何個のゼロが } zerocounter[i] := zsum { 出現しているか,を記録 } end; ytail := l + 2 - zsum { 入力最終行の位置ポインターを決定する} end end; {for y} 10: fdisply(1); 11: write('データOK? (Y/N/E) >'); readln(indata); if (indata = 'E') or (indata = 'e') or (indata = 'E') or (indata = 'e') then halt until (indata = 'Y') or (indata = 'y') or (indata = 'Y') or (indata = 'y'); lvlid := 0; write('被乗数行の途中経過表示をする場合は,表示桁(1--) を入力 >'); read(inchar); if indata <> '' then begin lvlid := ord(inchar) - 48; read(inchar) end; writeln; { 後処理 } for i := 0 to XA do adtabs[i] := 0; {シンボルテーブルの数字(0-9)を定義テーブル(relationtb)に登録(親子関係なし)} for y := 0 to ytail do begin for x := gyotbl[y].head to gyotbl[y].tail do begin if not kanjsw then begin { 半角数字処理 } if (s[y, x] >= '0 ') and (s[y, x] <= '9 ') then begin s2 := copy(s[y,x], 1,1); val(s2, digit, code); v[y, x] := digit; relationtb[y, x, 0] := y; relationtb[y, x, 1] := x { 変更なし用に自分自身を定義する } end; end else begin { 全角数字処理 } if (s[y, x] >= '0') and (s[y, x] <= '9') then begin if s[y, x] = '1' then v[y, x] := 1 else if s[y, x] = '2' then v[y, x] := 2 else if s[y, x] = '3' then v[y, x] := 3 else if s[y, x] = '4' then v[y, x] := 4 else if s[y, x] = '5' then v[y, x] := 5 else if s[y, x] = '6' then v[y, x] := 6 else if s[y, x] = '7' then v[y, x] := 7 else if s[y, x] = '8' then v[y, x] := 8 else if s[y, x] = '9' then v[y, x] := 9 else if s[y, x] = '0' then v[y, x] := 0; relationtb[y, x, 0] := y; relationtb[y, x, 1] := x { 変更なし用に自分自身を定義する } end; end end{for x} end; {for y} { 同一覆面文字をサーチして,その親子関係を設定する } for i := gyotbl[0].head to gyotbl[0].tail do init_sub( 0, i ); for i := gyotbl[1].tail downto gyotbl[1].head do begin init_sub( 1, i ); l := XA - i + 2 - zerocounter[i]; for j := gyotbl[l].tail downto gyotbl[l].head do init_sub( l, j ); init_sub( ytail, i ) end; for i := gyotbl[ytail-1].tail-1 downto gyotbl[ytail].head do init_sub( ytail, i ); { 各行の定義文字情報を作成 } for y := 0 to ytail do begin for x := gyotbl[y].tail downto gyotbl[y].head do begin if relationtb[y, x, 0] < 99 then begin { この行に含まれるリレーション文字数合計 } gyotbl[y].relcnt := gyotbl[y].relcnt + 1; gyotbl[y].dfp := x { 一番始め(左端)のリレーション文字位置 } end end end end; procedure solut2( hijyosu: longint; x2: integer; adtabs: _intaxa; usednumtb: _inta10 ); {========================================================================= 2行目の乗数の値を(右から左方向に)決定するルーチン. ------------------------------------------------------------------------ hijyosu: 被乗数値 x2: 解を決定すべき乗数の左端からの位置(この値を変えて再帰呼出しする) adtabs: chrchk で使用する合計行の桁上がり数値保存テーブル usednumtb: =========================================================================} label 10, 20, 90; var l: integer; { 解の値(0から9までの数字)} fukws: _inta10; fukroot, linked: boolean; i: integer; result: integer; dx, dy: integer; begin { 初期値設定 } fukroot := FALSE; linked := FALSE; { 乗数行のX 位置の数値がゼロなら,位置をさらに左に移行 } while s[1, x2] = '0' do begin chrchk(0, 1, hijyosu, x2, result, linked, adtabs, usednumtb); if result <> 0 then goto 90; x2 := x2 - 1 end; { x 位置の乗数の初期値を 1 にセット } if gyotbl[XA - x2 + 2 - zsum].keta > gyotbl[0].keta then l := 2 else l := 1; if relationtb[1, x2, 0] < 90 then begin dx := relationtb[1, x2, 1]; dy := relationtb[1, x2, 0]; { リンク先 } l := v[dy, dx]; linked := TRUE { リンク先に値をセット } end else begin if relationtb[1, x2, 0] = 90 then { 覆面文字ルート } while ( true ) do begin if not usednumtb[l] then begin { まだ使用していない } usednumtb[l] := TRUE; fukroot := TRUE; goto 10 end; l := l + 1; if l > 9 then goto 90 end; 10: end; { メインルーチン } repeat { --- 初期値をセット } for i := 0 to 9 do fukws[i] := usednumtb[i]; v[1, x2] := l; { 乗数に値をセット } { --- メイン処理 } chrchk(l, 0, hijyosu, x2, result, linked, adtabs, usednumtb); if result = 0 then { 処理位置の文字 s(1,x) の値は1でok } if x2 = gyotbl[1].head then { 全文字へ割当が終了 } fdisply(0) else { 次の位置の文字処理へ進む } solut2(hijyosu, x2 - 1, adtabs, usednumtb); { --- 後処理 } if linked then goto 90; for i := 0 to 9 do usednumtb[i] := fukws[i]; if fukroot then begin usednumtb[l] := FALSE; while( true ) do begin l := l + 1; if l > 9 then goto 90; if not usednumtb[l] then begin { まだ使用していない } usednumtb[l] := TRUE; goto 20 end end; 20:; end else l := l + 1 until l > 9; 90: end; procedure solution( x, level: integer; adtabs: _intaxa; usednumtb: _inta10 ); {=========================================================================== 1行目の被乗数の値を(左から右方向順に)決定するルーチン. --------------------------------------------------------------------------- x: 解を決定すべき被乗数の左端からの位置(この値を変えて再帰呼出する) adtabs: 合計行データ(chrchkで使用)値 usednumtb: level: 途中経過表示をする際の表示レベル(乗数行の左端より1,2.) ===========================================================================} label 30, 40, 90; var linked, fukroot: boolean; l: integer; { 解の値(0から9の数字)} hijyosu: longint; i: integer; dx, dy: integer; nx: integer; procedure milestone; { 途中経過表示 } var i: integer; begin if level <= lvlid then begin for i := 1 to level do write(' '); write('位置-',level:2); write('=', l); gettime(hour, min, sec, sec100); writeln(' ', hour:2, ':', min:2, ':',sec:2) end end; begin { 初期値セット} linked := FALSE; fukroot := FALSE; { x位置の被乗数の解の初期値をセット } if x = gyotbl[0].head then l := 1 { 左端文字の初期値は1 } else l := 0; { それ以外は0 } if relationtb[0, x, 0] < 90 then begin { この位置の文字は既出なので親を捜す } dx := relationtb[0, x, 1]; dy := relationtb[0, x, 0]; { リンク先(親の位置) } l := v[dy, dx]; { 初期値には親の値を採用する } linked := TRUE { この文字はリンク値を採用したというリンクサインをセット } end else { 新しい文字なので新しい値を採用する } if relationtb[0, x, 0] = 90 then begin { 覆面文字のルートの場合は } while( true ) do begin if not usednumtb[l] then begin { この値はまだ使用されていないので } usednumtb[l] := TRUE; { 使用済みにし,} fukroot := TRUE; { 覆面文字のルートサインをセットする } goto 30 end; l := l + 1; if l > 9 then goto 90 { 全ての数字が使われており,新しい数字が得られない } end; 30: end; { メインルーチン } repeat v[0, x] := l; { 初期値を値テーブルにセット } milestone; {(採用値の)途中経過表示 } { --- メイン処理 } if x <> gyotbl[0].tail then { 被乗数行の右端まで達していないなら } solution(x + 1, level + 1, adtabs, usednumtb) { 次の位置で再帰呼出 } else begin { 被乗数行は終了したので } hijyosu := v[0, gyotbl[0].head]; { 被乗数値を作成して } for i := gyotbl[0].head + 1 to gyotbl[0].tail do hijyosu := hijyosu * 10 + v[0, i]; nx := gyotbl[1].tail; { 乗数行の解析文字位置(右端から左へ)をセットして } solut2(hijyosu, nx, adtabs, usednumtb) { 乗数行の値を決定に行く } end; { 後処理:リカーシブコールで値をさらに決定しに行ったが失敗に終わった. ということは,このルーチンで採用した値が妥当ではなかったわけで 設定した値を戻し,このルーチンを呼んだ親に戻る } if linked then goto 90; { 子であるので値を他の文字にする必要がない } if fukroot then begin { 覆面文字ルートなら } usednumtb[l] := FALSE; while( true ) do begin l := l + 1; if l > 9 then goto 90; if not usednumtb[l] then begin { まだ未使用 } usednumtb[l] := TRUE; goto 40 end end; 40: end else l := l + 1 { x位置の値を次の数字(+1)にして再トライする } until l > 9; 90: end; { -------------------------------------------- メインルーチン -------------------------------------------- } begin clrscr; writeln(' +++ +++'); writeln(' +++ 虫食い算パズルの解法(かけ算) +++'); writeln(' +++ +++'); writeln(' +++ a. udagawa : ver 2.2 +++'); writeln(' +++ Long Integer (9 桁まで) version +++'); repeat initset( adtabs, usednumtb ); gettime(hour, min, sec, sec100); writeln('解析スタート ', hour:2, ':', min:2, ':',sec:2); solution( gyotbl[0].head, 1, adtabs, usednumtb ); gettime(hour, min, sec, sec100); writeln('解析終了 ', hour:2, ':', min:2, ':',sec:2) until hour <0; end. |