『プログラミング言語の概念と構造』の 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