コラム / テトリスぽいもの / 15



もうすぐピースの動きが見れます!!!。
FormのpublicにTPieceの変数を定義します。
 public
   { Public 宣言 }
   BackScreen:TBitmap;
   b_img:TBitmap;
   MapBlocks:TMapBlocks;
   Piece:TPiece;
   procedure CopyBlockImageToField(x,y:integer;block:TBitmap);
 end;
初期化、解放を書きます。
procedure TForm1.FormCreate(Sender: TObject);
begin
   BackScreen:=TBitmap.Create;

          (略)

   MapBlocks.Blocks[17,4]:=True;

   Piece:=TPiece.Create;
   Piece.x:=5;
   Piece.y:=0;
   Piece.d_counter:=0;
   Piece.d_max:=30;//30フレーム、つまり1秒
   Piece.shape[0,1]:=True;
   Piece.shape[1,1]:=True;
   Piece.shape[2,1]:=True;
   Piece.shape[2,2]:=True;
   Piece.MapBlocks:=MapBlocks;
   Piece.death:=False;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
   Piece.Free;
   MapBlocks.Free;

   b_img.Free;
   BackScreen.Free;
end;
初期化の、最後から二行目、
   Piece.MapBlocks:=MapBlocks;
ですが、この処理は、TForm1.FormCreate
なので、デフォルトの参照はForm1. となります。
ゲーム全体の方のMapBlocksはForm1のpublic変数なので、
何もつけない場合は、Form1.MapBlocks となり、
MapBlocksで参照できます。
Pieceの参照用のMapBlocksは、
Piece.をつけて参照すれば、Form1.MapBlocksとは違うので、
ごちゃごちゃにならないで済んでいる、というわけです。
Timerのメインルーチンを編集します。

procedure TForm1.Timer1Timer(Sender: TObject);
begin
   with BackScreen.Canvas do begin
       Brush.Color:=clBlack;
       FillRect(Rect(0,0,150,300));
   end;

   if Assigned(Piece) then begin
       Piece.Main;
       if Piece.death then begin
           FreeAndNil(Piece);
       end;
   end;

   MapBlocks.Draw;

   PaintBox1.Canvas.Draw(0,0,BackScreen);
end;
新しく追加された部分を見て見ましょう。
   if Assigned(Piece) then begin
       Piece.Main;
       if Piece.death then begin
           FreeAndNil(Piece);
       end;
   end;
まず、Assigned関数は、参照がnilじゃなければTrueを返します。
nilというのは、クラス型の変数の値が、
住所じゃなくて、「空」 になっている状態です。
「0」ではありません。「無し、空」です。
Pieceは積もったら役割が終わるので、
積もった後Freeして解放されるのですが、
解放したあともルーチンを呼び出したらエラーが出てしまいます。
ですから、解放するときに、住所を「空」にして、
住所が空なら実行をしないようにすれば、
解放=住所が空=もう実行しない
とできます。
参照がnilじゃなかったら、つまり、まだ解放されてなかったら、
       Piece.Main;
       if Piece.death then begin
           FreeAndNil(Piece);
       end;
を実行します。
まずはmainです。
mainは1フレーム分の処理でした。
で、死んだときはdeathを真にする約束でした。
なので、mainの後は、
deathが真かチェックし、真なら、
「FreeAndNilで解放&住所を空」
にしています。
これで、無事解放でき、
次のループでは、住所がnilなので、
Assigned関数が偽を返し、
main;は実行されないですむ、というわけです。
実行。

img6.png

動いた動いた!
まだバグがあるのですが、それを取るのはまた明日