Perpetuum Mobile
Perpetuum Mobile: Rube Goldberg style esolang
Perpetuum Mobile is an esolang developed by User:Vorpal in late may of 2018. This is currently an early draft version.
Introduction
It is a 2D language with objects moving around. "Turn based" like an automaton.
Objects are placed on an infinite square-tiled "playing field".
Interacting objects:
- Electrical wires
- Particles (only exists at runtime, do not mutate the playing field)
- Moves one square per turn.
- Multiple particles can occupy the same square, but it will not be possible to separate them ever in that case. It will however be possible to detect (2 particles generating 2 V instead of 1 V in a detector)
- Particles can only move in the cardinal directions
- Strings (mutates the playing field)
The program terminates when there has been no particles moving, no electrical pulses or moving strings during a turn.
Particles going off towards infinity are discarded when the implementation determines that will never able to interact with anything again. The details of when it determines that is implementation defined, but it has happen within a finite number of turns. An obvious implementation would be when the particle crosses a bounding box containing the program.
Particles are stopped by objects by default, unless the description of the object says otherwise (but see the optional high energy extension below as an alternative).
Signals propagate along wires and movements along strings in one turn, regardless of the length of said wire or string.
File format
File format is UTF-8 encoded text with LF line endings.
Leading lines starting with # are ignored (can be used for comments) except if they start with #@ in which case they contain directives. After the last line starting with #, the rest of the file is entirely source code.
Unknown codepoints are ignored (can be used for comments) but it is an error that must be diagnosed if anything happens to interact with them.
The currently only recognized directive is exts, listing exactly which extensions must be enabled. For example:
#!/usr/bin/env perpetuum_mobile #@ exts: high-energy, io
Unknown directives that do not start with _ are an error. Unknown directives starting with _ are ignored.
Playing field elements
| and - are electrical wires Note: | only connects at the top and bottom and - only connects left and right. This means that |+ does not connect for example. + is used when turning a corner or splitting wires and connects in all cardinal directions. ( is used for wire crossings without connections. It will also allow particles and strings to pass under it horizontally. ( itself can thus only be moved by a string vertically (which would also be pretty useless since it would be impossible to attach anything a wire to both ends if one end is taken up by a string). v, ^, >, and < are diodes, preventing back propagation across wires, v only propagating down and ^ only up and so on ~ is a horizontal string that can be pulled, moving the items attached to the end S is a vertical string ¤ is an motor/generator that actives adjacent activatables one step per received V. It also acts as a generator when activated (generating 1 V per moved step) M alias for ¤ in case your keyboard can't easily produce ¤. o is a wheel for pulling and splitting strings, not itself moved by string Can be attached to motor (which will drive the motor like a generator as well as being driven by the motor). If only attached to strings, it will rotate when the one string is pulled instead (and pull all the other strings attached to it) Note: strings can be split forever, i.e. there is infinite torque. It will only interact with strings arriving straight at it, Example: ~~~o but not: ~~~ o T is a piston, pushing an object at the top away from itself upon activation Can be attached to motor at it's base Pushing is transitive, i.e. if there is not a free space beyond the pushed object, it is also pushed. A piston can push anything except a piston that is actuating the same turn in the opposite direction. The order of piston activation is undefined. ┬, ┴, ┤ and ├ are also pistons (in different directions) = is a particle detector triggering an electrical pulse of 1 V when a particle passes through it (TODO: different horizontal/vertical behaviour?) \ and / are mirrors reflecting particles (in the obvious way) [ is a particle source emitting particles to the right (out the wide end) ] similarly emits to the left These two need 5 V per particle produced. Non-multiples of 5 V per turn will be discarded. Only one particle can be emitted per turn, so 10 V the same turn will queue emitting for the next turn. D is a block of lead, stopping and discarding any particles B is the battery, it provides 1 V of energy the first turn of the program and is after that spent. There can only be one in a program. After that the program will have provide it's own power
Turn processing order
Each turn the following phases happen:
- First turn only: Battery queues an electrical signal in adjacent wires and electrical devices.
- Particles
- Particles are moved along one step
- Objects that the particle are now on top of are activated (detectors, ...)
Electrical signals are queued for the next phase - Objects that affect the particles on top of them are processed (mirrors, ...)
- Electrical
- Queued electrical signals are propagated along wires, computing which devices will receive pulses and how large those pulses will be. Pulses are combined (i.e. 2 x 1 V will be the same as 1 x 2 V)
- Activated electrical devices causing mechanical power are processed (motors, ...). Mechanical devices activated is queued for the next phase.
- Activated particle emitters are processed.
- Mechanical
- Queued pistons are processed
- Queued wheels are processed, and strings pulled. Wheels activated by strings are processed recursively
- Wheels turning motor/generators queue electrical power for the next electrical phase.
- Strings pulling blocks move them and update wire. If a wire is attached on the opposite of the pulled block, it is also pulled (possibly "extending" it, if it is connected to a wheel on the other end)
- Check for halting condition: If none of the phases processed anything, halt the program.
TODO: it is possible to create undefined behaviour with string pulling if (for example) a vertical string pulls a horizontal string segment, which order are they processed in?
Loops and splitting
To prevent infinite loops in a single turn, the implementation must keep track of already visited wires, strings and objects. The same object can be visited more than once in a turn, but only once per "input". For example this should cause only 1 V on the wire at the bottom (assuming 1 V entering via the wire from above):
| ¤o~~~o S S o~~~o¤ |
While this would cause 2 V on the wire at the bottom:
| o¤o S S o¤o |
TODO: Would this cause 2 V or 4 V at the bottom:
| o¤o S S o~o S S o¤o |
This only causes 1 V at the bottom (assuming 1 V enters at the top):
| +++ ||| +++ |
Also of note is that energy doesn't decrease when splitting wires. Assuming 1 V enters from the top again:
| +++ | | ¤ ¤ o o S S
Both of these threads pulled in one step (as opposed to half a step).
Extensions
Name for use in ext directive in file is listed after the descriptive title. All extensions are optional to implement, though the IO one is probably pretty useful to have.
I/O extension (io)
This is not part of the core language, which only has halting/not halting as output.
0 and 1 are particle targets that consume the particle and generate that digit on standard output. { is a conditional particle generator (to the right), that generates a particle on a turn iff the user hits any key (that isn't reserved by the interpreter, for single stepping or similar) and it is supplied with 5 V that turn. } works similarly to the left
Sub assemblies (sub)
This extension allows for sub-assemblies to be defined at the top of the file and then inserted as 1x1 sized blocks inside the main playing field (or other sub assemblies defined afterwards).
To define a sub assembly, put a section such as the following before the main playing field:
#@ sub-start: X | v ->+- #@ sub-end
This defines a new playing field component "X" that will connect to wires on the top, left and right.
The outermost rows and columns must either be empty or contain exactly one of:
- Wire perpendicular to the edge (i.e. - on the left and right, | on top and bottom)
- String perpendicular to the edge (i.e. ~ on the left and right, S on top and bottom)
- Particle source [ or ] (only allowed on left or right)
This will be where external connections to wires, strings connect and particles can enter the sub assembly. Any particle can exit anywhere along any edge to exit in that direction.
The rest of the sub assembly can contain anything the main playing field can contain. Sub assemblies can not contain themselves (the unicode codepoint is only defined after the end of the sub assembly definition). If the same symbol is defined more than once it will override the previous definition.
Interactions with other elements:
- Sub assemblies can be pushed with pistons.
- Sub assemblies can be pulled with string, except on the side where they have a string outside attachment (in which case they transfer the energy into the assembly instead, and thus appear to act more like a wheel.
- Sub assemblies will burn in the high energy extension if fed electricity from any side they don't accept energy on via a wire outside attachment.
- Sub assemblies will be transmuted by a particle in the high energy extension if hit by a particle on any side they do not have a particle source on.
High energy extension (high-energy)
Particles have a lot of energy, so hitting anything not designed for particle interaction will transmute that object into a random other object.
Also, in this extension, high voltage (> 100 V) will burn objects attached to the end of wires not designed for electricity (anything except =, ¤ and other wire elements). Burned objects are removing from the playing field.
Charge extension (charge)
Particles have charge, and you can use a magnet "U" or electromagnet "I" to bend their path 90 degrees left/right depending on the charge.
TODO: How to generate particles with a specific charge?
Pullable wires (pullable-wires)
Wires can be pulled like string, though if + is used instead of o for joining them, pulling will result in a catastrophic breakage, halting the program due to the electrical fire immediately.
Belts and boxes (belts)
This extension was designed by User:Arseniiv
There are several proposed additional elements:
Belts
_ horizontal ! vertical
These can be turned in either direction (left/right or up/down) depending on what end of the belt is acted on (by a wheel or by an electrical pulse — use either or both as you wish). If there are simultaneous actions on both ends, the belt stays stationary this time.
Boxes
# is a box
In the beginning, any box should be adjacent to exactly one belt as follows:
! ! #_ _#_ _# # # # ! !
In these cases, the box is considered staying on that belt, e. g. the belt without it would look like:
! ! __ ___ __ ! ! ! ! !
In contrast, the following examples are ambiguous:
! ! _# #__ _#_ ! ! !
They should be disallowed, lead to undefined behavior, or something alike.
More than one adjacent box could stay on the same belt, the rules to decide to what belt they belong and if the placement is ambiguous stay the same. For example:
______##! ________! # ! ! ! #__##_ ! # (With boxes omitted: ______ ! ! ) ! # ! ! # ###_ ! ____
Behavior
There could be different kinds of boxes; boxes could conduct electricity and absorb or reflect particles, or do something else.
The current box conducts electricity and will absorb and store particles, dislodging them after being moved (or attempted to be moved) in the original direction of travel
A box moves left/right or up/down when the belt it stands on turns, given that the space where it should go will be not occupied by another box. A box at the end of the belt will remain in place if no other belt stands next to it, otherwise it will hop there.
For example, the configuration
o~ #_#_#__
will evolve as follows, when the string is pulled several times:
o~ _#_#_#_
o~ __#_#_#
o~ ___#_## o~ ____###
Then it ceases changing, as boxes are blocked completely.
In the next example, all three strings are pulled simultaneously:
o~ ##_#_!__o ! S o~
o~ _##_#!__o ! S o~
Now a box will hop to a vertical belt:
o~ __##_#__o * ! S o~
o~ ___##!__o # S o~
...and another one:
o~ ____##__o * # S o~
Now, it can’t move down. It’s pushed by the left box instead:
o~ _____##_o * # S o~
o~ _____#_#o * # S o~
No change after.
Note that steps marked with a star * can’t be represented as code because of ambiguity.
Code examples
Some basic code examples
Amplifying battery signal
To be able to generate a particle from the battery, the signal needs to be amplified. This can be done via wires for example
B +<¤o~o¤o~o¤>+ +<¤o~~o~~o¤>+ | ¤ | | v | +-----+-----+ |
Provides 5 V out the bottom by turn 2. Without the diodes this would quickly escalate to provide more and more power every turn (since the generators would drive each other as motors).
Scaling down energy
One way to scale down energy is to using particles:
--[=D+-- +-+
would convert the voltage arriving on the left into a series of pulses (one every turn) of 1 V each for each multiple of 5 V input. This would also discard the remainder.
Energy magnitude detection
It is possible to detect voltage levels (up to an arbitrary maximum) via observing how far a string is pulled.
Assuming 1 V entering from the top and then the next round 1 V entering from the bottom
| ¤ o +S--- 3 V +S--- 2 V +S--- 1 V +---- 0 V |
This could be made re settable by also providing a delayed pull down the next round.
It might be possible to construct a circuit to first determine which multiple of 5, followed by what the remainder is. Furthermore it may be possible to construct a circuit to repeatedly divide by 5 and provide a pulse train in unary indicating the number of multiples of 5.
Infinite particle loop
Assuming we have something like the above, we can quite easily set up an infinite loop with a particle. Assuming 5 V enters from the wire at the top:
| / \ [ = / \ | T | ¤ | +--+
This will push up the mirror the first time the particle hits the detector, causing it to enter an infinite loop, activating the piston once every cycle around the loop (from now on pushing an empty space, thus doing nothing).
Clocks
The infinite loop example above is also acts as a simple 12 cycle clock. I believe it is possible to make clocks with any periodicity.
The following will upon receiving 5 V from the top start a clock generating a 1 V signal to the left in the following pattern: 1001001001...
| +-+ |/=\| [ =/| \+-+---- T| ¤+
If 10 V were input instead you would get: 110110110110.
Here is the general shape of an 8-cycle clock:
/ \ \ =/
A 4-cycle clock could be constructed by combining two 8-cycle clocks with their periods 90 degrees out of phase. Similarly a 2-cycle clock could be made by combining two of those, and a 1-cycle clock by combining two of those.
Delays
A signal can be delayed one turn by flipping it between mechanical and electrical:
--¤o~o¤--
Pulling objects with strings
This demonstrates pulling objects with strings. Assuming 1 V enters the wire from the top:
| ¤o~~=~~D
This will pull the \ one to the left, and then also pull the D one to the left, transforming the grid to the following:
| ¤o~=~~D
However, if 1 V enter from the top of:
| ¤o~~=~~o
you will instead get:
| ¤o~=~~~o
since wheels are not movable by string.
String behaviour
For all examples assuming 1 V from the top:
| | ¤o~~~~ => ¤o~~~
| | ¤o~~o => ¤o~~o S S S
| | +¤o~~o¤-- => +¤o~~o¤--
In other words, a wheel (o) is both a like a pulley and a wheel with four infinite strings on it, one for each cardinal direction.
This example demonstrates the way to unwind a string: by pulling it from the other direction with a wheel. Also a string attached to the other side of an object being pulled is pulled with the object:
| | +¤o~D~o¤-- => +¤oD~~o¤--
There is also an implicit string between an object and a wheel next to it:
| | +¤o~Do¤-- => +¤oD~o¤--
The string does not attach "through" multiple objects though:
| | +¤o~DD~o¤-- => +¤oD D~o¤--
There is infinite torque when splitting strings (similar to how there is infinite voltage when splitting wires to multiple end points):
| | +¤o~~o~~o => +¤o~~o~~o S S S S S S S S S
Memory
It is possible to do something like a delay line of yore, using particles
+--[ \ | / / | \ \ | / / | \ = | | +<¤o¤+ +<¤o¤+ +<¤o¤+ +<¤o¤+ +<¤o¤+ |
The bottom allows input as well as output from the delay line.
Note the amplifier to 5 V.
Gates
If you use a signal voltage of 1 V, an AND gate for two electrical signals can be implemented by boosting one signal to 4 V and then converting to a to a particle. This will only be enough voltage if there is an additional 1 V signal
------->---+ +------ | + ---+¤o¤>+--+-[=D +¤o¤>+ +¤o¤>+ +¤o¤>+
TODO: Figure out other gates.
An OR gate where you don't care about voltage is easy (but useless when the other gates do care about it):
->+--- ->+
However if you use you run everything on a global clock, you could do an automatically resetting relay like thing, that could perhaps pass off as a transistor:
Data Clk | | | | ¤-AND--+ ┴ | ---------(------- | T | ¤------+
Where it says "AND" insert the AND circuit from above.
A NOT gate can be done using an energy magnitude detection circuit.
Computational class
The language is Turing complete, as it can implement 8-bit brainfuck.
A simple loop could work by multiplying an input by 5 using an amplifier and generating particles from the result. A machine would perform a certain operation while particles are being emitted. We could easily use this to subtract, multiply, divide, or do exponentiation. (Addition can be handled more easily with +.)
We could compress the entire brainfuck tape onto one number using (cell 0) + 2^8*(cell 1) + 2^16*(cell 2) + …. The pointer would take the form 2^(8*n). By dividing by the pointer and other related powers of 2 you could easily generate + and - using AND, OR and NOT gates. < and > are also fairly simple. [ and ] are harder, but using wires and )'s to loop back to earlier code (and checking for 0 using similar code to + and -) would be possible.