DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

100 Languages Speedrun: Episode 60: Asciidots

Asciidots is an esoteric 2D language. The most obvious question is how it compares to the classic 2D language Befunge.

  • Befunge has access to stack; Asciidots has nothing - dots just carry two numbers (value and id)
  • Befunge has one thing moving around the grid; Asciidots has a lot of dots moving around the grid
  • Befunge calculations involve values on top of the stack; Asciidots calculations involve multiple dots interacting
  • Asciidots has much higher level commands than Befunge
  • Asciidots supports libraries, which are sort of like small 2D boards you can plug into your program

As a result of these changes, Asciidots is more pure 2D than Befunge, but also lets you achieve a lot more complex programs. Anyway, let's get started!

Hello, World!

.---$"Hello, World!"---&
Enter fullscreen mode Exit fullscreen mode

The dot starts at ., it follows the line it's at, $"..." is "print with newline" command, and & ends the dot. We don't actually need the & here, as just letting the final dot die by hitting a dead end would also end it.

$ asciidots hello.dots
Hello, World!
Enter fullscreen mode Exit fullscreen mode

Math

Here's a program that adds some numbers:

    /-#30--{+}---$#---&
.---*       |
    \-#39---/
Enter fullscreen mode Exit fullscreen mode
$ asciidots math.dots
69
Enter fullscreen mode Exit fullscreen mode

The dot can start anywhere, not just on top left.

Here * splits the dot in two. Every dot carries two numbers (value # and id @). Top dot has its value set to 30, bottom one to 39. When they meet at {+}, their values are added, and the result is sent in horizontal direction ([+] would be vertical direction). Finally & ends the program.

Loop

Here's a program that prints a number from 1 to 10:

       /-#1-{+}-$#---\
.-#0->-*     |       |
     | \-----/       |
     |        &      |
     \--------~----*-/
              |    |
              |  /-*-\
              |  |   #
              |  |   1
              |  |   0
              \-{=}--/
Enter fullscreen mode Exit fullscreen mode
$ asciidots loop.dots
1
2
3
4
5
6
7
8
9
10
Enter fullscreen mode Exit fullscreen mode

There's a lot of dots going around here.

  • The main dot comes from the left and has its value # set to 0
  • it reaches > which redirects dots coming from all directions to the right - but it's already going to the right
  • it is split between top dot and bottom dot
  • top dot has its value # set to 1
  • bottom dot gets added to top dot with {+}, now just one dot continues the loop
  • dot reaches $# and prints its numeric value (with the newline)
  • dot follows the loop clockwise until it gets split with * - main dot continuing the loop, its copy going down
  • the dot that went down gets split, left dot has the same value, right dot gets its value # set to 10
  • those two dots are compared with {=} - if they're equal (so dot # value is equal to 10), the resulting dot has value # of 1, otherwise it has value of 0 - there are no special booleans, just integers
  • ~ operator does route control - the main dot arriving from the right gets either allowed to continue towards the left (if bottom arriving dot is 0), or it gets sent up (if bottom arriving dot is 1)
  • if it gets sent up it hits & and ends the program - this will happen once the dot's value equals 10
  • if it continues, it eventually hits > which makes it goes right to the incrementer

This might sound really complicated at first, but notice how elegant all of this is - it's just dots, each carrying two numbers (and we didn't even do anything with ID number).

Fibonacci

Here's a program that prints infinite Fibonacci numbers. Values in dots have unlimited precision:

         /-$#
         |
.-#1--->-*-\
       |   |
.-#1->-*--{+}-\
     |        |
     \--------/
Enter fullscreen mode Exit fullscreen mode
$ asciidots fib.dots | head -n 30
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
Enter fullscreen mode Exit fullscreen mode

There are many interesting things going on here:

  • we start with two dots, representing first two numbers in the Fibonacci sequence A and B
  • top dot A gets split with *, one of them gets sent to be printed with $#
  • the other dot A top gets sent to the adder {+}
  • bottom dot B gets split two, one of them gets sent to replace the top top, with redirection through > (A = B)
  • the other bottom dot B gets sent to the adder, to replace itself (B = A + B)
  • it looks forever

By the way I'm not trying to make these programs super compact. There's a lot of extra wires to make everything more readable.

Fizz

Asciidots has {%} modulo operator, so we can do the Fizz the usual way, but it's more fun to do it a different way:

Counter
     /---------\    /-$"Fizz"
.-#0->-*---{+}-*----~-$#
       \-#1-/       |
                    |
Fizz                |
       >->---->-----/
.->-#0-*-*-#1-*
  \-----------/
Enter fullscreen mode Exit fullscreen mode
$ asciidots fizz.dots | head -n 20
1
2
Fizz
4
5
Fizz
7
8
Fizz
10
11
Fizz
13
14
Fizz
16
17
Fizz
19
20
Enter fullscreen mode Exit fullscreen mode
  • The part labeled "Counter" generates nubmbers 1, 2, 3, 4, 5, 6, ... in infinite sequence
  • The part labeled "Fizz" generates numbers 0, 0, 1, 0, 0, 1, ... in infinite sequence
  • The top right part decides what to print based on the Fizz input - it either sends the counter up to print "Fizz", or right to print its value

FizzBuzz Problem

I tried to extend that counter sending solution to FizzBuzz, but that doesn't quite work.

I think it's interesting to explain why.

First we can start with the Fizz arrangement - a dot goes either one way (which will be Fizz or FizzBuzz), or the other way (which will be Buzz or number).

Then we have a 0,0,0,0,1 dot generator that sends a signal. But where does it send it? We could copy it with * and send it to both. At first it will be fine, but the Buzz-dots which didn't get a signal are stuck now and everything goes out of sync. There are ways to make everything synchronized, but honestly it's so convoluted that it's not even worth it.

On the "Buzz or number" branch:

  • counter 1 and Buzz 0 arrive, prints counter
  • counter 2 and Buzz 0 arrive, prints counter
  • counter 3 does not arrive, Buzz 0 arrives and waits
  • counter 4 arrives and with previous Buzz 0 prints counter; new Buzz 0 arrives and waits
  • counter 5 arrives and with previous Buzz 0 prints counter (BUG!), new Buzz 1 arrives and waits
  • counter 6 does not arrive, Buzz 0 arrives and waits
  • counter 7 arrives arrives, with previous Buzz 1 prints Fizz, everything derails more and more

Loop Revisited

I'll get to the FizzBuzz, but let's revisit the loop. Originally we split the dot, assigned constant value to one of them, and then performed some operation. But really we just want infinite supply of same-numbered dots.

    .-#1-(*)
          |
.-#0->---{+}-$#-\
     |  &       |
     \--~-----*-/
        |     |
        \-{=}-/
           |
    .-#10-(*)
Enter fullscreen mode Exit fullscreen mode

This also prints numbers from 1 to 10. I think this is conceptually simpler. ( and ) are mirrors, so once dot gets between them, it will just bouncing forever.

We create a dot on the top, set its value # to 1, then make it bounce between mirrors. Every time it flies past, it splits on *, sending one dot over. That's infinite supply of dots with value # of 1.

There's same arrangement on the bottom, supplying infinite dots with value # of 10.

Meanwhile the main dot goes around in a loop. For being incremented by 1 we don't need to do anything fancy, we just take one of the infinite 1 dots. For the comparison with 10 we still need to duplicate the main dot to {=} check it, but it's much simpler code.

By the way while many of the operators in Asciidots have all multiple directional variants or just work in whichever direction (like -, >, {+}, *, / etc.), quite a few like mirrors (( )) and control flow ~ need to be oriented in a specific way. This usually isn't a problem, as turning things around or crossing wires with + is very easy.

FizzBuzz

Here's the FizzBuzz:

                           /-$"Fizz"
                    /------~-$"FizzBuzz"
                /---*--{%}-/
                | .-#5-(*)
                |          /-$#
        /-----\ |   /------~-$"Buzz"
  .-#0-->-{+}-*-~---*--{%}-/
     .-#1-(*)   | .-#5-(*)
                |
       >->---->-/
.->-#0-*-*-#1-*
  \-----------/
Enter fullscreen mode Exit fullscreen mode

It starts like the Fizz program, with counter generator on the left, and Fizz generator (0, 0, 1 in a loop) on the bottom left. Then it goes to either "Fizz or FizzBuzz" section or to "Buzz or Number" section - both are pretty much identical.

In each of these two sections, we generate infinite 5 dots, and take counter modulo 5. If it's zero, we go one branch, if it's not, we go the other.

Should you try Asciidots?

Asciidots is much less know than Befunge or Brainfuck, but I think it's a great esoteric language.

It's extremely elegant, and it's crazy powerful. For example it's possible to write a collections library in Asciidots! Array elements could be represented by a bunch of dots traveling in a loop, with ID @ indicating their position, and value # indicating their value, and some interaction ports would let you read numbers, write numbers, or do other fun things.

The whole language is a great puzzle, and I definitely recommend it.

One downside is that it's really slow, even by the standards of esoteric languages, so hopefully someone creates a more optimized implementation.

Code

All code examples for the series will be in this repository.

Code for the Asciidots episode is available here.

Top comments (0)