Some day–and that day may never come–it may fall to you to write the most important program of your life. Here’s one man’s example of how to do that.

In these times of not going out, and these times of having no time, and these times of our minds on other things, readers may find themselves facing a terrible fate: Valentine’s Day without a card. I solved this problem years ago, and solve it as a matter of principle every year. I am a programmer. Of course I program my Valentines.

Somewhere between 60 and 70 years ago, I learned and memorized an important mathematical expression:

cube root of x squared, plus or minus the square root of x squared.

As we’ll soon see, this expression draws a very nice heart shape. I have no recollection of where I found it, in some old math thing that I was reading. I feel sure that I learned it in my teens but that was so long ago that it might have been last week. More likely decades ago.

I’ve used this expression often to create a valentine. I did so this year. My plan here is to create, yet again, a program that draws an interesting valentine, and I plan to polish that program far beyond reason. Why? Just for the fun of it.

Analysis

It is clear on inspection1 that the range of x in this expression must be from -1 to +1, because the square root is imaginary outside that range. It’s only a bit less clear that the heart will be quite small. We’ll want a larger one. And it’s clear enough that there are two points for every value of x, the “plus” one and the “minus” one.

OK, that’s enough math. Let’s get started.

Programming

I will of course be doing this in Codea, which has nice graphics and is my current play language of choice. Has been for years now: it’s quite nice.

We’ll be doing this is small steps, because This is the Way. We’ll not be using TDD for this. The program is Important, but it is also both basically simple and quite graphically fiddly. In my hands, TDD doesn’t help much with those things, and there’s always the chance that we’ll discover that not using TDD is a big mistake. Everyone enjoys a laugh on me, I hope.

I start with a nearly empty program:

-- Heart
-- RJ 20220216

function setup()
end

function draw()
end

Because I am a fantastically good programmer, I will type in the entire first cut at the program. Here goes:

function draw()
    background(255)
    stroke(255,0,0)
    for x = -1,1,0.01 do
        drawHeartLine(x)
    end
end

function drawHeartLine(x)
    local ht = 1
    local hb = -1
    line(x,ht,x,hb)
end

I decided that the top of the heart is always 1 and the bottom always -1, and I draw a line between those points. I’m basically going to fill the heart with lines. Of course this draws nothing: it’s drawing a tiny rectangle at or near 0,0, the lower left of the screen. We need it bigger and centered.

Our deep knowledge of Codea suggests translating and scaling.

function draw()
    background(255)
    stroke(255,0,0)
    translate(WIDTH/2,HEIGHT/2)
    scale(100)
    for x = -1,1,0.01 do
        drawHeartLine(x)
    end
end

This draws nothing. A wild guess suggests to me that I need to set the stroke width of the line, so in desperation I try this:

function draw()
    background(255)
    stroke(255,0,0)
    strokeWidth(1)
    translate(WIDTH/2,HEIGHT/2)
    scale(100)
    for x = -1,1,0.01 do
        drawHeartLine(x)
    end
end

This provides an interesting square:

rounded corners

The edges are a bit fuzzy and the corners are rounded. Does Codea allow a setting of the line caps? It does. Experimentation suggests that PROJECT gives the best result, but I am not satisfied.

fuzzy square

I conclude, tentatively, that we may need to scale the math for our heart, not just the graphics, but we’ll see. Let’s start doing the actual drawing. That entails making this function match our desired math incantation:

function drawHeartLine(x)
    local ht = heartTop(x)
    local hb = heartBottom(x)
    line(x,ht,x,hb)
end

function heartTop(x)
    return 1
end

function heartBottom(x)
    return -1
end

I extract two trivial functions, which of course gives me the same picture. Now for some math.

function drawHeartLine(x)
    local ht = heartTop(x)
    local hb = heartBottom(x)
    line(x,ht,x,hb)
end

function heartTop(x)
    return (x*x)^0.33333 + math.sqrt(1-x*x)
end

function heartBottom(x)
    return (x*x)^0.33333 - math.sqrt(1-x*x)
end

The math looks good but the picture does not:

blocky heart

I conclude that this is because scaling up from a fractional pixel to a big block is bad. I decide to scale inside the function.

function draw()
    background(255)
    stroke(255,0,0)
    strokeWidth(1)
    lineCapMode(PROJECT)
    translate(WIDTH/2,HEIGHT/2)
    scale(10)
    for x = -1,1,0.01 do
        drawHeartLine(x)
    end
end

function drawHeartLine(x)
    local sc = 10
    local ht = heartTop(x)
    local hb = heartBottom(x)
    line(sc*x, sc*ht, sc*x,sc*hb)
end

Note that I reduced the scale call in draw by a factor of ten, since I’ve scaled the actual heart up by ten. Now I get something nearly OK:

nearly ok heart

The bottom point looks kind of flat. I think I’ll try turning off the lineCapMode. That seems to improve the point a bit:

better point

Now the heart is rather tall and skinny. This is a property of the actual expression. Let’s try scaling it a bit in the y direction.

function drawHeartLine(x)
    local scx = 10
    local scy = 8
    local ht = heartTop(x)
    local hb = heartBottom(x)
    line(scx*x, scy*ht, scx*x,scy*hb)
end

That looks good to me:

good heart

Now before we do anything more like a Valentine card, let’s see if we can tidy up this code a bit. I don’t really like that we’re scaling it in two places. Normally, I like to use the Codea scaling capability but since our basic equation is scaling also … no, let’s not move yet. Let’s just let the heart draw itself as it wishes, and we’ll scale it as needed when we place it, or refactor a bit later, when we know more.

I have in mind a big heart with a circle of little ones around it. Let’s code that by intention.

First we’ll extract a drawHeart function:

function draw()
    background(255)
    stroke(255,0,0)
    strokeWidth(1)
    translate(WIDTH/2,HEIGHT/2)
    scale(10)
    drawHeart()
end

function drawHeart()
    for x = -1,1,0.01 do
        drawHeartLine(x)
    end
end

Picture’s still good. Now to draw the circle:

function draw()
    background(255)
    stroke(255,0,0)
    strokeWidth(1)
    translate(WIDTH/2,HEIGHT/2)
    pushMatrix()
    scale(10)
    drawHeart()
    popMatrix()
    drawHeartCircle()
end

function drawHeartCircle()
    radius = 400
    for angle = 0,300,60 do
        pushMatrix()
        rotate(angle)
        translate(radius,0)
        drawHeart()
        popMatrix()
    end
end

I just randomly chose 400 as the radius of the circle. To position each heart in the circle, I rotate the screen to the designed angle (0,60,120, etc), then translate right by the radius. Then I draw. That provides a result that may surprise you. The hearts are small, no surprise there, but they are also rotated. A moment’s thought, though, and we gain a rough view through this issue: we rotated the screen and then drew the heart straight up, but the definition of “straight up” is rotated2.

rotated little hearts

We can rotate back after the translate:

function drawHeartCircle()
    radius = 400
    for angle = 0,300,60 do
        pushMatrix()
        rotate(angle)
        translate(radius,0)
        rotate(-angle)
        drawHeart()
        popMatrix()
    end
end

Now they should stand up, and they do:

unrotated little hearts

They’re a bit tiny, aren’t they? Let’s try making them, oh, half the size of the big one:

function drawHeartCircle()
    radius = 400
    for angle = 0,300,60 do
        pushMatrix()
        rotate(angle)
        translate(radius,0)
        rotate(-angle)
        scale(5)
        drawHeart()
        popMatrix()
    end
end

bigger little hearts

I have one more thing in mind, and let’s do it before we refactor. I’d like the circle to spin around the big heart. To do that, we need an increasing angle “added” to the rotation of the little hearts. We could do that with more rotate calls, but I think that would make it difficult to get them vertical. Instead, let’s just add in a small value to the rotation in the heart circle:

function setup()
    adj = 0
end

function draw()
    background(255)
    stroke(255,0,0)
    strokeWidth(1)
    translate(WIDTH/2,HEIGHT/2)
    pushMatrix()
    scale(10)
    drawHeart()
    popMatrix()
    drawHeartCircle(adj)
    adj = adj + 0.1
end

function drawHeartCircle(adjustment)
    radius = 400
    for angle = 0,300,60 do
        pushMatrix()
        rotate(angle+adjustment)
        translate(radius,0)
        rotate(-angle-adjustment)
        scale(5)
        drawHeart()
        popMatrix()
    end
end

This gives me just what I want:

small hearts rotating

Now in actual use, for this year’s Valentine, I added some mushy text and set the program running on the big-screen TV. The iPad’s screen sharing made that quite easy, and it made for an impressive Valentine card.

Polishing

Now, of course, for a real Valentine of this type, one is always in a hurry and stops as soon as it does what it needs to. After all, we’ll do a new one next year. But let’s polish this one, and try to go too far.

Here’s the whole program now:

-- Heart
-- RJ 20220216

function setup()
    adj = 0
end

function draw()
    background(255)
    stroke(255,0,0)
    strokeWidth(1)
    translate(WIDTH/2,HEIGHT/2)
    pushMatrix()
    scale(10)
    drawHeart()
    popMatrix()
    drawHeartCircle(adj)
    adj = adj + 0.1
end

function drawHeart()
    for x = -1,1,0.01 do
        drawHeartLine(x)
    end
end

function drawHeartCircle(adjustment)
    radius = 400
    for angle = 0,300,60 do
        pushMatrix()
        rotate(angle+adjustment)
        translate(radius,0)
        rotate(-angle-adjustment)
        scale(5)
        drawHeart()
        popMatrix()
    end
end

function drawHeartLine(x)
    local scx = 10
    local scy = 8
    local ht = heartTop(x)
    local hb = heartBottom(x)
    line(scx*x, scy*ht, scx*x,scy*hb)
end

function heartTop(x)
    return (x*x)^0.33333 + math.sqrt(1-x*x)
end

function heartBottom(x)
    return (x*x)^0.33333 - math.sqrt(1-x*x)
end

I see duplication here:

function drawHeartLine(x)
    local scx = 10
    local scy = 8
    local ht = heartTop(x)
    local hb = heartBottom(x)
    line(scx*x, scy*ht, scx*x,scy*hb)
end

We can remove that thusly:

function drawHeartLine(x)
    local scx = 10
    local scy = 8
    local xs = x*scx
    local ht = heartTop(x)*scy
    local hb = heartBottom(x)*scy
    line(xs,ht, xs,hb)
end

More lines, less duplication. Better? You decide. I’m here to do too much.

I see duplication here:

function heartTop(x)
    return (x*x)^0.33333 + math.sqrt(1-x*x)
end

function heartBottom(x)
    return (x*x)^0.33333 - math.sqrt(1-x*x)
end

We can remove some of the excess x*x this way:

function drawHeartLine(x)
    local scx = 10
    local scy = 8
    local xs = x*scx
    local xsq = x*x
    local ht = heartTop(xsq)*scy
    local hb = heartBottom(xsq)*scy
    line(xs,ht, xs,hb)
end

function heartTop(xsq)
    return xsq^0.33333 + math.sqrt(1-xsq)
end

function heartBottom(xsq)
    return xsq^0.33333 - math.sqrt(1-xsq)
end

Gee, there’s still a lot of duplication there. Let’s do this:

function drawHeartLine(x)
    local scx = 10
    local scy = 8
    local xs = x*scx
    local xsq = x*x
    local cr = xsq^0.33333
    local sr = math.sqrt(1-xsq)
    local ht = (cr + sr)*scy
    local hb = (cr - sr)*scy
    line(xs,ht, xs,hb)
end

Program still runs fine. I’m sure this is a lot better.

Maybe better names would help.

function drawHeartLine(x)
    local scaleX = 10
    local scaleY = 8
    local xScaled = x*scaleX
    local xSquared = x*x
    local cubeRoot = xSquared^0.33333
    local squareRoot = math.sqrt(1-xSquared)
    local heartTop = (cubeRoot + squareRoot)*scaleY
    local heartBottom = (cubeRoot - squareRoot)*scaleY
    line(xScaled,heartTop, xScaled,heartBottom)
end

Sure, that’s pretty wonderful. Not perfect, though. It has lost some meaning. Let’s do better.

function drawHeartLine(x)
    local scaleX = 10
    local scaleY = 8
    local xScaled = x*scaleX
    local xSquared = x*x
    local cubeRootOfXSquared = xSquared^0.33333
    local squareRootOfOneMinusXSquared = math.sqrt(1-xSquared)
    local heartTop = (cubeRootOfXSquared + squareRootOfOneMinusXSquared)*scaleY
    local heartBottom = (cubeRootOfXSquared - squareRootOfOneMinusXSquared)*scaleY
    line(xScaled,heartTop, xScaled,heartBottom)
end

Oh hell yeah, that’s much better than this:

function drawHeartLine(x)
    local scx = 10
    local scy = 8
    local ht = heartTop(x)
    local hb = heartBottom(x)
    line(scx*x, scy*ht, scx*x,scy*hb)
end

function heartTop(x)
    return (x*x)^0.33333 + math.sqrt(1-x*x)
end

function heartBottom(x)
    return (x*x)^0.33333 - math.sqrt(1-x*x)
end

Well, no, actually, this isn’t better. I would rate it somewhere around “terrible”.

Yes, There IS a Lesson Here

We’ve removed just about as much duplication as we can, and now we see why Beck’s rules #2 and #3 are in competition.

“The code is simple enough when, in priority order:

  1. It runs all the tests;
  2. It contains no duplication;
  3. It expresses all our ideas about the program;
  4. It minimizes entities: classes, methods, functions, …”

Beck himself expressed the rules with #2 and 3 in that order and also reversed. I prefer them in this order … but then I don’t follow them.

I like treating removal as higher priority than expression, at first, because duplication is so often a sign–in my code at least–that there is an idea waiting to be discovered and expressed, and most commonly, extracting the duplication will expose that idea.

But as we’ve seen here, removing duplication can be taken too far, to the point where it obscures our idea rather than exposing it.

Therefore the proper order for simple code is expression over removal of duplication. But, in my view, the proper order for discovering simple code is duplication removal over expression. Until it isn’t.

The “Best” Way?

Is there a best way to express this program? I’m sure that I don’t know. Is there a better way? Well, we didn’t find it this time. Let’s set it back and take one more look.

Our best cut so far is roughly this one:

function drawHeart()
    for x = -1,1,0.01 do
        drawHeartLine(x)
    end
end

function drawHeartLine(x)
    local scx = 10
    local scy = 8
    local xs = x*scx
    local ht = heartTop(x)*scy
    local hb = heartBottom(x)*scy
    line(xs,ht, xs,hb)
end

function heartTop(x)
    return (x*x)^0.33333 + math.sqrt(1-x*x)
end

function heartBottom(x)
    return (x*x)^0.33333 - math.sqrt(1-x*x)
end

Can we do better? I try using Codea’s scale in the heartLine function, but I get that blurry effect again. We’re better off scaling directly. That’s kind of a hack but there you are, you gotta live with the system you have.

What if we created all the points at once:

function drawHeartLine(x)
    local scx = 10
    local scy = 8
    local x,t, x,b = heartSlice(x)
    line(x*scx,t*scy, x*scx,b*scy)
end

function heartSlice(x)
    return x,heartTop(x), x,heartBottom(x)
end

I’ve gone back to scaling longhand. Could pass the scaling down, I suppose. Let’s hold that thought and see how we might modify heartSlice. Let’s inline:

function heartSlice(x)
    return x,heartTop(x), x,heartBottom(x)
end

function heartTop(x)
    return (x*x)^0.33333 + math.sqrt(1-x*x)
end

function heartBottom(x)
    return (x*x)^0.33333 - math.sqrt(1-x*x)
end

Inlining both functions, we get:

function heartSlice(x)
    return x,(x*x)^0.33333 + math.sqrt(1-x*x), x,(x*x)^0.33333 - math.sqrt(1-x*x)
end

Now we can try removing the duplication again:

function heartSlice(x)
    local cube = (x*x)^0.33333
    local sqrt = math.sqrt(1-x*x)
    return x,cube+sqrt, x,cube-sqrt
end

That’s actually pretty nice. I do want a comment:

function heartSlice(x)
    -- cube_root(xSquared) +/- sqrt(1-xSquared)
    local cube = (x*x)^0.33333
    local sqrt = math.sqrt(1-x*x)
    return x,cube+sqrt, x,cube-sqrt
end

What about the caller:

function drawHeartLine(x)
    local scx = 10
    local scy = 8
    local x,t, x,b = heartSlice(x)
    line(x*scx,t*scy, x*scx,b*scy)
end

I think we’d do better with better names for t and b.

function drawHeartLine(x)
    local scx = 10
    local scy = 8
    local x,top, x,bottom = heartSlice(x)
    line(x*scx,top*scy, x*scx,bottom*scy)
end

We should express that there’s scaling going on:

function drawHeart()
    for x = -1,1,0.01 do
        drawHeartLineScaled(x)
    end
end

function drawHeartLineScaled(x)
    local scx = 10
    local scy = 8
    local x,top, x,bottom = heartSlice(x)
    line(x*scx,top*scy, x*scx,bottom*scy)
end

I think I rather like this version. Let’s look at the whole thing again:

-- Heart
-- RJ 20220216

function setup()
    adj = 0
end

function draw()
    background(255)
    stroke(255,0,0)
    strokeWidth(1)
    translate(WIDTH/2,HEIGHT/2)
    pushMatrix()
    scale(10)
    drawHeart()
    popMatrix()
    drawHeartCircle(adj)
    adj = adj + 0.1
end

function drawHeartCircle(adjustment)
    radius = 400
    for angle = 0,300,60 do
        pushMatrix()
        rotate(angle+adjustment)
        translate(radius,0)
        rotate(-angle-adjustment)
        scale(5)
        drawHeart()
        popMatrix()
    end
end

function drawHeart()
    for x = -1,1,0.01 do
        drawHeartLineScaled(x)
    end
end

function drawHeartLineScaled(x)
    local scx = 10
    local scy = 8
    local x,top, x,bottom = heartSlice(x)
    line(x*scx,top*scy, x*scx,bottom*scy)
end

function heartSlice(x)
    -- cube_root(xSquared) +/- sqrt(1-xSquared)
    local cube = (x*x)^0.33333
    local sqrt = math.sqrt(1-x*x)
    return x,cube+sqrt, x,cube-sqrt
end

We could do more, but the day is already too silly for its pants. Let’s sum up.

Summary

First of all, you never know when you may need to draw a Valentine-style heart, and now you know how. I’ll leave the mushy text to your own discretion. I recommend “Snell-Roundhand Black” font for a nice classic look, but YMMV.

Then there’s the question of how far we go along the line from type it in and get the hell out of town, and polish it until it gleams like gold. We see that not every “improvement” really is, and maybe we see that if you try, sometimes you get what you need. In this program, pretty much any old heart will do, since we can move and scale at will. Maintaining this little app will probably not require us to change the equation nor how we represent it.

But in general, we’ll be back, using and editing our code again and again. So if we can make it a bit less ad hoc, a bit more clear, every time we pass through, it’ll pay off in faster development with less confusion.

Should you do as I do? I don’t do “should”. I’m not here to tell you how to be a programmer or software engineer or big macho craftsman. I’m here to show you what I do, and what happens when I do it. What I do works for me and makes my day-to-day, hour-to-hour programming more enjoyable and less of a slog. Would that happen to you? I don’t know. It might. Would it be worth it to you? I don’t know. It might.



  1. “Clear on inspection” is math talk for “it is possible to figure this out, probably”. 

  2. Thought, though, rough, through. Get it?