Bouncy

From Esolang
Jump to navigation Jump to search

Bouncy is a 2D esoteric programming language centered around controlling how the instruction pointer interacts with walls. The Bouncy programming language and its official interpreter are designed by User:Mercerenies.

Overview

A Bouncy program functions as a two-dimensional grid. Every Bouncy program must contain exactly one $ dollar sign character, which indicates the starting point of the program. The instruction pointer starts at the dollar sign and is initially facing east. The instruction pointer is eight-directional and thus can be moving either horizontally, vertically, or diagonally. Bouncy code is terminated by an @ at sign. When the interpreter encounters an at sign, the program stops immediately.

Bouncy source code wraps on all four sides, so if the instruction pointer ever drops off the eastern side of the grid, it reappears on the west, and the same is true for the other three sides. That is, Bouncy source code can be treated as taking place on a torus.

Bouncy is a register-based language. A Bouncy program has access to

  • Two registers, called the primary register (PR) and the secondary register (SR). Both are initialized to zero.
  • A current mode. Every Bouncy program starts out in BOUNCE mode. This affects how the instruction pointer reacts to reflection instructions and also changes which array is active.
  • A collection of four arrays, one for each of the four modes. Each array is infinite in both directions, can be indexed by arbitrary integers, and starts out as an infinite tape of zeroes.
  • A memory pointer (MP), indicating where the interpreter is currently looking in the array. This value starts at zero.

The active array is the array for the current mode. Only the active array is accessible at a given moment, and MP always points to a position in the active array.

The only datatype in Bouncy is the integer. All registers and array values always contain integers, never strings or floating-point values.

Commands

No-ops

  • $ - The starting point of the program. If the instruction pointer ends up here at some future point, this instruction is a no-op.
  • . - No-op
  •   (Space) - No-op

Spaces and dots can be used interchangeably as no-op instructions and simply allow the interpreter to continue in the current direction. It is recommended, but not required, to use dots to indicate the intended flow of control through the program.

Control Flow

  • @ - The ending point of the program. If the instruction pointer encounters this command, the program is terminated immediately. Unlike $, it is not required that this command be unique. There can be zero or more @ commands in a Bouncy program.
  • # - Changes the mode of the program to the current mode plus PR, modulo 4 if necessary.
  • \ - Reflection command, see Modes and Reflections.
  • / - Reflection command, see Modes and Reflections.
  • _ - Reflection command, see Modes and Reflections.
  • | - Reflection command, see Modes and Reflections.

Memory

  • 0 to 9 - Sets PR to the given constant.
  • T - Sets PR to ten.
  • S - Stores PR in the active array, at index MP.
  • L - Loads the value at index MP from the active array into PR.
  • ( - Sets MP to (MP - PR).
  • ) - Sets MP to (MP + PR).
  • " - Swaps the values of PR and SR.

Arithmetic

  • + - Sets PR to PR plus the value pointed to by MP.
  • - - Sets PR to PR minus the value pointed to by MP.
  • * - Sets PR to PR times the value pointed to by MP.
  • % - Sets PR to PR divided by the value pointed to by MP, rounding down.
  • m - Sets PR to PR modulo the value pointed to by MP.
  • n - Sets PR to (- PR), the arithmetic inverse.
  • ~ - Logical negation; Sets PR to 1 if PR is 0, or 0 otherwise.
  • & - Bitwise AND; Sets PR to PR bitwise-and the value pointed to by MP.
  • ; - Bitwise OR; Sets PR to PR bitwise-or the value pointed to by MP.
  • ^ - Bitwise XOR; Sets PR to PR bitwisr-xor the value pointed to by MP.

Comparison

  • < - Sets PR to 1 if PR is less than the value pointed to by MP, or 0 otherwise.
  • = - Sets PR to 1 if PR is equal to the value pointed to by MP, or 0 otherwise.
  • > - Sets PR to 1 if PR is greater than the value pointed to by MP, or 0 otherwise.

Input / Output

  • p - Outputs the integer value PR.
  • P - Outputs the character whose ASCII value is PR.
  • i - Reads an integer value from STDIN and stores it in PR.
  • I - Reads a single character from STDIN and stores its ASCII value in PR.

Modes and Reflections

The central mechanism of control flow in Bouncy is reflections. A Bouncy instruction pointer is always in one of four modes: BOUNCE (0), GHOST (1), ZAP (2), or FLOW (3). The only way to change the current mode is with the # instruction, which adds PR to the mode (modulo 4). For instance, if the current mode is ZAP (2) and PR is 1, then after a # command, the new mode would be FLOW (3). If, instead, the current mode was ZAP (2) and PR was 3, then after a # command, the new mode would be GHOST (1), since 1 is equal to 2 + 3 (modulo 4).

The current mode determines two things. First, it determines the active array. Each mode has its own infinite array, which is initially set to all zeroes, and only the array corresponding to the current mode can be accessed. Second, the mode determines the behavior of the four reflection commands: \, /, _, and |.

Mode 0 - BOUNCE

Befitting the name, this is the mode every Bouncy program starts in. In BOUNCE mode, reflection commands are treated as walls, and the instruction pointer bounces off of them. For instance, if the instruction pointer hits a \ command while moving east, it will "reflect" off the command and start moving south.

Reflection commands hit at a parallel angle will be ignored, so for example if the instruction pointer is moving northeast and encounters a /, it will continue in the current direction.

Mode 1 - GHOST

The simplest mode. In GHOST mode, all reflection commands are no-ops. That is, the instruction pointer will never change direction while in this mode and will simply skip over any reflection commands.

Mode 2 - ZAP

In ZAP mode, the instruction pointer will always leave a reflection command in a direction parallel to the command. So, for instance, the instruction pointer will always leave a | command facing north or south. Given the choice between the two, the instruction pointer will try to continue in as close a direction to its current direction as possible. So to continue with the | example, if the instruction pointer is facing south, southeast, or southwest, it will proceed southward. If it is facing north, northeast, or northwest, it will proceed northward.

In case of a tie, the instruction pointer will favor turning right relative to its current direction. So in the | example, if the instruction pointer is facing east, it will prefer to start moving south, while if the instruction pointer is facing west, it will prefer to start moving north.

Mode 3 - FLOW

FLOW mode is very similar to ZAP mode except that the instruction pointer will always leave a reflection command facing perpendicular to the command, rather than parallel. So a | in FLOW mode behaves identically to a _ in ZAP mode, and vice versa. The same is true for / and \.


Example Programs

Infinite Loop

$

The shortest possible Bouncy program. Since Bouncy programs only terminate when an @ is encountered and this program contains no such symbol, it will run forever.

Cat

$IP

Only slightly more interesting is the program which prints its input. Remember that Bouncy programs wrap, so this program infinitely reads one character from STDIN and prints it out.

Hello World

         @$..3#...8S9*P..TS*S1+P..8+PP/
        .                              S
       .                                .
      P                                  3
     *                                    +
    3                                      P
   S                                        4
  +                                          S
 1                                            \
\                                            8
 P                                          *
  *                                        P
   S                                      4
    T                                    S
     P                                  3
      *                                *
       9                              S
        S                            7
         /+2STP+3S.P+1S+TS*ST..P+3S*|

The string "Hello World!", printed out using FLOW mode.

Truth Machine

_......|
.      .
.  $i\ .
.    # 1
. @p0/ p
.    # .
|...._._

A truth machine. This program takes input from the user. If the input is zero, it prints zero and terminates. If the input is one, it prints one repeatedly forever. This is our first nontrivial example of control flow.

Remember that a Bouncy program starts at the $ facing east in BOUNCE (0) mode. First, we take input. Then we bounce off the \ command and hit the # below it. If the input is zero, then # does nothing, and we're still in BOUNCE (0) mode, so we print zero and terminate. However, if the input is one, then # switches our mode to GHOST (1), meaning we ignore the / and proceed south. In that case, we run # again, which moves us to ZAP (2) mode and allows us to enter our infinite loop, which encompasses the outer perimeter of our program.

Factorial

$2#..1S))S(.iS0=.|  /=0S"("-_
                 # #         "
                 ..           )
          @pL(1#3|             \
                 L             "
                 \             |
                  "           L
                   1         "
                    _("*S1_)/

No examples section would be complete without it. This program takes an integer from the user and prints its factorial to STDOUT.

In this case, we actually need to store some numbers in our data arrays to keep track of the running factorial. Remember that we have four arrays, so we have to decide which mode we'll do our computation in. In this case, we stay in ZAP (2) mode for most of the program and switch to FLOW (3) mode when we want to exit our loop. So we use the ZAP (2) array for storage. We store the running factorial total in ZAP[0] and the iteration variable in ZAP[1]. Finally, we store the constant 1 in ZAP[2], since we'll need it in order to decrement values.

The initial segment of the program immediately sets the mode to ZAP (2) and then initializes the ZAP array, taking input from the user along the way. THen we check if the user input is equal to zero and modify the mode accordingly. When we hit the topmost |, we'll always still be in ZAP (2) mode, so we'll unconditionally move south, but when we hit the lower |, we might be in ZAP (2) or FLOW (3) mode. If we're in FLOW (3) mode, we head west, print out the result, and exit. If not, we go around in a bit octagon, multiply the current value into the running total, and decrement the loop iteration value. Finally, we run our "equal to zero" condition again and shift modes. This loop will run as long as we remain in ZAP (2) mode and will terminate whenever we hit FLOW (3) mode due to the condition being true.

Implementation

The official implementation of Bouncy is written in Ruby and is available on GitHub.