Tips for golfing in Charcoal

15

2

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.

Okx

Posted 2017-04-21T19:06:08.977

Reputation: 15 025

Screw it, I'll go try to do a charcoal answer on something, brb. Maybe I can give minor pointers on this thread. – Magic Octopus Urn – 2017-04-21T19:23:23.970

Answers

5

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.

ASCII-only

Posted 2017-04-21T19:06:08.977

Reputation: 4 687

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. – Neil – 2017-06-03T09:32:41.223

3

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.

ASCII-only

Posted 2017-04-21T19:06:08.977

Reputation: 4 687

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. – Neil – 2017-06-05T10:23:16.343

Hmm the limitation is kinda intentional, but I guess I should change it because it wouldn't hurt anyway – ASCII-only – 2017-06-05T10:25:29.997

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. – Neil – 2017-06-11T00:17:02.460

@Neil lol oops, I'll try to fix that ASAP (I think that's a coincidence too haha) – ASCII-only – 2017-06-11T00:18:02.417

3

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.

Neil

Posted 2017-04-21T19:06:08.977

Reputation: 95 035

These should be fixed on TIO now, it would be nice if you could verify that it works – ASCII-only – 2017-06-05T00:19:36.273

@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.) – Neil – 2017-06-05T08:02:55.347

Yeah they're the other way around – ASCII-only – 2017-06-05T10:07:35.663

3

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.

Neil

Posted 2017-04-21T19:06:08.977

Reputation: 95 035

3

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.

Neil

Posted 2017-04-21T19:06:08.977

Reputation: 95 035

Question: What does Transform mean? – CalculatorFeline – 2017-06-18T22:29:45.337

@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. – Neil – 2017-06-18T23:34:11.443

Can't think of anything better, so you should just explain how Transform works in the answer somewhere. – CalculatorFeline – 2017-06-18T23:35:42.827

@CalculatorFeline I've added a key to the table, just in case the other columns weren't clear either. – Neil – 2017-06-19T00:58:01.370

The bugs should be fixed now. Also thank you so much for taking the time to write this explanation! – ASCII-only – 2017-07-27T00:39:44.933

You might want to mention that the cursor will (well at least it should) end up on the same position on the final copy as before the transformation. – ASCII-only – 2017-07-27T00:42:19.123

@ASCII-only Indeed, it does for reflections and basic rotations but for the rotations that keep a copy the cursor doesn't quite end up at the same place. (I think I have at least one answer that depends on the buggy behaviour...) – Neil – 2017-07-27T09:55:17.533

@ASCII-only Also while testing I found that diagonal reflections with an overlap of more than 1 don't work properly. – Neil – 2017-07-27T10:05:56.853

If you mean they move vertically that's intended behaviour, I guess that isn't exactly useful (btw can you accept/decline the GitHub invite pls) – ASCII-only – 2017-07-27T22:57:57.523

@ASCII-only Why would you want ReflectOverlap(:DownRight, 2); to overlap 1 horizontally and 2 vertically? – Neil – 2017-07-27T23:17:05.377

Let us continue this discussion in chat.

– ASCII-only – 2017-07-27T23:27:28.943

2

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.

ASCII-only

Posted 2017-04-21T19:06:08.977

Reputation: 4 687

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));.) – Neil – 2017-06-11T00:19:07.607

@Neil I don't think so, will change ASAP – ASCII-only – 2017-06-11T00:19:54.400

2

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)

ASCII-only

Posted 2017-04-21T19:06:08.977

Reputation: 4 687

1You have to watch out for ambiguities though because the previous command may accept an optional parameter. – Neil – 2017-06-04T16:12:52.593

0

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.

ASCII-only

Posted 2017-04-21T19:06:08.977

Reputation: 4 687

0

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.

Neil

Posted 2017-04-21T19:06:08.977

Reputation: 95 035

0

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.

Neil

Posted 2017-04-21T19:06:08.977

Reputation: 95 035