17
\$\begingroup\$

Charcoal is a language created by ASCII-only and DLosc that specialises in ASCII art challenges.

What tips do you have for golfing in Charcoal? Obviously I am looking for tips related specifically to Charcoal, not those which can be applied to most - if not all - languages.

\$\endgroup\$
1
  • \$\begingroup\$ Screw it, I'll go try to do a charcoal answer on something, brb. Maybe I can give minor pointers on this thread. \$\endgroup\$ Commented Apr 21, 2017 at 19:23

12 Answers 12

6
\$\begingroup\$

Take advantage of the deverbosifier

Using the deverbosifier means you can write in ASCII (--dv or --deverbosify to deverbosify, -v or --verbose to execute as verbose code). Furthermore, it compresses strings for you, which can be useful in some ASCII-art challenges when the string to compress is too long.

@Neil recommends using -vl or -v --sl. This is short for --verbose --showlength, meaning it will be interpreted as verbose Charcoal, and the length of the normal Charcoal code will be shown. Also, when deverbosifying, check the output to make sure that the input has actually been parsed correctly, since Charcoal generally ignores parse errors. If there is a syntax error, use -a (--astify) or --oa (--onlyastify) to help figure out the problem.

\$\endgroup\$
1
  • \$\begingroup\$ Further to this, I recommend using -v -sl. Also, when deverbosifying, check the output to make sure that the input actually got parsed correctly, as you don't always get a parse error. \$\endgroup\$
    – Neil
    Commented Jun 3, 2017 at 9:32
4
\$\begingroup\$

Learn your reflections and rotations

There are a lot of variations of the basic reflection and rotation, so it pays to know what the subtle differences are. Key to tables:

  • Command: Name of the command in Verbose mode.
  • Transform: Whether Charcoal should attempt to flip or rotate the characters as it mirrors or rotates them. For instance, a / might become \ after a rotate or flip.
  • Keep Original: Whether Charcoal should merge the result with the original canvas.
  • Overlap: (Only applies when Keep Original is Yes.) Determines the position of the axis of reflection/rotation, in half characters from the border. In the case of reflections, equivalent to the number of rows/columns that are not affected and end up in the middle of the result. In the case of rotations, the rotated copy is allowed to overwrite clear areas (but not spaces) in the original.

Reflections

|         Command         | Transform | Keep Original | Overlap |
|-------------------------|-----------|---------------|---------|
| Reflect                 | No        | No            | n/a     |
| ReflectCopy             | No        | Yes           | 0       |
| ReflectOverlap          | No        | Yes           | 1       |
| ReflectOverlapOverlap   | No        | Yes           | n       |
| ReflectTransform        | Yes       | No            | n/a     |
| ReflectMirror           | Yes       | Yes           | 0       |
| ReflectButterfly        | Yes       | Yes           | 1       |
| ReflectButterflyOverlap | Yes       | Yes           | n       |

The direction of reflection is optional. The default is to reflect once to the right. For those reflections that keep the original, a multiple direction is allowed, which simply repeats the command for each direction. (This means that for instance ReflectMirror(:¬) will actually create four copies in total.)

The cursor is moved along with the reflection (even when the original is kept).

Rotations

|         Command         | Transform | Keep Original | Overlap |
|-------------------------|-----------|---------------|---------|
| Rotate                  | No        | No            | n/a     |
| RotateCopy              | No        | Yes           | 0       |
| RotateOverlap           | No        | Yes           | 1       |
| RotateOverlapOverlap    | No        | Yes           | n       |
| RotateTransform         | Yes       | No            | n/a     |
| RotatePrism             | Yes       | Yes           | 0       |
| RotateShutter           | Yes       | Yes           | 1       |
| RotateShutterOverlap    | Yes       | Yes           | n       |

For those rotations that keep the original, there is an optional origin of rotation. The default is the bottom right of the canvas. Allowable values are any of diagonal directions.

The amount of rotation (in 45° increments) is optional. The default is 2, i.e. 90° anticlockwise (counterclockwise). For those rotations that keep the original, there are two alternative options: a multidigit integer specifies to rotate the canvas once for each digit and then merge the results, while an integer list simply repeats the command for each rotation, with variable results depending on how the canvas changes in between.

\$\endgroup\$
16
  • \$\begingroup\$ Question: What does Transform mean? \$\endgroup\$ Commented Jun 18, 2017 at 22:29
  • \$\begingroup\$ @CalculatorFeline Good question. The cases where Transform is no are simply a character-for-character copy. For instance, a standard reflection of "4>2" is just the characters in reverse order, i.e. "2>4". This isn't always desirable, so Transform attempts to switch characters in the most appropriate manner, thus the reflection of "4>2" would become "2<4". Transform may not be the best description of this, so feel free to suggest something better. \$\endgroup\$
    – Neil
    Commented Jun 18, 2017 at 23:34
  • \$\begingroup\$ Can't think of anything better, so you should just explain how Transform works in the answer somewhere. \$\endgroup\$ Commented Jun 18, 2017 at 23:35
  • \$\begingroup\$ @CalculatorFeline I've added a key to the table, just in case the other columns weren't clear either. \$\endgroup\$
    – Neil
    Commented Jun 19, 2017 at 0:58
  • 1
    \$\begingroup\$ @jixperson Well, you can immediately save a byte by using Oblong for the other lines. Best I could do offhand is Oblong(5, 4, "/"); ReflectMirror(:Down, :Right); ReflectMirror(:Down, :Right); ReflectMirror(); ReflectButterflyOverlap(20); ReflectButterflyOverlap(:Down, 8);. \$\endgroup\$
    – Neil
    Commented Mar 4, 2022 at 15:48
3
\$\begingroup\$

Use overloads

For example, many commands only need one argument:

  • Rectangle, Oblong and Box make a square if only one argument is given
  • Reflect commands default to reflecting right
  • Rotate commands default to 90 degrees counterclockwise
  • Polygon and PolygonHollow can accept a multidirectional and a side length. This can be used if all of the sides are the same length.
\$\endgroup\$
4
  • \$\begingroup\$ I discovered that fact about PolygonHollow by accident. You can even have several multidirectionals, but they have to come before normal arrows (I don't know whether this limitation is intentional). I used that in my answer to the "Draw a cube" challenge. \$\endgroup\$
    – Neil
    Commented Jun 5, 2017 at 10:23
  • \$\begingroup\$ Hmm the limitation is kinda intentional, but I guess I should change it because it wouldn't hurt anyway \$\endgroup\$
    – ASCII-only
    Commented Jun 5, 2017 at 10:25
  • \$\begingroup\$ I see you fixed Polygon to accept arrows and multidirectionals in any order, thanks! While I'm here, I was expecting ReflectButterfly dls to call ReflectButterfly for each direction, but (as the wiki correctly documents) it actually calls ReflectOverlap. \$\endgroup\$
    – Neil
    Commented Jun 11, 2017 at 0:17
  • \$\begingroup\$ @Neil lol oops, I'll try to fix that ASAP (I think that's a coincidence too haha) \$\endgroup\$
    – ASCII-only
    Commented Jun 11, 2017 at 0:18
3
\$\begingroup\$

Avoid consecutive constants of the same type

For example, Plus(Times(i, 2), 1) translates as ⁺×鲦¹, but you can save a byte by switching the parameters: Plus(1, Times(i, 2)) translates as ⁺¹×ι² and Plus(Times(2, i), 1) as ⁺×²ι¹ both of which save a byte. Plus(1, Times(2, i)) (which translates as ⁺¹×²ι) would be even better if there was another numeric constant following it.

\$\endgroup\$
3
\$\begingroup\$

Use the predefined variables

Here is a list of all the variables that can be used, giving the succinct greek letter and the verbose letter that represents it.

α/a: The uppercase alphabet
β/b: The lowercase alphabet
γ/g: A string of all the ASCII characters from space to ~
δ/d: The fifth input
ε/e: The fourth input
ζ/z: The third input
η/h: The second input
θ/q: The first input
υ/u: An empty array
φ/f: 1000
χ/c: 10
ψ/y: The null character
ω/w: The empty string

The variables representing inputs will be empty if not enough input exists, but all other variables not shown here must be assigned before use.

\$\endgroup\$
3
  • \$\begingroup\$ These should be fixed on TIO now, it would be nice if you could verify that it works \$\endgroup\$
    – ASCII-only
    Commented Jun 5, 2017 at 0:19
  • \$\begingroup\$ @ASCII-only Can you confirm that y and f are the other way around from what I've pasted? (I may have misread the greek letters when I originally wrote this.) \$\endgroup\$
    – Neil
    Commented Jun 5, 2017 at 8:02
  • \$\begingroup\$ Yeah they're the other way around \$\endgroup\$
    – ASCII-only
    Commented Jun 5, 2017 at 10:07
2
\$\begingroup\$

Use commands without a command character

An expression that is not part of a command is printed.
If it is preceded by a direction, the expression is printed in the specified direction.
Numbers are printed as a line with the specified length using a character selected from \/-|.

If a direction is not followed by an expression, it is counted as a move one space in the specified direction.

Note: This may sometimes be counted as part of the previous command so the command character may actually be required. (thanks Neil for reminding me)

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You have to watch out for ambiguities though because the previous command may accept an optional parameter. \$\endgroup\$
    – Neil
    Commented Jun 4, 2017 at 16:12
2
\$\begingroup\$

Use multidirectionals

Some commands can accept multidirectionals: +X*|-\/<>^KLTVY7¬⌊⌈. What they expand to are here. In general, the direction list starts from up and continues clockwise.

\$\endgroup\$
2
  • \$\begingroup\$ Any particular reason why some commands accept multidirectionals but others require a direction list? (There might be a good reason for this that I'm overlooking, but I was trying to do ReflectButtterflyOverlap(:¬, Modulo(g, 2));.) \$\endgroup\$
    – Neil
    Commented Jun 11, 2017 at 0:19
  • \$\begingroup\$ @Neil I don't think so, will change ASAP \$\endgroup\$
    – ASCII-only
    Commented Jun 11, 2017 at 0:19
1
\$\begingroup\$

Make full use of Sum

Sum has lots of handy overloads:

  • On a list of strings, concatenates them together. However, if it's possible that the list might be empty, this would give None, so in this case, use Join(..., "") instead.
  • On a non-empty list of numbers, simply takes their sum.
  • On a non-empty list of lists, concatenates them together (flattens to depth 1).
  • On an integer, takes the sum of its digits. (Floating-point values are truncated to integer. If you want the sum of the decimal places, cast to string first.)
  • On a string containing only digits and optionally one . character, takes the sum of the digits.
  • On a string containing numbers and separators, takes the sum of the numbers cast to int or float (but note that - counts as a separator).

A convenient side-effect of the last two rules is that if you use Sum on a character then the digits 1-9 get cast to their value and everything else returns zero, unlike Cast, which fails for non-digit values.

\$\endgroup\$
1
  • \$\begingroup\$ Note that while these all work on the version of Charcoal on ATO, the version currently on TIO is buggy and won't Sum a list of lists. \$\endgroup\$
    – Neil
    Commented Dec 17, 2022 at 19:34
1
\$\begingroup\$

Use Filter to slice the first character from an array or string

Even if you're lucky, using Slice to slice the first character from a string takes 2 bytes: Slice(..., 1). It will take longer if the expression to be sliced ends in a number, requiring a separator, or if the following code can be interpreted as an expression, as in that case Slice will want to consume it as an additional parameter.

Instead, just use Filter(..., k), which drops the first element, thus achieving the desired result. (Obviously use the appropriate loop index variable if your expression is nested inside another loop.) This is always 2 bytes and cannot be affected by surrounding code.

\$\endgroup\$
1
  • \$\begingroup\$ Additionally, if you want the first element of a string, then CycleChop(..., 1) will fail if the string is empty, so for the condition of a while loop you would consider Slice(..., 0, 1, 1), but Filter(..., Not(k)) will also return an empty string in that case. \$\endgroup\$
    – Neil
    Commented Jan 20, 2023 at 19:58
1
\$\begingroup\$

Use switch instead of if (Equals(...)) where possible

The construct

if (Equals(q, h)) ...
else ...

takes 4 bytes but written as a switch it only takes 3 bytes:

switch (q) {
case h: ...
default: ...
}

However this only works if the else portion can be unambiguously parsed as a command rather than an expression. In particular:

  • If the else portion is empty, then this only works at the end of a block or program, but that was true for the original if statement.
  • If the else portion contains multiple commands, then this works as an if but not as a switch.
  • If the else portion prints an expression, then this only works as a switch at the end of a block or program.
\$\endgroup\$
1
\$\begingroup\$

Some useful uses of Base

Base(array, 1) is like Sum(array) for a numeric array, but it returns 0 when the array is empty, while Sum returns None, which is usually inconvenient to process, unless you're using it in a Boolean context.

Base(array, 0) returns the last element of a numeric array (or 0 if the array is empty) without modifying the array.

Base([x, y], Negate(1)) returns y-x. This is useful to compare the elements of a two-element array for equality (typically calculated from a Map).

Base(array, number) also works to substitute a number in a polynomial.

In all the above cases the two parameters can be exchanged if it is inconvenient to have the number second (i.e. if it would require two consecutive numbers to be separated).

\$\endgroup\$
0
\$\begingroup\$

Use split for a string array, split and cast for a number array

Split for a string array is only three characters of overhead, and split and cast is only four characters of overhead. Compare this to writing the array literally, which would require array start and end, and a separator between every array element.

If your number array only has numbers less than 95, use either a cast (if all numbers are less than 10) or index into one of the predefined variables.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.