『プログラミング言語の概念と構造』の p 28 に Little Quilt というのが紹介されています。これは、キルトと呼ばれる単純な模様が描かれた小片を回転させたり、くっつけたりして、下図のような模様を作り出します。
次の 2 つの基本片があり、それらを a (弓形の模様)と b (帯状の模様)と呼ぶことにします:
a:
b:
キルトに対する操作は、次の規則により指定される(p 28 より引用):
基本操作として、
がある。turn はキルトを時計回りに 90 度回転させます。
上図の一番右端にある図形は、turn(turn(turn(b))) で作られます。
sew はキルト同士をくっつけます。
turn, sew 関数から、反時計回りに 90 度回転させる関数 unturn と、キルトを縦にくっつける(sew は横にくっつける)pile 関数を作ります。それぞれ定義は以下のとおり:
unturn(x) = turn(turn(turn(x))) pile(x, y) = unturn(sew(turn(y), turn(x)))
上図の左端に位置する 2 つのキルトをそれぞれ bb, bb2 と名前を付けたとすると、右端のキルトは
turn(pile(bb, bb2))
で得られることになる。(bb が上のキルトで、bb2 が下)
プログラムを作成します。基本片は、回転させたものを含め予め用意しておきます。それぞれの名前は以下のとおり:
l0.bmp | l1.bmp | l2.bmp | l3.bmp |
c0.bmp | c1.bmp | c2.bmp | c3.bmp |
unit Unit2; interface uses SysUtils; const MAX_TABLE_SIZE = 8; type TQuilt = record Direction: 0..3; Kind: 0..1; end; TQuiltTable = array[1..MAX_TABLE_SIZE, 1..MAX_TABLE_SIZE] of TQuilt; EInvalidSewOperation = class(Exception); EOutofBounds = class(Exception); TQuiltCanvas = class private FTable: TQuiltTable; FWidth: Integer; FHeight: Integer; function GetTbl(i, j: Integer): TQuilt; public constructor Create(Quilt: TQuilt); overload; constructor Create(Quilt1, Quilt2: TQuilt); overload; constructor Create(QuiltCanvas: TQuiltCanvas); overload; function Turn: TQuiltCanvas; function Sew(QuiltCanvas: TQuiltCanvas): TQuiltCanvas; function Clone: TQuiltCanvas; property Height: Integer read FHeight; property Width: Integer read FWidth; property Table: TQuiltTable read FTable; property Tbl[i, j: Integer]: TQuilt read GetTbl; default; end; (* 以下の関数は、引数に対して破壊的です。 一度引数として渡したオブジェクトを再び参照してはいけません。 引数として渡された TQuiltCanvas クラスのインスタンスの解放は、 関数の中で行いますので、戻り値のオブジェクトに対してだけ Free メソッドを 呼んで下さい。 *) function Turn(QuiltCanvas: TQuiltCanvas): TQuiltCanvas; overload; function Turn(Quilt: TQuilt): TQuilt; overload; function Unturn(QuiltCanvas: TQuiltCanvas): TQuiltCanvas; overload; function Unturn(Quilt: TQuilt): TQuilt; overload; function Sew(QuiltCanvas1, QuiltCanvas2: TQuiltCanvas): TQuiltCanvas; overload; function Sew(Quilt1, Quilt2: TQuilt): TQuiltCanvas; overload; function Sew(Quilt: TQuilt; QuiltCanvas: TQuiltCanvas): TQuiltCanvas; overload; function Sew(QuiltCanvas: TQuiltCanvas; Quilt: TQuilt): TQuiltCanvas; overload; function Pile(QuiltCanvas1, QuiltCanvas2: TQuiltCanvas): TQuiltCanvas; overload; function Pile(Quilt1, Quilt2: TQuilt): TQuiltCanvas; overload; implementation function Turn(QuiltCanvas: TQuiltCanvas): TQuiltCanvas; overload; begin Result := QuiltCanvas.Turn; end; function Turn(Quilt: TQuilt): TQuilt; overload; begin Quilt.Direction := (Quilt.Direction+1) mod 4; Result := Quilt; end; function Unturn(QuiltCanvas: TQuiltCanvas): TQuiltCanvas; overload; begin Result := QuiltCanvas.Turn.Turn.Turn; end; function Unturn(Quilt: TQuilt): TQuilt; overload; begin Result := Turn(Turn(Turn(Quilt))); end; function Sew(QuiltCanvas1, QuiltCanvas2: TQuiltCanvas): TQuiltCanvas; overload; var Clone: TQuiltCanvas; begin if QuiltCanvas1 = QuiltCanvas2 then begin Clone := QuiltCanvas2.Clone; Result := QuiltCanvas1.Sew(Clone); Clone.Free; end else begin Result := QuiltCanvas1.Sew(QuiltCanvas2); QuiltCanvas2.Free; end; end; function Sew(Quilt1, Quilt2: TQuilt): TQuiltCanvas; overload; begin Result := TQuiltCanvas.Create(Quilt1, Quilt2); end; function Sew(Quilt: TQuilt; QuiltCanvas: TQuiltCanvas): TQuiltCanvas; overload; begin Result := TQuiltCanvas.Create(Quilt).Sew(QuiltCanvas); end; function Sew(QuiltCanvas: TQuiltCanvas; Quilt: TQuilt): TQuiltCanvas; overload; begin Result := QuiltCanvas.Sew(TQuiltCanvas.Create(Quilt)); end; function Pile(QuiltCanvas1, QuiltCanvas2: TQuiltCanvas): TQuiltCanvas; overload; var Clone: TQuiltCanvas; begin if QuiltCanvas1 = QuiltCanvas2 then begin Clone := QuiltCanvas1.Clone; Result := Unturn(Sew(Turn(QuiltCanvas2), Turn(Clone))); end else Result := Unturn(Sew(Turn(QuiltCanvas2), Turn(QuiltCanvas1))); end; function Pile(Quilt1, Quilt2: TQuilt): TQuiltCanvas; overload; begin Result := Unturn(Sew(Turn(Quilt2), Turn(Quilt1))); end; { TQuiltCanvas } constructor TQuiltCanvas.Create(Quilt: TQuilt); begin FTable[1, 1] := Quilt; FWidth := 1; FHeight := 1; end; constructor TQuiltCanvas.Create(Quilt1, Quilt2: TQuilt); begin FTable[1, 1] := Quilt1; FTable[2, 1] := Quilt2; FWidth := 2; FHeight := 1; end; constructor TQuiltCanvas.Create(QuiltCanvas: TQuiltCanvas); begin FTable := QuiltCanvas.Table; FWidth := QuiltCanvas.Width; FHeight := QuiltCanvas.Height; end; function TQuiltCanvas.Clone: TQuiltCanvas; begin Result := TQuiltCanvas.Create(Self); end; function TQuiltCanvas.GetTbl(i, j: Integer): TQuilt; begin Result := FTable[i, j]; end; function TQuiltCanvas.Sew(QuiltCanvas: TQuiltCanvas): TQuiltCanvas; var i, j: Integer; begin if FHeight <> QuiltCanvas.Height then raise EInvalidSewOperation.Create('連結画像同士の高さが異なります'); if (FWidth + QuiltCanvas.Width) > MAX_TABLE_SIZE then raise EOutOfBounds.Create('サイズが大きすぎます'); for i := 1 to QuiltCanvas.Width do for j := 1 to QuiltCanvas.Height do FTable[FWidth+i, j] := QuiltCanvas[i, j]; Inc(FWidth, QuiltCanvas.Width); Result := Self; end; function TQuiltCanvas.Turn: TQuiltCanvas; procedure Swap(var x, y: Integer); var Tmp: Integer; begin Tmp := x; x := y; y := Tmp; end; var i, j: Integer; TmpTable: TQuiltTable; begin for i := 1 to FWidth do for j := 1 to FHeight do begin FTable[i, j].Direction := (FTable[i, j].Direction+1) mod 4; // 向きを変える TmpTable[-j+FHeight+1, i] := FTable[i, j]; // 座標を移動させる end; Swap(FWidth, FHeight); FTable := TmpTable; Result := Self; end; end.
基本片であるキルトは、レコード型 TQuilt として定義します。その要素として、
の 2 つがありますが、Direction はキルトの向きを、Kind は 2 つの基本片のうち、どちらであるかの情報が格納されるようになっています。
画像は最終的に TPaintBox に描画します。その手法は,
ということで、知りたいのは、どのキルトがどの方向を向いていて、どの順序で並んでいるのかということなので、2 次元配列を用意し、その中にキルトの向きと種類の情報をもつ TQuilt 型の値を入れています。キルトの(描画)位置は、その 2 次元配列に格納されているインデックスに対応させます。これを擬似コードで表すと次のような感じになります:
for i := 1 to 横幅(横に並んでいる個数) for j := 1 to 縦幅 キルト := キルトの情報が入っている配列[i, j] キルトを描画。その位置は、 x が i * キルト(画像)の横幅 y が j * キルト(画像)の縦幅
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Grids, ExtCtrls; type TForm1 = class(TForm) PaintBox1: TPaintBox; PaintBox2: TPaintBox; Button1: TButton; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} uses Unit2; const a: TQuilt = (Direction:0; Kind:0); // 弓形の模様 b: TQuilt = (Direction:0; Kind:1); // 帯状の模様 var LineBmps, CurveBmps: array[0..3] of TBitmap; function GetQuiltBmp(Direction, Kind: Integer): TBitmap; begin if Kind = 0 then Result := CurveBmps[Direction] else Result := LineBmps[Direction]; end; procedure DrawQuilt(QuiltCanvas: TQuiltCanvas; Box: TPaintBox); var i, j: Integer; Bmp: TBitmap; Quilt: TQuilt; begin for i := 1 to QuiltCanvas.Width do for j := 1 to QuiltCanvas.Height do begin Quilt := QuiltCanvas[i, j]; Bmp := GetQuiltBmp(Quilt.Direction, Quilt.Kind); Box.Canvas.Draw((i-1)*Bmp.Width, (j-1)*Bmp.Height, Bmp); end; end; procedure TForm1.FormCreate(Sender: TObject); var i: Integer; begin for i := 0 to 3 do begin LineBmps[i] := TBitmap.Create; LineBmps[i].LoadFromFile(Format('l%d.bmp', [i])); CurveBmps[i] := TBitmap.Create; CurveBmps[i].LoadFromFile(Format('c%d.bmp', [i])); end; end; procedure TForm1.FormDestroy(Sender: TObject); var i: Integer; begin for i := 0 to 3 do begin LineBmps[i].Free; CurveBmps[i].Free; end; end; end.
定数 a と b が冒頭で名前を付けた 2 つの基本片であるキルト a と b に対応します。
function QuiltEx1: TQuiltCanvas; function Parts1: TQuiltCanvas; var p, q: TQuiltCanvas; begin p := Sew(Pile(Turn(b), Unturn(b)), Pile(Unturn(b), Turn(a))); q := Sew(Pile(Turn(Turn(b)), b), Pile(a, Turn(Turn(b)))); Result := Pile(p, q); end; function Parts2: TQuiltCanvas; begin Result := Sew(Pile(Turn(a), Unturn(a)), Pile(Turn(Turn(a)), a)); end; var c: TQuiltCanvas; begin c := Pile(Parts2, Turn(Turn(Parts2))); Result := Sew(Parts1, Sew(c, Turn(Turn(Parts1)))); end; function QuiltEx2: TQuiltCanvas; function Parts1: TQuiltCanvas; begin Result := Sew(Pile(Unturn(b), Turn(b)), Pile(a, Turn(Turn(a)))); end; function Parts2: TQuiltCanvas; begin Result := Pile(Parts1, Turn(Turn(Parts1))); end; begin Result := Sew(Parts2, Sew(Parts2, Parts2)); end; procedure TForm1.Button1Click(Sender: TObject); var Quilt: TQuiltCanvas; begin Quilt := QuiltEx1; DrawQuilt(Quilt, PaintBox1); Quilt.Free; Quilt := QuiltEx2; DrawQuilt(Quilt, PaintBox2); Quilt.Free; end;
これを実行すると、下のような模様が出来上がります。
以下は、『プログラミング言語の概念と構造』p 64 の演習問題 2.1 で紹介されているキルトです。
function Exa: TQuiltCanvas; var p: TQuiltCanvas; begin p := Pile(Sew(Unturn(a), Unturn(a)), Sew(Unturn(a), Unturn(a))); Result := Pile(p, p); end; function Exb: TQuiltCanvas; var p: TQuiltCanvas; begin p := Pile(Sew(Turn(b), Unturn(b)), Sew(Turn(b), Unturn(b))); Result := Pile(p, p); end; function Exc: TQuiltCanvas; var p: TQuiltCanvas; begin p := Pile(Sew(a, Unturn(a)), Sew(Turn(a), Turn(Turn(a)))); Result := Pile(p, p); end; function Exd: TQuiltCanvas; var p: TQuiltCanvas; begin p := Pile(Sew(Unturn(b), Turn(b)), Sew(Turn(b), Unturn(b))); Result := Pile(p, p); end; function Exe: TQuiltCanvas; function Parts: TQuiltCanvas; begin Result := Pile(Sew(Turn(a), Turn(Turn(a))), Sew(b, Unturn(b))); end; begin Result := Pile(Parts, Turn(Turn(Parts))); end;
up
Last update: 2004/9/14