Thursday, 15 December 2022

Listen to the sea

 

Rockstar is an esoteric programming language that creates code that looks like song lyrics.

I tried to use it to solve this code puzzle: https://adventofcode.com/2022/day/1.

This wasn't that hard, and after puzzling a bit, I ended up with this code that works.

my ship is everything
waves are everything
let my life be waves
let my dreams be waves
Listen to the sea

A pirate wants gold and freedom
If gold is greater than freedom
send gold

give freedom back

While the sea isn't mysterious or the mind isn't mysterious
If the sea is silent
let my life be a pirate taking my ship, and my life
let my ship be waves
Put the sea into the mind
Listen to the sea

If the sea isn't silent
let my love be the sea without waves 
let my ship be with my love
Put the sea into the mind
Listen to the sea


let the sea be my life
shout her

(Full explanation of all the lines follows further below in this blogpost)

You can try it online, and it returns the correct answer if you append two newlines to the bottom of the input. But this could be nicer.

According to the specifications, the expression:
the sea is mysterious

It should return true if "the sea" is null (meaning end of file) and false if "the sea" is an empty string. 

In addition, the expression:  
If the sea is silent

should return true if the sea is an empty string.

But in practice, due to a bug in the interpreter, both mysterious and silent act exactly the same and match both an empty string and a null value. This means we cannot distinguish between the two and need two newlines at the end to ensure the program can detect it.

If we write a program according to spec, we get these lines.

my ship is everything
waves are everything
let my life be waves
let my dreams be waves
Listen to the sea

A pirate wants gold and freedom
If gold is greater than freedom
send gold

give freedom back

While the sea isn't mysterious
If the sea is silent
let my life be a pirate taking my ship, and my life
let my ship be waves
Listen to the sea

If the sea isn't silent
let my love be the sea without waves 
let my ship be with my love
Listen to the sea


let the sea be my life
shout her


When I first made this edit, I accidentally also removed the line "let my ship be waves"  and I actually found that the song flows better without that line, so I decided the keep the "wrong" version. (If you wanna have a singable fixed version, you can move the last "Listen to the sea" one line down, and replace the second "listen to the sea" with "let my ship be waves".)

I also realigned the verses, so each verse has 4 lines. I also broke down long lines into two, and combined some shorter lines.

Both these changes completely break the program as a valid rockstar program, but make it possible to actually sing it.

So then I end up with verses that are close to the working program but not quite.

my ship is everything
waves are everything
let my life be waves; let my dreams be waves
Listen to the sea

A pirate wants gold and freedom
If gold is greater than freedom
send gold, give freedom back
While the sea isn't mysterious

If the sea is silent
let my life be a pirate 
taking my ship, and my life
Listen to the sea

If the sea isn't silent
let my love be the sea without waves 
let my ship be with my love
Listen to the sea

let the sea be my life
shout her


This can actually be sung, so I hired Liliia K  to sing it for me. The result can be seen below




The full song (before I applied a bunch of artistic licences) is equivalent to the following python code. 


myShip = 0
waves = 0
myLife = waves
myDreams = waves
theSea = input()

def aPirate(gold, freedom):
    if gold > freedom:
        return gold
    return freedom

while theSea is not None:
    if theSea == '':
        myLife = aPirate(
            myShip, myLife
        )
        myShip = 0
        theSea = input()
    if theSea != '':
        myLove = theSea - waves
        myShip += myLove
        theSea = input()

theSea = myLife
print(theSea)

There are only two caveats. 

  1. the input function in python blocks when there are no more lines to read, I modified it to return None instead. This matches the behaviour in rockstar
  2. Rockstar uses javascript internally, so a string minus an integer is an integer. In python, this gives an error.
You can add these lines above your script to mitigate both problems.

with open("input1.txt") as file:
    lines = list(reversed(file.read().split("\n")))


def input():
    if lines:
        l = lines.pop()
        try:
            return int(l)
        except ValueError:
            return l

Here follows a line-by-line explanation of all the code:

myShip = 0       | my ship is everything
waves = 0        | waves are everything
myLife = waves   | let my life be waves
myDreams = waves | let my dreams be waves

We initialise a bunch of variables. "everything" has ten letters, so the value of the poetic literal is 0. 
The variable "myDreams" is never used, but it was left over after a refactor, and the song flows better using the variable, so I decided to leave it.

theSea = input() | Listen to the sea

Store the current line from the input into the sea. Rockstar does not have "foreach" nor "do while" loops, so the only way to read the input line by line is to initialize "theSea" to the first line before the while loop. More about that later.

def aPirate(gold, freedom): | A pirate wants gold and freedom
    if gold > freedom:      | If gold is greater than freedom
        return gold         | Send gold
    return freedom          | Give freedom back

Define a function "aPirate" that takes in two arguments ("gold" and "freedom") and returns the larger one. Needs a newline after "Send gold" to close the if block and after "return freedom" to close the function call.

while theSea is not None:  | While the sea isn't mysterious

A while loop. while the input file has unread lines. 

if theSea == '': | If the sea is silent

Checks if we have read an empty line. In the context of the riddle, we start to count the calories of a new elf.

myLife = aPirate(myShip, myLife) | let my life be a pirate taking my ship, and my life

"myLife" is a variable that stores the elf with the highest amount of calories. "myShip" stores the calories of the current elf. This line of code stores the maximum of the calories of the current elf and the already found maximum into the maximum. In python terms, this would be equivalent to myLife = max(myShip, myLife). This changes the variable "myLife" from "the highest number of calories excluding the current elf" to "the highest number of calories including the current elf."

myShip = 0 | let my ship be waves

Resets the "myShip", meaning the "current calory counter" back to zero. This line is missing in the sung version.

theSea = input() | Listen to the sea

Reads the next line of the input

if theSea != '': | If the sea isn't silent

Checks if we have read a non-empty line. This is the alternative (else) branch of the previous if. Although in practice, it can be shortcutted cause the variable "theSea" might have changed since the previous if. This makes no difference to the actual execution of the code.

myLove = theSea - waves | let my love be the sea without waves 


Converts "theSea" to an integer. This is a hack. In javascript -and rockstar by extension- subtracting zero from a string converts it into an integer. So "myLove" ends up with a number of calories as an integer. 


myShip += myLove | let my ship be with my love

Adds the number of calories on the current line to the number of calories of the current elf. 

theSea = input() | Listen to the sea


Reads the next line of the input

theSea = myLife | let the sea be my life
print(theSea) | shout her

Just a poetic version of printing the current maximum stored in "myLife."