Delphi: Sierpinski triangle: procedure not running itself completely

The problem is that you draw the first triangle, then you call DrawTriangle to draw the bottom-left part, which does that, and then calls DrawTriangle to draw the new bottom-left part, and so on, until count has reached n. Then we return one procedure at a time to the original procedure, and in each step we draw the remaining two triangles only once.

The following logic works as intended. (Remove the global count variable.)

procedure DrawTriangle(aCanvas: TCanvas; x, y, size: extended; n: integer);
var
  h: extended;
  w: extended;
  x1, x2, x3, y1, y2, y3: extended;
begin
  w := size;
  h := size;
  x1 := x;
  y1 := y;

  //1st - left
  aCanvas.MoveTo(Round(x1), Round(y1));
  aCanvas.LineTo(Round(x1+w*2), Round(y1));
  aCanvas.LineTo(Round(x1+w), Round(y1-h));
  aCanvas.LineTo(Round(x1), Round(y1));
  //2nd - right
  x2 := x1+w*2;
  y2 := y1;
  aCanvas.MoveTo(Round(x2), Round(y2));
  aCanvas.LineTo(Round(x2+w*2), Round(y2));
  aCanvas.LineTo(Round(x2+w), Round(y2-h));
  aCanvas.LineTo(Round(x2), Round(y2));
  //3rd - top
  x3 := x2-w;
  y3 := y2-h;
  aCanvas.MoveTo(Round(x3), Round(y3));
  aCanvas.LineTo(Round(x3+w*2), Round(y3));
  aCanvas.LineTo(Round(x3+w), Round(y3-h));
  aCanvas.LineTo(Round(x3), Round(y3));

  //Run itself
  if n > 0 then
  begin
    DrawTriangle(aCanvas, x1, y1, size/2, n-1);
    DrawTriangle(aCanvas, x2, y2, size/2, n-1);
    DrawTriangle(aCanvas, x3, y3, size/2, n-1);
  end;
end;

I leave it as an exercise to figure out why it works.

Also notice I removed the completely unnecessary try..except block.

Finally, you can write the code much more efficiently, as demonstrated by Sir Rufo.


Your attempt to limit the depth of recursion contains an error.

You draw each of the 3 inner triangles and increment count, and then call DrawTriangle again to draw the triangles within those triangles. Each call increments count again, meaning that count does not reflect the depth of recursion but simply how many times DrawTriangle has been called. As a result of specifying a limit of 10, you will find that in your results, 10 sets of triangles have been drawn.

Instead of incrementing count to track recursion depth you should decrement n for each call until n = 0.

To make this intention clearer you can use a nested procedure where the outer call accepts the maximum number of levels of recursion specified with the inner, nested procedure doing the actual recursion indicating the number of levels remaining and decrementing this number for the recursive calls themselves:

procedure DrawTriangle(aCanvas: TCanvas;x,y,size : extended; maxLevels: integer);

  procedure Draw(x,y,size: extended; levelsLeft: integer);
  var
    h : extended;
    w : extended;
    i : integer;
    x1,x2,x3,y1,y2,y3 : extended;
  begin
    w := size;
    h := size;
    x1 := x;
    y1 := y;

    //1st - left
    aCanvas.MoveTo(Round(x1),Round(y1));
    aCanvas.LineTo(Round(x1+w*2),Round(y1));
    aCanvas.LineTo(Round(x1+w),Round(y1-h));
    aCanvas.LineTo(Round(x1),Round(y1));

    //2nd - right
    x2 := x1+w*2;
    y2 := y1;
    aCanvas.MoveTo(Round(x2),Round(y2));
    aCanvas.LineTo(Round(x2+w*2),Round(y2));
    aCanvas.LineTo(Round(x2+w),Round(y2-h));
    aCanvas.LineTo(Round(x2),Round(y2));

    //3rd - top
    x3 := x2-w;
    y3 := y2-h;
    aCanvas.MoveTo(Round(x3),Round(y3));
    aCanvas.LineTo(Round(x3+w*2),Round(y3));
    aCanvas.LineTo(Round(x3+w),Round(y3-h));
    aCanvas.LineTo(Round(x3),Round(y3));

    //Run itself
    if (levelsLeft > 0) then
    begin
      Draw(x1, y1, size/2, levelsLeft - 1);
      Draw(x2, y2, size/2, levelsLeft - 1);
      Draw(x3, y3, size/2, levelsLeft - 1);
    end;
  end;

begin
  if Assigned(aCanvas) then
    Draw(x, y, size, maxLevels);
end;

This also allows pre-conditions to be tested in a clearer fashion, in this case limited currently to ensuring that a canvas has been specified, but could also involve normalising or validating other parameters as might be required, before initiating the recursive call.

Since the original parameters remain in scope for the nested procedure it also means that you can limit the parameters in the calls to the recursive procedure to only those which actually change on each call. (I have updated the code in my answer to incorporate this).

Incidentally, a try..except that simply raises any caught exceptions is exactly equivalent to having no try..except block at all so I removed it from this implementation.

Also you might wish to consider adding an additional condition to halt recursion if the value of size reaches a certain minima (where the inner triangles are indistinct, e.g. size < 2).


Just as an addition to the given answers, here is a DRY version:

procedure DrawTriangleDRY( aCanvas: TCanvas; CenterX, CenterY, Width, Height: extended; n: integer );
begin
  aCanvas.MoveTo( Round( CenterX ), Round( CenterY - Height / 2 ) ); // top
  aCanvas.LineTo( Round( CenterX + Width / 2 ), Round( CenterY + Height / 2 ) ); // bottom right
  aCanvas.LineTo( Round( CenterX - Width / 2 ), Round( CenterY + Height / 2 ) ); // bottom left
  aCanvas.LineTo( Round( CenterX ), Round( CenterY - Height / 2 ) ); // top

  // draw childs
  if n > 0
  then
    begin
      // top
      DrawTriangleDRY( aCanvas, CenterX, CenterY - Height / 4, Width / 2, Height / 2, n - 1 );
      // left
      DrawTriangleDRY( aCanvas, CenterX - Width / 4, CenterY + Height / 4, Width / 2, Height / 2, n - 1 );
      // right
      DrawTriangleDRY( aCanvas, CenterX + Width / 4, CenterY + Height / 4, Width / 2, Height / 2, n - 1 );
    end;
end;

Update

I just realized, that this can be optimized to only draw the last childs

procedure DrawTriangleDRY( aCanvas: TCanvas; CenterX, CenterY, Width, Height: extended; n: integer );
begin
  // draw childs
  if n > 0
  then
    begin
      DrawTriangleDRY( aCanvas, CenterX, CenterY - Height / 4, Width / 2, Height / 2, n - 1 ); // top
      DrawTriangleDRY( aCanvas, CenterX - Width / 4, CenterY + Height / 4, Width / 2, Height / 2, n - 1 ); // left
      DrawTriangleDRY( aCanvas, CenterX + Width / 4, CenterY + Height / 4, Width / 2, Height / 2, n - 1 ); // right
    end
  else
    begin
      aCanvas.MoveTo( Round( CenterX ), Round( CenterY - Height / 2 ) ); // top
      aCanvas.LineTo( Round( CenterX + Width / 2 ), Round( CenterY + Height / 2 ) ); // bottom right
      aCanvas.LineTo( Round( CenterX - Width / 2 ), Round( CenterY + Height / 2 ) ); // bottom left
      aCanvas.LineTo( Round( CenterX ), Round( CenterY - Height / 2 ) ); // top
    end
end;

Tags:

Delphi