Two different things people mean
The phrase “ASCII art program” covers two different ideas, and it is worth separating them.
The first is a program that prints ASCII art. It might draw a triangle of asterisks or a banner. The source code can look like anything, because the picture appears in the output. This is a common beginner exercise in nearly every language and is not especially unusual.
The second is a program whose source text is itself a picture, such as a spiral, a logo or a silhouette. That second kind is the subject here. The question it raises is how much of the shape the program needs, and how much is just dressing.
Shape as decoration
In most languages, the structure of the text is flexible. Spaces, line breaks and comments can be added or removed in many places, and a programmer who wants the source to look like a shape can pad it with extra characters until it fits.
This has a long tradition in obfuscated code contests, where entries are sometimes laid out so that the code reads as a picture. The picture is usually achieved by a mix of choosing where to break lines, inserting harmless filler and using the freedom to rename things. The result looks clever, but the shape is cosmetic. Reformat the file and it still runs the same way.
Decoration is also easy to overdo. A heavily padded layout can make the actual logic harder to find, which is the same tension discussed under code readability in esoteric languages: the more effort goes into appearance, the less the text tells you about behavior.
Shape as structure
In a two-dimensional language the situation is different. The layout carries information, so a shape can be a side effect of the program rather than a coat of paint on it.
Consider Befunge, where an instruction pointer moves across the grid and arrows turn it. A loop is a path that returns to where it started, so loops often look like rectangles. A program with several nested loops can come out as a set of boxes. Nobody planned the picture; it is a record of how the pointer travels.
Here is a tiny program, framed to show the idea. It prints the two letters “Hi” and stops:
>"iH",,v
^ @
Trace it. The pointer starts at the top left going right. The first character is an arrow that keeps it heading right. The quote character switches on string mode, so the next characters, i and H, are pushed on the stack instead of executed. The second quote ends string mode. Each comma then pops one value from the stack and prints it as a character. The top of the stack is H, so the output is H followed by i. The v at the end of the first line turns the pointer downward, into the @ on the second line, which ends the program.
Notice the caret at the start of the second line. Nothing ever reaches it. It is there only so the program looks like a frame, which makes it a small example of dead code kept for appearance. In a real program such a character could be harmless, or it could become a trap if a later edit sends the pointer into it.
Making the code and the picture agree
Anyone who wants a program shaped like a picture in a grid language has two competing demands: the instructions must do what you want, and the layout must also look like the picture. A few habits help.
- Use unreachable cells as filler. Any region the pointer never enters can hold whatever characters you like. This is the most flexible way to fill out a silhouette.
- Let the path draw the outline. If you choose the route first, such as a long winding corridor, the picture can be the corridor itself.
- Use no-operation characters. In many grid languages a space does nothing, so you can pad lines with spaces freely, as long as the pointer is not in string mode when it crosses them. In string mode, spaces may be pushed onto the stack as data.
- Mind the edges. On a wrapping grid, the pointer leaving one side re-enters on the other. A picture that ignores this can accidentally create loops through the margins.
Self-describing shapes
Some programmers aim higher: a program whose shape is meaningful to its function. Examples people try include a counter laid out as a spiral, a loop drawn as a ring, or a program that prints a picture of its own outline. These are puzzles in the same family as palindromic programs and polyglot programs, where a layout constraint is imposed on top of the ordinary goal of working code.
Such goals tend to be hard in the beginning and easier with a clear plan. Write the logic first in a plain layout, trace it by hand to confirm it works, and only then try to rearrange it. Keep a copy of the working version in case reshaping breaks something.
Is the shape worth it?
A shaped program is usually not the best way to learn a language. A plain layout is easier to debug. But shaping teaches useful things:
- It makes you notice exactly which cells the pointer visits.
- It forces you to learn what each blank cell does.
- It gives a reason to read the language rules closely, particularly around string mode and wrapping.
If you try it, keep expectations modest. A simple frame or a diagonal run is a good start. Elaborate pictures often take far more time than the program itself, and because a single misplaced character can change the route, it pays to test after every change.
A note on what counts as art
There is no formal definition. A reader may count a neatly boxed program as art, while another may only count a recognizable image. It is fine for the term to stay loose: the interesting part is that for some languages the picture and the program can be one and the same thing, and for others they can only ever be neighbors.