99
\$\begingroup\$

Robbers' challenge thread is here.

Cops' challenge: Design a programming language that appears to be unusable for programming, but admits computation (or at least completion of the task) through some non-obvious mechanism.

You should design a simple programming language that reads code from an input file and then does ... something. You must prepare a solution program that finds the 3rd-largest number in the input when run in your interpreter. You need to make it as hard as possible for robbers to find a solution program. Note that robbers can post any solution that accomplishes the task, not just the one you had in mind.

This is a popularity contest. The cops' goal is to get as many votes as possible while surviving 8 days after posting the interpreter without being cracked. To this end, the following practices should help:

  • Accurately explaining your language's semantics
  • Writing readable code

The following tactics are strongly discouraged:

  • Using encryption, hashes, or other cryptographic methods. If you see a language that employs RSA encryption, or refuses to execute a program unless its SHA-3 hash is equal to 0x1936206392306, please do not hesitate to downvote.

Robbers' challenge: Write a program that finds the third-largest integer in the input when run in the cops' interpreter.

This one is relatively straightforward. In order to crack a cop answer, you must create a program that completes the task when run in its interpreter. When you crack an answer, post a comment saying "Cracked" on the cop's answer linking to your post. Whoever cracks the most cops wins the robbers' thread.

I/O Rules

  • Interpreters should take a filename on the command line for the program, and use standard input and output when running it.
  • Input will be given in unary and consist only of the characters 0 and 1 (48 and 49 in ASCII). A number N is encoded as N 1s followed by a 0. There is an additional 0 before end of file. Example: For the sequence (3, 3, 1, 14), the input is 11101110101111111111111100.
  • Input is guaranteed to have at least 3 numbers. All numbers are positive integers.
  • Output will be judged by the number of 1s printed before the program halts. Other characters are ignored.

In the following examples, the first line is the input in decimal format; the second is the actual program input; the third is a sample output.

1, 1, 3 101011100 1 15, 18, 7, 2, 15, 12, 3, 1, 7, 17, 2, 13, 6, 8, 17, 7, 15, 11, 17, 2 111111111111111011111111111111111101111111011011111111111111101111111111110111010111111101111111111111111101101111111111111011111101111111101111111111111111101111111011111111111111101111111111101111111111111111101100 111111,ir23j11111111111u 247, 367, 863, 773, 808, 614, 2 <omitted> <contains 773 1's> 

Boring rules for cop answers:

  • To prevent security through obscurity, the interpreter should be written in a language in the top 100 of this TIOBE index and have a freely available compiler/interpreter.
  • The interpreter must not interpret a language which was published before this challenge.
  • Interpreter should fit into your post and not be hosted externally.
  • Interpreter should be deterministic
  • Interpreter should be portable and follow the standard of its own language; don't use undefined behavior or bugs
  • If the solution program is too long to fit into the answer, you must post a program that generates it.
  • Solution program should consist only of printable ASCII and newlines.
  • You must execute your solution program in less than 1 hour on your own computer for each of the example inputs above.
  • The program should work for any integers less than 106, and any number of integers less than 106 (not necessarily in under an hour), provided that the total input length is less than 109.
  • To become safe, a cop must edit the solution program into the answer after 8 days have passed.

Scoring

The cop who becomes safe with the highest score, and a positive score, wins this question.

\$\endgroup\$
25
  • 1
    \$\begingroup\$ You don't explicitly state this but am I right in assuming that the cop actually has to write and post the interpreter in their answer? \$\endgroup\$ Commented Oct 26, 2015 at 14:06
  • \$\begingroup\$ @muddyfish Yes, the interpreter should be the content of the cop answer. \$\endgroup\$ Commented Oct 26, 2015 at 14:08
  • 1
    \$\begingroup\$ @kirbyfan64sos Output will be judged by then number of 1s printed before the program halts. Other characters are ignored. \$\endgroup\$ Commented Oct 27, 2015 at 16:22
  • 1
    \$\begingroup\$ @Luminous Apparently nothing at all. \$\endgroup\$ Commented Oct 28, 2015 at 17:49
  • 29
    \$\begingroup\$ I nerd-sniped myself with this challenge. I created a programming language and then spent hours programming the task to see if the language could even work only to find out that it actually was unusable. \$\endgroup\$ Commented Oct 31, 2015 at 12:58

15 Answers 15

35
\$\begingroup\$

Shuffle (written in C++), Cracked! by Martin

Edit Martin cracked it. To see his solution, click the link. My solution has also been added.

Edit Fixed print-d command to be able to handle both registers and stacks. As this is a debugging command which is not permitted in a solution, this should not affect anyone using the previous version of the interpreter

I'm still new to this, so if there is something wrong with my answer or my interpreter, please let me know. Please ask for clarification if something is not clear.

I don't imagine that this will be too terribly difficult, but hopefully it will provide some sort of challenge. What makes shuffle fairly unusable is that it will only print when things are in their proper place.

-->Basics:

There are 24 Stacks, we call them stack1, ... stack24. These stacks live in a list. At the beginning of any program, these stacks have zero pushed and they start in their proper place, i.e. stacki in the ith position in the list (Note that we will index beginning from 1, unlike C++). During a course of a program, the order of the stacks within the list will shift. This is important for reasons that will be explained when I discuss the commands.

There are 5 registers available for use. They are named Alberto, Gertrude, Hans, Leopold, ShabbySam. Each of these is set to zero at the beginning of a program.

So, at the onset of any program, there are 24 stacks, each has its number matching its index in the stack list. Every stack has exactly one zero on top. Each of the five registers is initialized to zero.

-->Commands and Syntax:

There are 13 commands (+1 debugging command) that are available in Shuffle. They are as follows

  • cinpush this command takes no arguments. It waits for command line input in the fashion described in the question (other input will lead to unspecified/undefined results). It then splits the input string into integers, e.g. 101011100 --> 1,1,3. For each input received, it does the following: (1) permutes the list of stacks based on the value. Let the integer value in question be called a. If a is less than 10 it does permutation u. If a is between 9 and 30 (noninclusive) it does permutation d. Otherwise it does permutation r. (2) It then pushes a onto the the stack which is first in the list. Note that I do not mean stack1 (although it may be the case that stack1 is first in the list). The permutations are defined below. Since cinpush is the only way to get user input, it must appear in any solution.
  • mov value register The mov command is basically variable assignment. It assigns value to register. value can take several forms: it can be (1) an integer, e.g. 47 (2) the name of a different register, e.g. Hans (3) the index of a stack followed by 's', e.g. 4s. Note that this is the index in the list, not the number of the stack. As such, the number should not exceed 24.

    Some mov examples:

    mov 4s Hans mov Hans ShabbySam mov 9999 Gertrude 
  • movfs index register This stands for 'move from stack'. It is similar to the mov command. It exists so that you may access a stack indexed by a register. For example, if earlier you set Hans equal to 4 ( mov 4 Hans) Then you may use movfs Hans Gertrude to set Gertrude equal to the top of stack 4. This type of behavior is not accessible simply using mov.

  • inc register increases register value by 1.
  • dec register decreases register value by 1.
  • compg value value register This stands for 'compare greater'. It sets the register equal to the greater of the two values. value can be an integer, a register, or a stack index followed by 's', as above.
  • compl value value register 'compare lesser' same as above, except takes the smaller value.
  • gte value1 value2 register Checks if value1 >= value2 then puts the Boolean value (as 1 or 0 ) into register.
  • POP!! index pops off the top of the stack indexed by index in the stack list.
  • jmp label jumps unconditionally to the label label. This is a good time to talk about labels. A label is word followed by ':'. The interpreter pre-parses for labels, so you are able to jump forward to labels as well as back.
  • jz value label jumps to label if value is zero.
  • jnz value label jumps to label if value is nonzero.

    Examples of labels and jumping:

    this_is_my_label: jmp this_is_my_label mov 10 Hans jnz Hans infinite_loop infinite_loop: jmp infinite_loop 
  • "shuffle" permutation Here is the shuffle command. This allows you to permute the list of stacks. There are three valid permutations that can be used as arguments, l, f, and b.

  • print register This checks if all the stacks are in their initial positions, i.e stacki is at index i in the stack list. If this is the case, it prints the value at register in unary. Otherwise, it prints a nasty error. As you can see, to output anything, the stacks must all be in the correct places.
  • done! this tells the program to quit with no error. If the program ends without done!, it will print to the console the number on top of each stack followed by the stack's number. The order that the stacks are printed is the order they appear in the stack list. If a stack is empty, it will be omitted. This behavior is for debugging purposes and may not be used in a solution.
  • print-d value this prints the value of the stack, register, or integer given (to access stack i, pass is as argument, as explained above). This is a debugging tool and not part of the language, as such it is not permitted in an solution.

--> Here is the interpreter code

All the parsing happens in the main function. This is where you will find it parsing for specific commands.

#include<fstream> #include<iostream> #include<string> #include<stack> #include<cmath> using namespace std; class superStack: public stack<long> { private: int m_place; public: static int s_index; superStack() { m_place = s_index; s_index++; } int place() { return m_place; } }; int superStack::s_index=1; superStack stack1,stack2,stack3,stack4,stack5,stack6,stack7,stack8,stack9,stack10,stack11, \ stack12,stack13,stack14,stack15,stack16,stack17,stack18,stack19,stack20,stack21,stack22,stack23,stack24; superStack* stackptrs[]= { \ &stack1,&stack2,&stack3,&stack4,&stack5,&stack6,&stack7,&stack8,&stack9,&stack10,&stack11, \ &stack12,&stack13,&stack14,&stack15,&stack16,&stack17,&stack18,&stack19,&stack20,&stack21,&stack22,&stack23,&stack24 \ }; long Gertrude=0; long Hans=0; long Alberto=0; long ShabbySam=0; long Leopold=0; void SWAP( int i, int j) { // 0 < i,j < 25 i--; j--; superStack* tempptr = stackptrs[i]; stackptrs[i]=stackptrs[j]; stackptrs[j] = tempptr; } void u() { int list[3][4] = { {1,9,6,13}, {2,10,5,14}, {17,19,20,18}, }; for(int i=0;i<3;i++) { for(int j=1;j<4;j++) { SWAP( list[i][0], list[i][j] ); } } } void d() { int list[3][4] = { {3,11,8,15}, {4,12,7,16}, {22,24,23,21}, }; for(int i=0;i<3;i++) { for(int j=1;j<4;j++) { SWAP( list[i][0], list[i][j] ); } } } void r() { int list[3][4] = { {2,17,8,24}, {4,18,6,23}, {9,10,12,11}, }; for(int i=0;i<3;i++) { for(int j=1;j<4;j++) { SWAP( list[i][0], list[i][j] ); } } } void l() { int list[3][4] = { {1,19,7,22}, {3,20,5,21}, {14,13,15,16}, }; for(int i=0;i<3;i++) { for(int j=1;j<4;j++) { SWAP( list[i][0], list[i][j] ); } } } void f() { int list[3][4] = { {20,9,24,16}, {18,11,22,14}, {1,2,4,3}, }; for(int i=0;i<3;i++) { for(int j=1;j<4;j++) { SWAP( list[i][0], list[i][j] ); } } } void b() { int list[3][4] = { {19,10,23,15}, {17,12,21,13}, {5,6,8,7}, }; for(int i=0;i<3;i++) { for(int j=1;j<4;j++) { SWAP( list[i][0], list[i][j] ); } } } void splitpush(string input) { long value=0; for(long i=0;i<input.size();i++) { if(input[i]=='1'){ value++; } else if(input[i]=='0' && value!=0 ) { if(value<10) { u(); } else if (value<30) { d(); } else { r(); } (*stackptrs[0]).push(value); value=0; } else { break; } } } long strToInt(string str) { // IF STRING HAS NON DIGITS, YOU WILL GET GARBAGE, BUT NO ERROR long j=str.size()-1; long value = 0; for(long i=0;i<str.size();i++) { long x = str[i]-48; value+=x*floor( pow(10,j) ); j--; } return value; } bool isEmpty(superStack stk) { if( stk.size()>0){return false; } else {return true;} } long getValue(string str) { if(str=="ShabbySam") { return ShabbySam; } else if(str=="Hans") { return Hans; } else if(str=="Gertrude") { return Gertrude; } else if(str=="Alberto") { return Alberto; } else if(str=="Leopold") { return Leopold; } else if(str[ str.size()-1 ]=='s'){ str.pop_back(); long index = strToInt(str)-1; if( !isEmpty( (*stackptrs[index]) ) ) { return (*stackptrs[index]).top(); } else { cerr<<"Bad Expression or Empty Stack"; } } else { return strToInt(str); } } void printUnary(long i) { while(i>0) { cout<<1; i--; } } int main(int argc, char**argv) { if(argc<2){std::cerr<<"No input file given"; return 1;} ifstream inf(argv[1]); if(!inf){std::cerr<<"File open failed";return 1;} for(int i=0;i<24;i++) { (*stackptrs[i]).push(0); // Pre push a zero on every stack } string labels[20]; unsigned labelPoints[20]; int index=0; string str; while(inf) { // parse for labels inf>>str; //cout<<inf.tellg()<<" "; if(inf) { if(str[str.size()-1]==':') { str.pop_back(); bool alreadyExists = false; for(int i=0; i<index;i++){ if(labels[i] == str ) { alreadyExists=true;} } if(!alreadyExists) { labels[index]=str; labelPoints[index]= inf.tellg(); index++; } } } } inf.clear(); inf.seekg(0,inf.beg); while(inf) { // parse for other commands inf>>str; if(inf) { if(str=="cinpush") { string input; cin>>input; splitpush(input); } else if(str=="mov") { inf>>str; long value = getValue(str); inf>>str; if(str=="Gertrude"){Gertrude=value;} else if(str=="Hans"){Hans=value;} else if(str=="ShabbySam"){ShabbySam=value;} else if(str=="Alberto"){Alberto=value;} else if(str=="Leopold"){Leopold=value;} else {cerr<<"Bad register name. ";} } else if(str=="movfs") { inf>>str; long index = getValue(str); if(!isEmpty( *stackptrs[index-1] )) { inf>>str; long value = (*stackptrs[index-1]).top(); if(str=="Gertrude"){Gertrude=value;} else if(str=="Hans"){Hans=value;} else if(str=="ShabbySam"){ShabbySam=value;} else if(str=="Alberto"){Alberto=value;} else if(str=="Leopold"){Leopold=value;} else {cerr<<"Bad register name.";} } else { cerr<<"Empty Stack"; } } else if(str=="inc") { inf>>str; if(str=="Gertrude"){Gertrude++;} else if(str=="Hans"){Hans++;} else if(str=="ShabbySam"){ShabbySam++;} else if(str=="Alberto"){Alberto++;} else if(str=="Leopold"){Leopold++;} else {cerr<<"Bad register name. ";} } else if(str=="dec") { inf>>str; if(str=="Gertrude"){Gertrude--;} else if(str=="Hans"){Hans--;} else if(str=="ShabbySam"){ShabbySam--;} else if(str=="Alberto"){Alberto--;} else if(str=="Leopold"){Leopold--;} else {cerr<<"Bad register name. ";} } else if(str=="compg") { inf>>str; long value1 = getValue(str); inf>>str; long value2 = getValue(str); inf>>str; long larger; if(value1>value2){larger = value1;} else {larger = value2;} if(str=="Gertrude"){Gertrude=larger;} else if(str=="Hans"){Hans=larger;} else if(str=="ShabbySam"){ShabbySam=larger;} else if(str=="Alberto"){Alberto=larger;} else if(str=="Leopold"){Leopold=larger;} else {cerr<<"Bad register name. ";} } else if(str=="compl") { inf>>str; long value1 = getValue(str); inf>>str; long value2 = getValue(str); inf>>str; long larger; //LARGER IS REALLY SMALLER HERE if(value1>value2){larger = value2;} else {larger = value1;} if(str=="Gertrude"){Gertrude=larger;} else if(str=="Hans"){Hans=larger;} else if(str=="ShabbySam"){ShabbySam=larger;} else if(str=="Alberto"){Alberto=larger;} else if(str=="Leopold"){Leopold=larger;} else {cerr<<"Bad register name. ";} } else if(str=="gte") { inf>>str; long value1= getValue(str); inf>>str; long value2= getValue(str); inf>>str; bool torf = value1 >= value2; if(str=="Gertrude"){Gertrude=torf;} else if(str=="Hans"){Hans=torf;} else if(str=="ShabbySam"){ShabbySam=torf;} else if(str=="Alberto"){Alberto=torf;} else if(str=="Leopold"){Leopold=torf;} else {cerr<<"Bad register name. ";} } else if(str=="lte") { inf>>str; long value1= getValue(str); inf>>str; long value2= getValue(str); inf>>str; bool torf = value1 <= value2; if(str=="Gertrude"){Gertrude=torf;} else if(str=="Hans"){Hans=torf;} else if(str=="ShabbySam"){ShabbySam=torf;} else if(str=="Alberto"){Alberto=torf;} else if(str=="Leopold"){Leopold=torf;} else {cerr<<"Bad register name. ";} } else if(str=="POP!!") { inf>>str; long index = getValue(str); index--; //because we (C++ and this interpreter) index differently if(!isEmpty( *stackptrs[index] )) { (*stackptrs[index]).pop(); } else {cerr<<"Can't POP!! from empty stack";} } else if(str=="push"){cerr<<" You can't. ";} /* else if(str[str.size()-1]==':') { str.pop_back(); bool alreadyExists = false; for(int i=0; i<index;i++){ if(labels[i] == str ) { alreadyExists=true;} } if(!alreadyExists) { labels[index]=str; labelPoints[index]= inf.tellg(); index++; } }*/ else if(str=="jmp") { inf>>str; for(int i=0;i<index;i++) { if( labels[i]==str) { inf.seekg( labelPoints[i], ios::beg); } } } else if(str=="jz") { inf>>str; long value = getValue(str); if(value==0) { inf>>str; for(int i=0;i<index;i++) { if( labels[i]==str) { inf.seekg( labelPoints[i], ios::beg); } } } } else if(str=="jnz") { inf>>str; long value = getValue(str); if(value!=0) { inf>>str; for(int i=0;i<index;i++) { if( labels[i]==str) { inf.seekg( labelPoints[i], ios::beg); } } } } else if(str== "\"shuffle\"") { inf>>str; if(str=="l"){ l(); } else if(str=="f"){ f(); } else if(str=="b"){ b(); } else {cerr<<"Bad shuffle parameter";} } else if(str=="print") { for(int i=0;i<24;i++) { if( (i+1) != (*stackptrs[i]).place() ) { cerr<< "Sorry, your stacks are in the wrong place! You can't print unless you put them back! Exiting. "; return 1; } } inf>>str; if(str=="Gertrude"){printUnary(Gertrude);} else if(str=="Hans"){printUnary(Hans);} else if(str=="ShabbySam"){printUnary(ShabbySam);} else if(str=="Alberto"){printUnary(Alberto);} else if(str=="Leopold"){printUnary(Leopold);} else {cerr<<"Bad register name. ";} } else if(str=="done!") {return 0;} else if(str=="print-d" ){ inf>>str; long value = getValue(str); cout<<str; } } } /*for(int i=1;i<25;i++) { (*(stackptrs[i-1])).push(i); } u();d();r();l();f();b(); */ cout<<"\n"; for(int i=1;i<25;i++) { if( (*(stackptrs[i-1])).size()>0 ) { cout<<(*(stackptrs[i-1])).top()<<" "<<(*(stackptrs[i-1])).place()<<"\n"; (*(stackptrs[i-1])).pop(); } } /* for (int i=0;i<index;i++) { cout<<labels[i]<<": "<<labelPoints[i]<<"\n"; }*/ return 1; } 

-->Permutations The permutations permute the elements of the stack list in the following fashion:

Where means that

(These also appear in the interpreter code. If there is a discrepancy, the interpreter is correct.)

-->Simple Example

These two simple programs print the numbers from 24 to 1 (in unary) with no whitespace.

mov 24 Hans start: print Hans dec Hans jnz Hans start done! 

or

mov 24 Hans start: print Hans dec Hans jnz Hans start done! 

They are the same program.

Explanation and Solution:

Martin has a good explanation in his answer as well.

As Martin figured out, this language was inspired by the pocket cube (aka 2x2 Rubik's cube). The 24 stacks are like the 24 individual squares on the cube. The permutations are the basic moves allowed: up, down, right, left, front, back.

The main struggle here is that when values are pushed, only three moves are used: up, down, and right. However, you do not have access to these moves when "shuffling" the stacks. You have only the other three moves.

As it turns out, both sets of three moves actually span the entire group (i.e. are generators), so the problem is solvable. This means that you can actually solve any 2x2 Rubik's cube only using 3 of the moves.

All that is left to do is figure out how to undo the up, down, and right moves using the other three. To this end, I employed a computer algebra system called GAP.

After undoing the permutations, finding the third largest number is fairly trivial.

cinpush main: mov 1s ShabbySam POP!! 1 jmp compare continue: gte 0 ShabbySam Leopold jnz Leopold end gte ShabbySam 9 Leopold jz Leopold uinverse gte ShabbySam 29 Leopold jz Leopold dinverse jnz Leopold rinverse compare: gte ShabbySam Alberto Leopold jz Leopold skip mov Gertrude Hans mov Alberto Gertrude mov ShabbySam Alberto jmp continue skip: gte ShabbySam Gertrude Leopold jz Leopold skip_2 mov Gertrude Hans mov ShabbySam Gertrude jmp continue skip_2: gte ShabbySam Hans Leopold jz Leopold continue mov ShabbySam Hans jmp continue uinverse: "shuffle" f "shuffle" f "shuffle" f "shuffle" l "shuffle" b "shuffle" l "shuffle" b "shuffle" b "shuffle" b "shuffle" l "shuffle" l "shuffle" l "shuffle" b "shuffle" b "shuffle" b "shuffle" l "shuffle" l "shuffle" l "shuffle" f jmp main dinverse: "shuffle" f "shuffle" b "shuffle" l "shuffle" b "shuffle" b "shuffle" b "shuffle" f "shuffle" f "shuffle" f jmp main rinverse: "shuffle" b "shuffle" l "shuffle" f "shuffle" l "shuffle" b "shuffle" f "shuffle" f "shuffle" f "shuffle" b "shuffle" l "shuffle" l "shuffle" l "shuffle" b "shuffle" b "shuffle" b "shuffle" f "shuffle" f "shuffle" f "shuffle" l "shuffle" f "shuffle" l "shuffle" f "shuffle" l "shuffle" f "shuffle" f "shuffle" f "shuffle" l "shuffle" l "shuffle" l "shuffle" f "shuffle" l "shuffle" l "shuffle" f "shuffle" f "shuffle" f "shuffle" l "shuffle" l "shuffle" l "shuffle" l "shuffle" l "shuffle" l "shuffle" f "shuffle" l "shuffle" l "shuffle" l "shuffle" f "shuffle" f "shuffle" f "shuffle" l "shuffle" l "shuffle" l "shuffle" f "shuffle" f "shuffle" f "shuffle" l "shuffle" l "shuffle" l "shuffle" f "shuffle" f "shuffle" f "shuffle" l "shuffle" l "shuffle" l "shuffle" f "shuffle" f "shuffle" f "shuffle" l "shuffle" f "shuffle" l "shuffle" f "shuffle" l "shuffle" f "shuffle" l "shuffle" l "shuffle" l jmp main end: print Hans done! 
\$\endgroup\$
7
  • 3
    \$\begingroup\$ Cracked. :) I'm really impressed by the language! \$\endgroup\$ Commented Oct 28, 2015 at 11:02
  • \$\begingroup\$ Wow that was faster than I expected. Thanks, I'm glad it was as fun to figure out as it was to write. \$\endgroup\$ Commented Oct 28, 2015 at 11:05
  • \$\begingroup\$ I'm curious, would it have been decently harder if I had changed the names of the permutations to something less obviously about Rubik's cubes? \$\endgroup\$ Commented Oct 28, 2015 at 11:13
  • \$\begingroup\$ They were definitely a clue, but I think it wouldn't have taken that much longer if they had had different names. \$\endgroup\$ Commented Oct 28, 2015 at 11:16
  • \$\begingroup\$ Heh, looks like GAP was not being particularly clever about reversing the three input permutations. ;) \$\endgroup\$ Commented Oct 28, 2015 at 20:18
30
\$\begingroup\$

Brian & Chuck, Cracked by cardboard_box

I've been intrigued for some time now by the idea of a programming language where two programs interact with each other (probably inspired by ROCB). This challenge was a nice incentive to tackle this concept at last while trying to keep the language as minimal as possible. The design goals were to make the language Turing-complete while each of its parts individually are not Turing-complete. Furthermore, even both of them together should not be Turing-complete without making use of source code manipulation. I think I've succeeded with that, but I haven't proven any of those things formally yet. So without further ado I present to you...

The Protagonists

Brian and Chuck are two Brainfuck-like programs. Only one of them is being executed at any given time, starting with Brian. The catch is that Brian's memory tape is also Chuck's source code. And Chuck's memory tape is also Brian's source code. Furthermore, Brian's tape head is also Chuck's instruction pointer and vice versa. The tapes are semi-infinite (i.e. infinite to the right) and can hold signed arbitrary-precision integers, initialised to zero (unless specified otherwise by the source code).

Since the source code is also a memory tape, commands are technically defined by integer values, but they correspond to reasonable characters. The following commands exist:

  • , (44): Read a byte from STDIN into the current memory cell. Only Brian can do this. This command is a no-op for Chuck.
  • . (46): Write the current memory cell, modulo 256, as a byte to STDOUT. Only Chuck can do this. This command is a no-op for Brian.
  • + (43): Increment the current memory cell.
  • - (45): Decrement the current memory cell.
  • ? (63): If the current memory cell is zero, this is a no-op. Otherwise, hand control over to the other program. The tape head on the program which uses ? will remain on the ?. The other program's tape head will move one cell to the right before executing the first command (so the cell which is used as the test is not executed itself).
  • < (60): Move the tape head one cell to the left. This is a no-op if the tape head is already at the left end of the tape.
  • > (62): Move the tape head one cell to the right.
  • { (123): Repeatedly move the tape head to the left until either the current cell is zero or the left end of the tape is reached.
  • } (125): Repeatedly move the tape head to the right until the current cell is zero.

The program terminates when the active program's instruction pointer reaches a point where there are no more instructions to its right.

The Source Code

The source file is processed as follows:

  • If the file contains the string ```, the file will be split into two parts around the first occurrence of that string. All leading and trailing whitespace is stripped and the first part is used as the source code for Brian and the second part for Chuck.
  • If the file does not contain this string, the first line of the file will be used as the source for Brian and the second part for Chuck (apart from the delimiting newline, no whitespace will be removed).
  • All occurrences of _ in both programs are replaced with NULL bytes.
  • The two memory tapes are initialised with the character codes corresponding to the resulting string.

As an example, the following source file

 abc ``` 0_1 23 

Would yield the following initial tapes:

Brian: [97 98 99 0 0 0 0 ...] Chuck: [48 0 49 10 50 51 0 0 0 0 ...] 

The Interpreter

The interpreter is written in Ruby. It takes two command-line flags which must not be used by any solution (as they are not part of the actual language specification):

  • -d: With this flag, Brian and Chuck understand two more commands. ! will print a string representation of both memory tapes, the active program being listed first (a ^ will mark the current tape heads). @ will also do this, but then immediately terminate the program. Because I'm lazy, neither of those work if they are the last command in the program, so if you want to use them there, repeat them or write a no-op after them.
  • -D: This is the verbose debug mode. It will print the same debug information as ! after every single tick. @ also works in this mode.

Here is the code:

# coding: utf-8 class Instance attr_accessor :tape, :code, :ip OPERATORS = { '+'.ord => :inc, '-'.ord => :dec, '>'.ord => :right, '<'.ord => :left, '}'.ord => :scan_right, '{'.ord => :scan_left, '?'.ord => :toggle, ','.ord => :input, '.'.ord => :output, '!'.ord => :debug, '@'.ord => :debug_terminate } OPERATORS.default = :nop def initialize(src) @code = src.chars.map(&:ord) @code = [0] if code.empty? @ip = 0 end def tick result = :continue case OPERATORS[@code[@ip]] when :inc @tape.set(@tape.get + 1) when :dec @tape.set(@tape.get - 1) when :right @tape.move_right when :left @tape.move_left when :scan_right @tape.move_right while @tape.get != 0 when :scan_left @tape.move_left while @tape.ip > 0 && @tape.get != 0 when :toggle if @tape.get != 0 @tape.move_right result = :toggle end when :input input when :output output when :debug result = :debug when :debug_terminate result = :debug_terminate end return :terminate if result != :toggle && @ip == @code.size - 1 move_right if result != :toggle return result end def move_right @ip += 1 if @ip >= @code.size @code << 0 end end def move_right @ip += 1 if @ip >= @code.size @code << 0 end end def move_left @ip -= 1 if @ip > 0 end def get @code[@ip] end def set value @code[@ip] = value end def input() end def output() end def to_s str = self.class.name + ": \n" ip = @ip @code.map{|i|(i%256).chr}.join.lines.map do |l| str << l.chomp << $/ str << ' '*ip << "^\n" if 0 <= ip && ip < l.size ip -= l.size end str end end class Brian < Instance def input byte = STDIN.read(1) @tape.set(byte ? byte.ord : -1) end end class Chuck < Instance def output $> << (@tape.get % 256).chr end end class BrianChuck class ProgramError < Exception; end def self.run(src, debug_level=0) new(src, debug_level).run end def initialize(src, debug_level=false) @debug_level = debug_level src.gsub!('_',"\0") if src[/```/] brian, chuck = src.split('```', 2).map(&:strip) else brian, chuck = src.lines.map(&:chomp) end chuck ||= "" brian = Brian.new(brian) chuck = Chuck.new(chuck) brian.tape = chuck chuck.tape = brian @instances = [brian, chuck] end def run (puts @instances; puts) if @debug_level > 1 loop do result = current.tick toggle if result == :toggle (puts @instances; puts) if @debug_level > 1 || @debug_level > 0 && (result == :debug || result == :debug_terminate) break if result == :terminate || @debug_level > 0 && result == :debug_terminate end end private def current @instances[0] end def toggle @instances.reverse! end end case ARGV[0] when "-d" debug_level = 1 when "-D" debug_level = 2 else debug_level = 0 end if debug_level > 0 ARGV.shift end BrianChuck.run(ARGF.read, debug_level) 

Here is my own (handwritten) solution to the problem:

>}>}> brace left: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ arrow left: >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ brace left: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ arrow left: >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ question mk: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ >>>} Append a bunch of 1s as a dummy list element: +>+>+>+>+>+>+>+>+>+ Append two 1s and some code to the list; the first is a marker for later; the latter will be the integer 1: >+ brace right: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ arrow left: >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ question mk: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1: >+ brace right: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ arrow right: >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ brace right: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ arrow left: >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ question mk: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ _ {<<<<<<<<<{<{ Move back to the start Read a character and subtract 48 to get actual 0 or 1 ,------------------------------------------------ ? If 1 switch to Chuck otherwise continue >}>}>>>>>>>>>}<<<<<<- Subtract 1 from the result to account for initial 1 ? If integer was positive switch to Chuck @todo: process end _ This code is run when reading 1: }>}>>>>>>>>>}<<<<<<+ Move to the end of Chuck; skip one null cell; move to the end of the list {<<<<<<<<<{<? Move back to the code that resets this loop. _ This code is run after finalising an integer: change the code after the integer first <<--<--<--} Append two 1s and some code to the list; the first is a marker for later; the latter will be the integer 1: + brace right: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ arrow left: >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ question mk: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1: >+ brace right: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ arrow right: >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ brace right: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ arrow left: >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ question mk: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ {<<<<<<<+<<{<{ Move back to the start; incrementing the list length Read a character and subtract 48 to get actual 0 or 1 ,------------------------------------------------ ? If 1 switch to Chuck otherwise continue >}>}>>>>>>>>>} Leave the resetting code, but remove the rest of the last list element: <<<--<--<-- 1: <- question mk: <--------------------------------------------------------------- arrow left: <------------------------------------------------------------ brace right: <----------------------------------------------------------------------------------------------------------------------------- 1: <- <{< Move back to the cell we reserved for the counter <<<<<<-- decrement list size by two so we don't process the two largest elements _ <{<<<<<<{<{<{<{<{>}>}>>>>>>> This is the beginning of the loop which decrements all list elements by 1 + Add 1 to the running total >>- Set marker of dummy list element to zero _ Beginning of loop that is run for each list element <{<<<<<<{<{<{<{<{>}>}>}>}+ set last marker back to 1 >>>>>>>>>> move to next marker ? Skip the next bit if we're not at the end of the list <? Move back to the beginning of the loop @ we should never get here _ This is run when we're not at the end of the list <<<- Set marker to 0 to remember current position >>>>- Move to the current value and decrement it ? Move back to loop beginning if value is not zero yet - Make element negative so it's skipped in the future {<{<{>- Move to remaining list length and decrement it ? If it's nonzero switch to Chuck >>>>>>>>->->->->->->->->->- Remove the dummy list to make some space for new code: >}+ Fill in the marker in the list {<<<<<<<<< Add some loop resetting code after the result: brace left: +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ arrow left: >++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ question mk: >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ _ This loop adds a print command to Chuck's code while decrementing the result >>>>>>>>}>>>>>}>>>>>} Move to the very end of Chuck's code and leave some space to seek the 1 print: ++++++++++++++++++++++++++++++++++++++++++++++ {<<<<<{<<<<<{<<<<<<<{< - decrement the result ? if nonzero run loop again At this point we just need to make Chuck seek for the 1 at the end of this code print it often enough >>}>>>>>>>}>>>>>} arrow right: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ <?1 Switch to Chuck which moves Brian's tape head onto the 1 and then prints it N times ``` _ Dummy cell to hold input This code is run when reading a 1: {<{<{<{ ensure we're at the start }>}<? Skip 0 handling code and switch to Brian _ This code is run after a 1 has been processed: {<{<? 

This code is runnable as is, because all the annotations use no-ops and are skipped by { and }.

The basic idea is:

  1. Add a new zero-element to the list (at the end of Chuck's tape) and increase the list length by 1.
  2. While we're reading 1s, increment that element.
  3. When we're reading a 0, do some cleanup. If the resulting integer was greater than zero, go back to 1.

    Now we've got a list of the input numbers at the end of Chuck's tape and we know the length of the list.

  4. Subtract 2 from the length of the list (so the following steps ignore the two largest elements), call this N.

  5. While N > 0, increment a running total and then decrement all list elements. Whenever a list element hits zero, decrement N.

    At the end of this, the running total will contain the third-largest number in the input, M.

  6. Write M copies of . to the end of Chuck's tape.

  7. On Chuck, search for a 1 on Brian's tape and then execute those generated . at the end.

After finishing this I realised that I could simplify it quite a lot in some places. For instance, instead of keeping track of that counter and writing those . to Chuck's tape I could just print a 1 right away, each time before I decrement all the list elements. However, making changes to this code is quite a pain, because it propagates other changes all over the place, so I didn't bother making the change.

The interesting bit is how to keep track of the list and how to iterate through it. You can't just store the numbers back-to-back on Chuck's tape, because then if you want to check a condition on one of the list elements, you risk executing the remainder of the list which might contain valid code. You also don't know how long the list will be, so you can't just reserve some space in front of Chuck's code.

The next problem is that we need to leave the list to decrement N while we're processing it, and we need to get back to the same place we were before. But { and } would just skip past the entire list.

So we need to write some code dynamically onto Chuck. In fact, each list element i has the form:

[1 <Code A> i <Code B>] 

1 is a marker which we can set to zero to indicate where we left off processing the list. Its purpose is to catch { or } which will just pass over the code and the i. We also use this value to check if we're at the end of the list during processing, so while we're not, this will be 1 and the conditional ? will switch control to Chuck. Code A is used to handle that situation and move the IP on Brian accordingly.

Now when we decrement i we need to check whether i is already zero. While it is not, ? will again switch control, so Code B is there to handle that.

\$\endgroup\$
2
  • \$\begingroup\$ Cracked \$\endgroup\$ Commented Nov 9, 2015 at 7:30
  • \$\begingroup\$ @cardboard_box Nice! \$\endgroup\$ Commented Nov 10, 2015 at 2:07
26
\$\begingroup\$

Changeling (safe)

ShapeScript

ShapeScript is a naturally occurring programming language. Shape shifters (or Changelings, as they prefer to be called) can transform into a set of instructions that allows them to process data.

ShapeScript is a stack-based language with a relatively simple syntax. Unsurprisingly, most of its built-ins deal with altering the shape of strings. It is interpreted, character by character, as follows:

  • ' and " begin a string literal.

    Until the matching quote is found in the source code, all characters between these matching quotes are collected in a string, which is then pushed on the stack.

  • 0 to 9 push the integers 0 to 9 on the stack. Note that 10 pushes two integers.

  • ! pops a string from the stack, and attempts to evaluate it as ShapeScript.

  • ? pops an integer from the stack, and pushes a copy of the stack item at that index.

    Index 0 corresponds to the topmost stack item (LIFO) and index -1 to the bottom-most one.

  • _ pops an iterable from the stack, and pushes its length.

  • @ pops two items from the stack, and pushes them in reversed order.

  • $ pops two strings from the stack, and splits the bottom-most one at occurrences of the topmost one. The resulting list is pushed in return.

  • & pops a string (topmost) and an iterable from the stack, and joins the iterable, using the string as separator. The resulting string is pushed in return.

  • If ShapeScript is used on our planet, since pythons are the Changelings closest relatives on Earth, all other characters c pop two items x and y (topmost) from the stack, and attempt to evaluate the Python code x c y.

    For example, the sequence of characters 23+ would evaluate 2+3, while the sequence of characters "one"3* would evaluate 'one'*3, and the sequence of characters 1''A would evaluate 1A''.

    In the last case, since the result is not valid Python, the Changeling would complain that his current shape is unpurposed (syntax error), since it is not valid ShapeScript.

Before executing the code, the interpreter will place the entire user input in form of a string on the stack. After executing the source code, the interpreter will print all items on the stack. If any part in between fails, the Changeling will complain that his current shape is inadequate (runtime error).

Shape shifting

In their natural state, Changelings do not take the shape of ShapeScript. However, some of them can transform into one potential source code (which isn't necessarily useful or even syntactically valid).

All eligible Changelings have the following natural form:

  • All lines must have the same number of characters.

  • All lines must consist of printable ASCII characters, followed by a single linefeed.

  • The number of lines must match the number of printable characters per line.

For example, the byte sequence ab\ncd\n is an eligible Changeling.

In its attempt to shift into ShapeScript, the Changeling undergoes the following transformation:

  • Initially, there is no source code.

  • For each line the following happens:

    • The Changeling's accumulator is set to zero.

    • For each character c of the line (including the trailing linefeed), the code point of c is XORed with the accumulator divided by 2, and the Unicode character that corresponds to the resulting code point is appended to the source code.

      Afterwards, the difference between the code point of c and the code point of a space (32) is added to the accumulator.

If any part of the above fails, the Changeling will complain that its current shape is unpleasant.

After all lines have been processed, the Changeling's transformation into (hopefully valid) ShapeScript is complete, and the resulting code is executed.

Solution (ShapeScript)

"0"@"0"$"0"2*&"0"@+0?_'@1?"0"$_8>"1"*+@1?"0"+$""&'*!# 

ShapeScript actually turned out to be quite usable; it can even perform primality testing (proof) and therefore satisfies our definition of programming language.

I have re-published ShapeScript on GitHub, with a slightly modified syntax and better I/O.

The code does the following:

"0"@ Push "0" (A) and swap it with the input string (S). "0"$ Split S at 0's. "0"2* Push "00". & Join the split S, using "00" as separator. "0"@+ Prepend "0" to S. The input has been transformed into "0<run of 1's>000<run of 1's>0...0<run of 1's>0000". 0?_ Push a copy and compute its length (L). ' Push a string that, when evaluated, does the following: @1? Swap A on top of S, and push a copy of S. "0"$ Split the copy of S at 0's. _8> Get the length of the resulting array and compare it with 8. If this returns True, there are more than eight chunks, so there are more then seven 0's. With two 0's surrounding each run of 1's and three extra 0's at the end, this means that there still are three or more runs of 1's in the string. "1"* Push "1" if '>' returned True and "" if it returned False. + Append "1" or "" to A. @1? Swap S on top of A, and push a copy of A. "0"+ Append "0" to the copy of A. $ Split S at occurrences of A+"0". ""& Flatten the resulting array of strings. ' This removes all occurrences of "010" in the first iteration, all occurrences of "0110" in the second, etc., until there are less than three runs of 1's left in S. At this point, A is no longer updated, and the code inside the string becomes a noop. *! Repeat the code string L times and evaluate. # Drop S, leaving only A on the stack. 

Solution (Changeling)

"1+-....................... ""B1....................... "" 2)+7.................... "" 2)=<.................... ""( $86=................... ""B8=...................... ""247...................... ""]`b...................... ""B1....................... ""%D1=..................... ""%500=.................... ""%&74?.................... ""%&#.2.................... ""%[bdG.................... ""%D1=..................... ""%<5?..................... ""%:6>..................... ""%&65?.................... ""%&-%7>................... ""%D1=..................... ""%500=.................... ""%&74?.................... ""%&,)5>................... ""%&%,79................... "" )$?/=................... ""),-9=.................... ""# !...................... 

Like all Changeling programs, this code has a trailing linefeed.

ShapeScript will error immediately on any character is does not understand, but we can push arbitrary strings as fillers and pop them later. This allows us to put only a small amount of code on each line (at the beginning, where the accumulator is small), followed by an opening ". If we begin the next line with "#, we close and pop the string without affecting the actual code.

In addition, we have to overcome three minor obstacles:

  • The long string in the ShapeScript code is a single token, and we won't be able to fit it on a line.

    We will push this string in chunks ('@', '1?', etc.), which we'll concatenate later.

  • The code point of _ is rather high, and pushing '_' will be problematic.

    However, we'll be able to push '_@' effortlessly, followed by another '@' to undo the swap.

The ShapeScript code our Changeling will generate looks like this1:

"0"" "#@" "#"0"$" "#"0"2" "#*&"0"" "#@+" "#0?" "#_@" "#@" "#'@'" "#'1?'" "#'"0'" "#'"$'" "#'_@'" "#'@'" "#'8'" "#'>'" "#'"1'" "#'"*+'" "#'@'" "#'1?'" "#'"0'" "#'"+$'" "#'""&'" "#"+"77" "#+*!*" "#!#" 

I found the Changeling code by running the above ShapeScript code through '""#'"1'""#'"*+'""#'@'""#'1?'""#'"0'""#'"+$'""#'""&'""#"+"77""#+*!*""#!#"" rel="noreferrer">this converter.2

Interpreter (Python 3)

#!/usr/bin/env python3 import fileinput def error(code): print("This shape is " + ["unpleasant", "unpurposed", "inadequate"][code - 1] + ".") exit(code) def interpret(code): global stack stringing = 0 for char in code: quote = (char == "'") + 2 * (char == '"') if quote or stringing: if not stringing: string = "" stringing = quote elif stringing == quote: stack.append(string) stringing = 0 else: string += char elif char in "0123456789": stack.append(int(char)) else: x = stack.pop() if char == '!': interpret(x) else: if char == '?': y = stack[~x] elif char == "_": y = len(x) else: y = stack.pop() if char == '@': stack.append(x) elif char == '$': y = y.split(x) elif char == '&': y = x.join(map(str, y)) else: try: y = eval(repr(y) + char + repr(x)) except SyntaxError: error(2) stack.append(y) source = "" lengths = [] for line in fileinput.input(): if not line or sorted(line)[:2][-1] < " " or max(line) > "~": error(1) lengths.append(len(line)) accumulator = 0 for char in line: value = ord(char) try: source += chr(value ^ (accumulator >> 1)) except: error(1) accumulator += value - 32 lengths.append(len(lengths) + 1) if min(lengths) != max(lengths): error(1) stack = "" for line in fileinput.input("-"): stack += line stack = [stack] try: interpret(source) except: error(3) print("".join(map(str, stack))) 

1 Each line is padded with random garbage to the amount of lines, and the linefeeds aren't actually present.
2 The numbers at the bottom indicate the lowest and highest code point in the Changeling code, which must stay between 32 and 126.

\$\endgroup\$
14
  • 1
    \$\begingroup\$ -1 for use of xor/transformations. The changeling to ShapeScript conversion looks to much like encryption to me. \$\endgroup\$ Commented Nov 4, 2015 at 16:45
  • 15
    \$\begingroup\$ @MegaTom You can vote as you see fit, but the questions frowns upon encryption because it takes a key, a constant only known to the cop that puts the robbers at a significant disadvantage. The conversion is an unkeyed transformation. \$\endgroup\$ Commented Nov 4, 2015 at 17:56
  • 1
    \$\begingroup\$ ShapeScript, 67 bytes: 0"#002?'+'&'0'$'0?2?-@2?>*+00'&!++'1'*'0'+@1?$0?''&@_2-2?*@+@"3*!@#. I've given up on finding a Changeling for it, though. Even interspersed with mostly useless statements, I haven't been able to get more than 20 bytes in. \$\endgroup\$ Commented Nov 5, 2015 at 5:41
  • 2
    \$\begingroup\$ @MegaTom I'm actually fairly disappointed by the given solution. I was expecting something significantly more clever than 92.9% useless code. \$\endgroup\$ Commented Nov 6, 2015 at 15:10
  • 2
    \$\begingroup\$ @primo It took a bit more tinkering, but I found this Changeling that works with Python 2 as well. I don't know how clever my answer is, but my plan of posting a cop with a loophole that had to be found to crack it seems to have worked. \$\endgroup\$ Commented Nov 6, 2015 at 15:28
20
\$\begingroup\$

HPR, written in Python 3 (Cracked by TheNumberOne)

HPR (the name means nothing) is a language for processing lists of integers. It is designed to be minimalistic, extremely limited, and free of "artificial" restrictions. Programming in HPR is painful, not because you have to solve a puzzle to keep the interpreter from shouting at you, but because it's hard to make a program do anything useful. I don't know whether HPR is capable of anything substantially more interesting than computing the third largest element of a list.

Language specification

An HPR program is run in an environment, which is an unordered duplicate-free set of nonnegative integers and lists of nonnegative integers. Initially, the environment contains only the input list (the interpreter parses it for you). There are three commands and two "control flow" operators that modify the environment:

  • * removes the first element of every non-empty list in the environment, and places it in the environment. Empty lists are not affected. For example, it transforms

    {4,1,[0,2],[1,3],[]} -> {4,1,0,[2],[3],[]} 
  • - decrements all numbers in the environment, and then removes negative elements. For example, it transforms

    {4,2,0,[0,2],[4,4,4]} -> {3,1,[0,2],[4,4,4]} 
  • $ rotates every list in the environment one step to the left. For example, it transforms

    {4,1,[0,2],[3,4,1]} -> {4,1,[2,0],[4,1,3]} 
  • !(A)(B), where A and B are programs, is basically a while loop. It performs the "action" A until the "test" B would result in an empty environment. This may produce an infinite loop.
  • #(A)(B), where A and B are programs, applies A and B to the current environment and takes the symmetric difference of the results.

The commands are executed from left to right. At the end, the size of the environment is printed in unary.

The interpreter

This interpreter features the debug command ?, which prints the environment without modifying it. It should not appear in any solution to the task. Any characters except *-$!#()? are simply ignored, so you can write comments directly into the code. Finally, the interpreter recognizes the idiom !(A)(#(A)()) as "perform A until the result no longer changes", and optimizes it for extra speed (I needed it to get my solution to finish in under an hour on the last test case).

import sys def parse(prog): "Parse a prefix of a string into an AST. Return it and the remaining input." ret = [] while prog: if prog[0] in "#!": sub1, prog1 = parse(prog[2:]) sub2, prog2 = parse(prog1[1:]) ret += [prog[0],sub1,sub2] prog = prog2 elif prog[0] == ')': prog = prog[1:] break else: ret += [prog[0]] prog = prog[1:] return ret, prog def intp(ast, L_env, N_env): "Interpret the AST on an environment, return the resulting environment." ptr = 0 while ptr < len(ast): cmd = ast[ptr] if cmd == '*': N_env = N_env | set(L[0] for L in L_env if L) L_env = set(L[1:] for L in L_env) ptr += 1 elif cmd == '-': N_env = set(N-1 for N in N_env if N>0) ptr += 1 elif cmd == '$': L_env = set(L[1:]+L[:1] for L in L_env) ptr += 1 elif cmd == '!': # Speed optimization cond = ast[ptr+2] if cond == ['#', ast[ptr+1], []]: L_next, N_next = intp(ast[ptr+1], L_env, N_env) while L_next != L_env or N_next != N_env: L_env, N_env = L_next, N_next L_next, N_next = intp(ast[ptr+1], L_env, N_env) else: while True: L_test, N_test = intp(cond, L_env, N_env) if not L_test and not N_test: break L_env, N_env = intp(ast[ptr+1], L_env, N_env) ptr += 3 elif cmd == '#': L_env1, N_env1 = intp(ast[ptr+1], L_env, N_env) L_env2, N_env2 = intp(ast[ptr+2], L_env, N_env) L_env = L_env1 ^ L_env2 N_env = N_env1 ^ N_env2 ptr += 3 elif cmd == '?': print(L_env | N_env) ptr += 1 else: ptr += 1 return L_env, N_env def main(p, L): "Run the program on the input, return the output." # Parse the input list L = ''.join(c for c in L if c in "01") while "00" in L: L = L.replace("00","0") L = [-2] + [i for i in range(len(L)-1) if L[i:i+2] == "10"] L = tuple(b-a-1 for (a,b) in zip(L, L[1:])) # Parse the program ast = parse(p)[0] L_env, N_env = intp(ast, set([L]), set()) return '1'*(len(L_env) + len(N_env)) if __name__ == "__main__": filename = sys.argv[1] f = open(filename, 'r') prog = f.read() f.close() L = input() print(main(prog, L)) 

My solution

My reference solution is 484 bytes long, so quite short compared to TheNumberOne's 3271-byte program. This is most likely because of the sophisticated and awesome macro system TheNumberOne developed for programming in HPR. The main idea in both our programs is similar:

  1. Find out how to produce the maximum element of a list.
  2. To remove the maximum element, rotate the list until the first element equals the maximum, then pop it.
  3. Remove the maximum twice, then print the new maximum element.

However, as far as I can tell, the exact implementation details are quite different. Here's my solution:

!($)(!(-)(#(-)())#(!(-#(!(*)(#(*)())#(!(-)(#(-)()))())(!(-)(#(-)())))(#(-#(!(*)(#(*)())#(!(-)(#(-)()))())(!(-)(#(-)())))())#(-)(#(!(-)(#(-)()))()))(*)#(!(-)(#(-)()))())*!(-)(#(-)())!($)(!(-)(#(-)())#(!(-#(!(*)(#(*)())#(!(-)(#(-)()))())(!(-)(#(-)())))(#(-#(!(*)(#(*)())#(!(-)(#(-)()))())(!(-)(#(-)())))())#(-)(#(!(-)(#(-)()))()))(*)#(!(-)(#(-)()))())*!(-)(#(-)())!(-#(!(*)(#(*)())#(!(-)(#(-)()))())(!(-)(#(-)())))(#(-#(!(*)(#(*)())#(!(-)(#(-)()))())(!(-)(#(-)())))())-#(!(-)(#(-)()))() 

And here's the commented Python program that produces it (nothing fancy here, just basic string manipulation to get rid of all the repetition):

# No numbers in the environment no_nums = "#(-)()" # No non-empty lists in the environment no_lists = "#(*)()" # Remove all numbers from the environment del_nums = "!(-)(" + no_nums + ")" # Remove all lists from the environment del_lists = "#(" + del_nums + ")()" # Splat the list to the environment: # pop the list until it's empty and delete the empty list, # then take symmetric difference with all numbers removed splat = "#(!(*)(" + no_lists + ")" + del_lists + ")(" + del_nums + ")" # Put into the environment all numbers up to list maximum: # decrement and splat until a fixed point is reached. # Without the speed optimization, this is very slow if the list elements are large. splat_upto = "!(-" + splat + ")(#(-" + splat + ")())" # Copy maximum element to environment: # take all elements up to maximum, # then take symmetric difference of decrement and list deletion copy_max = splat_upto + "#(-)(" + del_lists + ")" # Delete maximum element from list: # rotate until first element is maximal, then pop and delete it del_max = "!($)(" + del_nums + "#(" + copy_max + ")(*)" + del_lists + ")*" + del_nums # Full program: # remove the maximum twice, then produce all numbers up to maximum, # then decrement and remove lists. The environment will contain exactly # the integers from 0 to third largest - 1, and their number is printed. main = del_max + del_max + splat_upto + "-" + del_lists print(main) 
\$\endgroup\$
2
  • \$\begingroup\$ Cracked!!! \$\endgroup\$ Commented Oct 31, 2015 at 18:26
  • \$\begingroup\$ @TheNumberOne Added my solution. \$\endgroup\$ Commented Nov 2, 2015 at 1:05
15
\$\begingroup\$

Acc!! (safe)

It's baack...

enter image description here

... and hopefully definitely more loophole-proof.

I already read the Acc! spec. How is Acc!! different?

In Acc!!, loop variables go out of scope when the loop exits. You can only use them inside the loop. Outside, you'll get a "name is not defined" error. Other than this change, the languages are identical.

Statements

Commands are parsed line by line. There are three types of command:

  1. Count <var> while <cond>

Counts <var> up from 0 as long as <cond> is nonzero, equivalent to C++ for(int <var>=0; <cond>; <var>++). The loop counter can be any single lowercase letter. The condition can be any expression, not necessarily involving the loop variable. The loop halts when the condition's value becomes 0.

Loops require K&R-style curly braces (in particular, the Stroustrup variant):

Count i while i-5 { ... } 

As mentioned above, loop variables are only available inside their loops; attempting to reference them afterwards causes an error.

  1. Write <charcode>

Outputs a single character with the given ASCII/Unicode value to stdout. The charcode can be any expression.

  1. Expression

Any expression standing by itself is evaluated and assigned back to the accumulator (which is accessible as _). Thus, e.g., 3 is a statement that sets the accumulator to 3; _ + 1 increments the accumulator; and _ * N reads a character and multiplies the accumulator by its charcode.

Note: the accumulator is the only variable that can be directly assigned to; loop variables and N can be used in calculations but not modified.

The accumulator is initially 0.

Expressions

An expression can include integer literals, loop variables (a-z), _ for the accumulator, and the special value N, which reads a character and evaluates to its charcode each time it is used. Note: this means you only get one shot to read each character; the next time you use N, you'll be reading the next one.

Operators are:

  • +, addition
  • -, subtraction; unary negation
  • *, multiplication
  • /, integer division
  • %, modulo
  • ^, exponentiation

Parentheses can be used to enforce precedence of operations. Any other character in an expression is a syntax error.

Whitespace and comments

Leading and trailing whitespace and empty lines are ignored. Whitespace in loop headers must be exactly as shown, with a single space between the loop header and opening curly brace as well. Whitespace inside expressions is optional.

# begins a single-line comment.

Input/Output

Acc!! expects a single line of characters as input. Each input character can be retrieved in sequence and its charcode processed using N. Trying to read past the last character of the line causes an error. A character can be output by passing its charcode to the Write statement.

Interpreter

The interpreter (written in Python 3) translates Acc!! code into Python and execs it.

import re, sys def main(): if len(sys.argv) != 2: print("Please supply a filename on the command line.", file=sys.stderr) return codeFile = sys.argv[1] with open(codeFile) as f: code = f.readlines() code = translate(code) exec(code, {"inputStream": (ord(char) for char in input())}) def translate(accCode): indent = 0 loopVars = [] pyCode = ["_ = 0"] for lineNum, line in enumerate(accCode): if "#" in line: # Strip comments line = line[:line.index("#")] line = line.strip() if not line: continue lineNum += 1 if line == "}": if indent: loopVar = loopVars.pop() pyCode.append(" "*indent + loopVar + " += 1") indent -= 1 pyCode.append(" "*indent + "del " + loopVar) else: raise SyntaxError("Line %d: unmatched }" % lineNum) else: m = re.fullmatch(r"Count ([a-z]) while (.+) \{", line) if m: expression = validateExpression(m.group(2)) if expression: loopVar = m.group(1) pyCode.append(" "*indent + loopVar + " = 0") pyCode.append(" "*indent + "while " + expression + ":") indent += 1 loopVars.append(loopVar) else: raise SyntaxError("Line %d: invalid expression " % lineNum + m.group(2)) else: m = re.fullmatch(r"Write (.+)", line) if m: expression = validateExpression(m.group(1)) if expression: pyCode.append(" "*indent + "print(chr(%s), end='')" % expression) else: raise SyntaxError("Line %d: invalid expression " % lineNum + m.group(1)) else: expression = validateExpression(line) if expression: pyCode.append(" "*indent + "_ = " + expression) else: raise SyntaxError("Line %d: invalid statement " % lineNum + line) return "\n".join(pyCode) def validateExpression(expr): "Translates expr to Python expression or returns None if invalid." expr = expr.strip() if re.search(r"[^ 0-9a-z_N()*/%^+-]", expr): # Expression contains invalid characters return None elif re.search(r"[a-zN_]\w+", expr): # Expression contains multiple letters or underscores in a row return None else: # Not going to check validity of all identifiers or nesting of parens-- # let the Python code throw an error if problems arise there # Replace short operators with their Python versions expr = expr.replace("^", "**") expr = expr.replace("/", "//") # Replace N with a call to get the next input character expr = expr.replace("N", "inputStream.send(None)") return expr if __name__ == "__main__": main() 

Solution

The downfall of the original Acc! was loop variables that continued to be accessible outside of their loops. This allowed saving copies of the accumulator, which made the solution far too easy.

Here, without this loophole (sorry), we have only the accumulator to store stuff. To solve the task, though, we need to store four arbitrarily large values.1 The solution: interleave their bits and store the resulting combination number in the accumulator. For example, an accumulator value of 6437 would store the following data (using the lowest-order bit as a single-bit flag):

1100100100101 Binary representation of accumulator 321032103210F Key The flag is 1 and the four numbers are Number 0: 010 = 2 Number 1: 001 = 1 Number 2: 100 = 4 Number 3: 110 = 6 

We can access any bit of any number by dividing the accumulator by the appropriate power of 2, mod 2. This also allows for setting or flipping individual bits.

At the macro level, the algorithm loops over the unary numbers in the input. It reads a value into number 0, then does a pass of the bubble sort algorithm to place it in proper position compared to the other three numbers. Finally, it discards the value that's left in number 0, as it is the smallest of the four now and can never be third-largest. When the loop is over, number 1 is the third-largest, so we discard the others and output it.

The hardest part (and the reason I had global loop variables in the first incarnation) is comparing two numbers to know whether to swap them. To compare two bits, we can turn a truth table into a mathematical expression; my breakthrough for Acc!! was finding a comparison algorithm that proceeded from low-order to high-order bits, since without global variables there's no way to loop over a number's bits from left to right. The lowest-order bit of the accumulator stores a flag that signals whether to swap the two numbers currently under consideration.

I suspect that Acc!! is Turing-complete, but I'm not sure I want to go to the trouble of proving it.

Here's my solution with comments:

# Read and process numbers until the double 0 Count y while N-48 { # Read unary number and store it (as binary) in number 0 # The above loop header consumed the first 1, so we need to add 1 to number 0 _ + 2 # Increment number 0 for each 1 in input until next 0 Count z while N-48 { # In this context, the flag indicates a need to carry; set it to 1 _ - _%2 + 1 # While carry flag is set, increment the next bit in line Count i while _%2 { # Set carry flag to 1 if i'th bit is 1, 0 if it's 0 # Set i'th bit to 1 if it was 0, 0 if it was 1 _ - _%2 + _/2^(i*4+1)%2 + (-1)^(_/2^(i*4+1)%2)*2^(i*4+1) } } # Bubble number 0 upwards Count n while n-3 { # The flag (rightmost bit of accumulator) needs to become 1 if we want to swap # numbers (n) and (n+1) and 0 if we don't # Iterate over bit-groups of accumulator, RTL Count i while _/2^(i*4+1) { # Adjust the flag as follows: # _/2^(i*4+n+1)%4 is the current bit of number (n+1) followed by the current # bit of number (n), a value between 0 and 3 # - If this quantity is 1 (01), number (n) is bigger so far; set flag to 1 # - If this quantity is 2 (10), number (n+1) is bigger so far; set flag to 0 # - If this quantity is 0 (00) or 3 (11), the two bits are the same; keep # current value of flag _ + (_/2^(i*4+n+1)%4%3 + 1)/2*(_/2^(i*4+n+1)%4%3%2 - _%2) } # Now swap the two if the flag is 1 Count i while (_%2)*(_/2^(i*4+1)) { # _/2^(i*4+n+1)%2 is the current bit of number (n) # _/2^(i*4+n+2)%2 is the current bit of number (n+1) _ - (_/2^(i*4+n+1)%2)*2^(i*4+n+1) - (_/2^(i*4+n+2)%2)*2^(i*4+n+2) + (_/2^(i*4+n+2)%2)*2^(i*4+n+1) + (_/2^(i*4+n+1)%2)*2^(i*4+n+2) } } # Discard number 0, setting it to all zeros for the next iteration Count i while _/2^(i*4+1) { _ - _/2^(i*4+1)%2*2^(i*4+1) } } # Once the loop is over, all input has been read and the result is in number 1 # Divide by 2 to get rid of flag bit _ / 2 # Zero out numbers 2 and 3 Count i while _/2^(i*4) { _ - _/2^(i*4+2)%2*2^(i*4+2) } Count i while _/2^(i*4) { _ - _/2^(i*4+3)%2*2^(i*4+3) } # Move bits of number 1 down to their final locations Count i while _/2^i { _ - _/2^(i*4+1)%2*2^(i*4+1) + _/2^(i*4+1)%2*2^i } # _ now contains the correct answer in decimal; to output in unary: Count z while z-_ { Write 49 } 

1 According to the question spec, only values up to 1 million need to be supported. I'm glad nobody took advantage of that for an easier solution--though I'm not entirely sure how you would do the comparisons.

\$\endgroup\$
1
  • \$\begingroup\$ LOL @ the Bill the Cat picture \$\endgroup\$ Commented Nov 3, 2015 at 20:58
14
\$\begingroup\$

TKDYNS (To Kill the Dragon, You Need a Sword) - Cracked by Martin Büttner

EDIT: I've added my solution and explanation below the main post.

Background

In this language, you take control of a valiant warrior who has been tasked with slaying a terrible dragon. The dragon lives in an underground labyrinth, full of perils and dangers, and until now, no-one has been able to map it out and survive. This means you must navigate your way to the dragon in pitch darkness, with nothing but intuition and bravery to guide you.

...

Well, not quite. You've brought an almost limitless supply of disposable minions with you, and they are willing to go ahead of you to discover the safe route. Unfortunately, they're all as thick as two short planks, and will only do exactly what you tell them to do. It's up to you to come up with a clever method of ensuring that your minions discover the correct path.

Some more details

The dragon's lair takes the form of a 10x10 grid. Between certain adjacent points in the grid, there is a narrow walkway; between others, there is a deep chasm and certain death. An example layout for a 4x4 grid might be as follows:

 0---1 2---3 | | | 4---5---6 7 | | 8 9--10 11 | | 12--13--14--15 

You know that there is always a way to get from one point to any other point, but no more than that has been revealed to you.

To successfully defeat the dragon, you first need to collect some items which you will be able to combine together to create a magical dragon-slaying blade. Conveniently, all the pieces for this weapon have been scattered around the dragon's lair. You simply have to collect them.

The twist is that each piece of the weapon has been booby-trapped. Every time one is collected, the layout of the walkways changes. Previously safe paths might now lead to certain death, and vice-versa.

Commands

There are only 5 valid commands.

  • < - Take a step to the left

  • > - Take a step to the right

  • ^ - Take a step upwards

  • v - Take a step downwards

  • c - Collect any items that happen to be lying around at your current position. If there were items present, the layout of the lair changes. With positions numbered in rows as above, take your position modulo 10. There are 10 layouts hard-coded into the interpreter, and the layout changes to the corresponding one. For example, if you are at position 13, then the layout changes to layouts[3]

The layouts as they appear in the interpeter have been encoded to integers in the following way:

  • The empty layout is encoded to zero.

  • For each edge in the layout, let x be the smaller of the two positions that it joins.

    • If the step is horizontal, add 2^(2*x) to the encoding (that's power-of, not XOR)

    • If the step is vertical, add 2^(2*x + 1) to the encoding.

Execution flow

The interpreter is run with the name of a source file as a command-line argument.

When the interpreter is run, it will prompt the user to provide a quest. This input should be in the form described in the question, and determines the locations in the lair of the components of the weapon. Specifically, each input integer is taken modulo 100, and placed at the corresponding location in the lair.

Each source program consists of several lines, each line consisting of some sequence of the above 5 valid characters. These lines represent your minions. You, the warrior, keep track of a sequence of actions known to be safe. Initially, you know nothing about the lair, so this sequence is empty. Taking each minion in turn, the following is performed, starting from position 0:

  • The minion is instructed to perform all the known-to-be-safe actions, followed by the actions in its own line of code.

    • If the minion dies at any point, you are notified of this, and the lair resets to its initial configuration. All items are replaced, and the walkways return to their starting positions.

    • If, instead, the minion survives, then you vaporize it anyway - it's only a minion, after all. As before, this triggers the resetting of the lair to its initial state, but this time, you append the actions from the minion's line of code to the sequence of known-to-be-safe actions.

Once all minions have been exhausted, you, the warrior, perform all of the actions that are known to be safe, again starting from position 0. There are two possible outcomes:

  • You collect all the pieces of the weapon - in this case, you successfully slay the dragon, and an exciting victory message is output. This victory message will contain, among other characters, n ones, where n is the third highest number provided as input.

  • You have failed to collect some of the pieces of the weapon - in this case, the dragon lives on, and you have failed in your quest.

Interpreter code (Python 2)

import collections import copy import sys size = 10 layouts = [108705550239329248440770931020110627243913363144548922111951,108386637020100924277694952798729434993885646706222210602969,133885860318189115027934177821170081234850573770998325845482,102397795295522647918061101991513921833376565032742993744791,131948019244359669407266662537098175265242405785636894694611,127512068876349726396523358265982765442984953916545984706958,106817519055019354200334114020150263381328246524221867629943,33472343358375525802921815863230485208221126168622186265959,133909781123963725874096031069972704024813281938330035579187,132244654022332695610020359820518831299843076834682749020986] class Interpreter(object): def __init__(self, source_file): self.source_filename = source_file self.layout = layouts[0] self.position = 0 self.ingredients = [] self.quest_items = {} self.attempts = collections.deque() self.safe_position = 0 self.safe_ingredients = [] self.safe_quest_items = {} self.safe_layout = layouts[0] def get_input(self): s = raw_input("Which quest would you like to embark on today?") ind = s.find('00') s = s[:ind] items = map(len, s.split('0')) for item in items: if item not in self.safe_quest_items: self.safe_quest_items[item] = 1 else: self.safe_quest_items[item] += 1 def parse_attempts(self): with open(self.source_filename, 'r') as source: for line in source: self.attempts.append(line.strip()) def enc(self, x, y): x, y = min(x, y), max(x, y) if x < 0: return 0 if y == x + 1: return 1 << (2*x) elif y == x + size: return 1 << (2*x + 1) else: return 0 def exec_command(self, char): if char == '<': if self.enc(self.position, self.position-1) & self.layout: self.position -= 1 else: return False elif char == '>': if self.enc(self.position, self.position+1) & self.layout: self.position += 1 else: return False elif char == '^': if self.enc(self.position, self.position-size) & self.layout: self.position -= size else: return False elif char == 'v': if self.enc(self.position, self.position+size) & self.layout: self.position += size else: return False elif char == 'c': for item in xrange(self.position, 10**6, size*size): if item in self.quest_items: self.ingredients += ([item] * self.quest_items.pop(item)) self.ingredients.sort() self.layout = layouts[self.position % 10] else: raise ValueError return True def run_minion(self): minion = self.attempts.popleft() self.position = self.safe_position self.ingredients = copy.copy(self.safe_ingredients) self.quest_items = copy.copy(self.safe_quest_items) self.layout = self.safe_layout dead = False for cmd in minion: if not self.exec_command(cmd): dead = True if not dead: self.safe_position = self.position self.safe_ingredients = copy.copy(self.ingredients) self.safe_quest_items = copy.copy(self.quest_items) self.safe_layout = self.layout def process_minions(self): while self.attempts: self.run_minion() def final_run(self): if len(self.safe_quest_items) == 0: print "You killed the dragon" + "!!1" * self.safe_ingredients[-3] else: print "The dragon lives on. Better luck next time..." def interpret(self): self.get_input() self.parse_attempts() self.process_minions() self.final_run() if __name__ == "__main__": if len(sys.argv) != 2: print "Wrong number of arguments" else: test = Interpreter(sys.argv[1]) test.interpret() 

Best of luck, valiant warrior.

My solution and explanation

Here's a Python script that generates source code to solve the problem. For interest, Martin's final source code is about 5 times smaller than the code produced by my script. On the other hand, my script to generate the code is about 15 times smaller than Martin's Mathematica program...

size = 10 layouts = [108705550239329248440770931020110627243913363144548922111951,108386637020100924277694952798729434993885646706222210602969,133885860318189115027934177821170081234850573770998325845482,102397795295522647918061101991513921833376565032742993744791,131948019244359669407266662537098175265242405785636894694611,127512068876349726396523358265982765442984953916545984706958,106817519055019354200334114020150263381328246524221867629943,33472343358375525802921815863230485208221126168622186265959,133909781123963725874096031069972704024813281938330035579187,132244654022332695610020359820518831299843076834682749020986] def enc(edge): x,y = min(edge), max(edge) if x < 0: return 0 if y == x + 1: return 1 << (2*x) elif y == x + size: return 1 << (2*x + 1) def path(x, y, tree): stack = [(x,'')] while stack[-1][0] != y: vertex, path = stack.pop() if (enc((vertex, vertex+1))&tree) and ((not path) or path[-1] != '<'): stack.append((vertex+1, path+'>')) if (enc((vertex, vertex-1))&tree) and ((not path) or path[-1] != '>'): stack.append((vertex-1, path+'<')) if (enc((vertex, vertex+size))&tree) and ((not path) or path[-1] != '^'): stack.append((vertex+size, path+'v')) if (enc((vertex, vertex-size))&tree) and ((not path) or path[-1] != 'v'): stack.append((vertex-size, path+'^')) return stack[-1][1] def reverse(path): rev = '' for i in xrange(len(path)-1, -1 ,-1): if path[i] == '<': rev += '>' if path[i] == '>': rev += '<' if path[i] == 'v': rev += '^' if path[i] == '^': rev += 'v' return rev def assert_at(x, tree): path_ul = path(x, 0, tree) path_dr = path(x, size*size - 1, tree) return path_ul + reverse(path_ul) + path_dr + reverse(path_dr) def safe_path(x, y, tree): return assert_at(x, tree) + path(x, y, tree) with open('source.txt', 'w') as f: for x in xrange(size*size - 1): for tree in layouts: f.write(safe_path(x, x+1, tree) + 'c\n') 

Basic structure

This generates 990 lines of source code, which come in groups of 10. The first 10 lines all contain instructions that attempt to move a minion from position 0 to position 1, and then collect an item - one set for each possible layout. The next 10 lines all contain instructions that attempt to move a minion from position 1 to position 2, and then collect an item. And so on... These paths are calculated with the path function in the script - it just does a simple depth-first search.

If we can ensure that, in each group of 10, precisely one minion succeeds, then we will have solved the problem.

The issue with the approach

It might not always be the case that precisely one minion from the group of 10 succeeds - for instance, a minion designed to get from position 0 to position 1 might accidentally succeed in moving from position 1 to position 2 (due to similarities in the layouts), and that miscalculation will propagate through, potentially causing failure.

How to fix it

The fix that I used was as follows:

For each minion that is trying to get from position n to n+1, first make him walk from position n to position 0 (the top-left corner) and back again, then from position n to position 99 (the bottom-right corner) and back again. These instructions can only be safely carried out from position n - any other starting position and the minion will walk off the edge.

These extra instructions therefore prevent minions from accidentally going somewhere they aren't meant to, and this ensures that exactly one minion from each group of 10 is successful. Note that it is not necessarily the minion you expect it to be - it might be the case that the minion who believes he is in layout 0 succeeds, even when we are really in layout 7 - but in any case, the fact that we have now changed position means that all other minions in the group will necessarily die. These extra steps are calculated by the assert_at function, and the safe_path function returns a path which is the concatenation of these extra steps with the ordinary path.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Cracked. This was good fun, but I think it brings up an issue with the "your programming language only has to solve this one task" rule, since cracking your puzzle had nothing to do with actually solving that programming task. ;) \$\endgroup\$ Commented Oct 29, 2015 at 21:09
  • \$\begingroup\$ I created this answer mainly because I noticed that issue existed - and there doesn't seem to be anything stopping someone from using a genuinely hard computational problem in its place. The ban on 'no crypto' seems arbitrarily narrow... \$\endgroup\$ Commented Oct 29, 2015 at 21:20
  • \$\begingroup\$ true. I guess that's why this is a popularity contest. If the problem was more of computational nature and not wrapped in such a nice story as many votes as an answer with a proper (potentially Turing complete) language which is actually just really hard to use. \$\endgroup\$ Commented Oct 29, 2015 at 21:25
  • \$\begingroup\$ Added my solution for interest. Also for interest, I originally designed the puzzle with a 1000x1000 grid in mind, but in the interests of not having layouts that were encoded to ~600000-digit numbers I opted for the smaller size. \$\endgroup\$ Commented Oct 30, 2015 at 10:30
10
\$\begingroup\$

Firetype, Cracked by Martin Büttner

A really weird mix of BF and CJam. And who knows what else! Pretty sure this'll be an easy one, but it was fun anyway. FYI, the name is referring to Vermillion Fire from Final Fantasy Type-0.

NOTE: Please forgive me for any ambiguities in this description. I'm not the best writer... :O

Martin cracked this really quickly! This was my original program to solve the problem:

_ , ^ ~ + | | - % + _ + = ` ~ + | % _ = \ @ - - ! < > > > _ + . < - ` ~ ~ + | | - # % 

Syntax

A Firetype script is basically a list of lines. The first character of each line is the command to run. Empty lines are basically NOPs.

Semantics

You have an array of integers and a pointer (think BF). You can move left and right or "push" elements onto the array.

Pushing

When you "push" an element and you're at the end of the array, an extra element will be appended to the end. If you're not at the end, the next element will be overriden. Regardless, the pointer will always be incremented.

Commands

_

Push a zero onto the array.

+

Increment the element at the current pointer.

-

Decrement the element at the current pointer.

*

Double the element at the current pointer.

/

Halve the element at the current pointer.

%

Take the element at the current pointer and jump forward that many lines, and move the pointer to the left. If the value is negative, jump backwards instead.

=

Take the element at the current pointer and jump to that line + 1. For instance, if the current element is 0, this will jump to line 1. This also moves the pointer to the left.

,

Read a character from standard input and push its ASCII value.

^

Take the element at the current pointer, interpret it as the ASCII value for a character, and convert it to an integer. For instance, if the current value is 49 (the ASCII value of 1), the element at the current pointer will be set to the integer 1.

.

Write the current number to the screen.

!

Take the element at the current pointer and repeat the next line that many times. Also moves the pointer to the left.

<

Move the pointer left. If you're already at the beginning, an error is thrown.

>

Move the pointer right. If you're already at the end, an error is thrown.

~

If the current element is nonzero, replace it with 0; otherwise, replace it with 1.

|

Square the current element.

@

Set the current element to the length of the array.

`

Duplicate the current element.

\

Sort the elements at and before the pointer.

#

Negate the current element.

The interpreter

Also available at Github.

#!/usr/bin/env python # The FireType interpreter. # Runs under Python 2 and 3. import sys class Array(object): def __init__(self): self.array = [] self.ptr = 0 def push_zero(self): if self.ptr == len(self.array): self.array.append(0) else: self.array[self.ptr] = 0 self.ptr += 1 def left(self): self.ptr -= 1 assert self.ptr >= 0 and self.array, 'pointer underflow (at %d)' % i def right(self): self.ptr += 1 assert self.ptr <= len(self.array), 'pointer overflow (at %d)' % i def sort(self): lhs = self.array[:self.ptr-1] rhs = self.array[self.ptr-1:] lhs.sort() lhs.reverse() self.array = lhs + rhs def __call__(self, *args): args = list(args) assert self.array, 'out-of-bounds (at %d)' % i if not args: return self.array[self.ptr-1] self.array[self.ptr-1] = args.pop() assert not args def __len__(self): return len(self.array) def __str__(self): return 'Array(array=%s, ptr=%s)' % (self.array, self.ptr) def __repr__(self): return 'Array(array=%r, ptr=%r)' % (self.array, self.ptr) def execute(lines): global i, array i = 0 array = Array() rep = 0 while i < len(lines): line = lines[i] if not line: i += 1 continue line = line[0] if line == '_': array.push_zero() elif line == '+': array(array() + 1) elif line == '-': array(array() - 1) elif line == '*': array(array() * 2) elif line == '/': array(int(array() / 2)) elif line == '%': i += array() array.left() elif line == '=': i = array()-1 array.left() elif line == ',': array.push_zero() array(ord(sys.stdin.read(1))) elif line == '.': sys.stdout.write(str(array())) elif line == '!': rep = array() array.left() i += 1 elif line == '<': array.left() elif line == '>': array.right() elif line == '^': array(int(chr(array()))) elif line == '~': array(int(not array())) elif line == '|': array(array() ** 2) elif line == '@': array(len(array)) elif line == '`': top = array() array.push_zero() array(top) elif line == '\\': array.sort() elif line == '#': array(-array()) if rep: rep -= 1 else: i += 1 def main(args): try: _, path = args except ValueError: sys.exit('usage: %s <file to run, - for stdin>') if path == '-': data = sys.stdin.read() else: with open(path) as f: data = f.read() try: execute(data.split('\n')) except: print('ERROR!') print('Array was %r' % array) print('Re-raising error...') raise if __name__ == '__main__': main(sys.argv) 
\$\endgroup\$
4
  • \$\begingroup\$ I think the assertion for the lower bound of the array is buggy. Since you're using self.ptr-1 for access, you should probably check self.ptr>0 not >=0. (This shouldn't invalidate any valid programs but might make some programs make work accidentally which shouldn't.) \$\endgroup\$ Commented Oct 28, 2015 at 16:39
  • \$\begingroup\$ One more thing: the code says = sets the value to array() - 1, the documentation says +1? \$\endgroup\$ Commented Oct 28, 2015 at 16:46
  • \$\begingroup\$ Cracked. (Assuming that the interpreter is normative, not the description.) \$\endgroup\$ Commented Oct 28, 2015 at 17:07
  • \$\begingroup\$ @MartinBüttner Dang, that was faster than I thought! :O I fixed the doc issues you mentioned. \$\endgroup\$ Commented Oct 28, 2015 at 17:20
10
\$\begingroup\$

Picofuck (safe)

Picofuck is similar to Smallfuck. It operates on a binary tape which is unbound on the right, bound on the left. It has the following commands:

  • > move the pointer right

  • < move the pointer left. If the pointer falls off the tape, the program terminates.

  • * flip the bit at the pointer

  • ( if the bit at the pointer is 0, jump to the next )

  • ) do nothing - parentheses in Picofuck are if blocks, not while loops.

  • . write to stdout the bit at the pointer as an ascii 0 or 1.

  • , read from stdin until we encounter a 0 or 1, and store this in the bit at the pointer.

Picofuck code wraps - once the end of the program is reached, it continues from the beginning.

In addition, Picofuck disallows nested parentheses. Parentheses appearing in a Picofuck program must alternate between ( and ), starting with ( and ending with ).

Interpreter

Written in Python 2.7

usage: python picofuck.py <source_file>

import sys def interpret(code): # Ensure parentheses match and are not nested. in_if_block = False for c in code: if c == '(': if in_if_block: print "NESTED IFS DETECTED!!!!!" return in_if_block = True elif c == ')': if not in_if_block: print "Unmatched parenthesis" return in_if_block = False if in_if_block: print "Unmatched parenthesis" return code_ptr = 0 array = [0] ptr = 0 while 1: command = code[code_ptr] if command == '<': if ptr == 0: break ptr -= 1 elif command == '>': ptr += 1 if ptr == len(array): array.append(0) elif command == '*': array[ptr] = 1-array[ptr] elif command == '(': if array[ptr] == 0: while code[code_ptr] != ')': code_ptr = (code_ptr + 1) % len(code) elif command == ',': while True: c = sys.stdin.read(1) if c in ['0','1']: array[ptr] = int(c) break elif command == '.': sys.stdout.write(str(array[ptr])) code_ptr = (code_ptr+1)%len(code) if __name__ == "__main__": with open(sys.argv[1]) as source_file: code = source_file.read() interpret(code) 

Solution

The following python 2.7 program outputs my solution which can be found here

I may edit this post later with a more thorough explanation of how this works, but it turns out that Picofuck is Turing-complete.

states = { "SETUP":( "*>", "GET_NUMBER", "GET_NUMBER" ), "GET_NUMBER":( ",", "CHANGE_A", "PREPARE_PRINT_C" ), "GET_DIGIT":( ",", "CHANGE_A", "GOT_DIGIT" ), "GOT_DIGIT":( ">>>>", "GO_BACK", "GO_BACK" ), "CHANGE_A":( ">", "CHANGE_B", "SET_A" ), "SET_A":( "*>>>>", "GET_DIGIT", "GET_DIGIT" ), "CHANGE_B":( ">", "CHANGE_C", "SET_B" ), "SET_B":( "*>>>", "GET_DIGIT", "GET_DIGIT" ), "CHANGE_C":( ">", "CONTINUE_GET_NUMBER", "SET_C" ), "SET_C":( "*>>", "GET_DIGIT", "GET_DIGIT" ), "CONTINUE_GET_NUMBER":( ">>", "GET_DIGIT", "GET_DIGIT" ), "GO_BACK":( "<<<<<", "FINISH_GO_BACK", "GO_BACK" ), "FINISH_GO_BACK":( ">", "GET_NUMBER", "GET_NUMBER" ), "PREPARE_PRINT_C":( ">>>", "PRINT_C", "PRINT_C" ), "PRINT_C":( ".>>>>>", "PRINT_C", "TERMINATE" ), "TERMINATE":( "<", "TERMINATE", "TERMINATE" ), } def states_to_nanofuck(states,start_state): state_list = list(states) state_list.remove(start_state) state_list.insert(0,start_state) states_by_index = [] for i,state in enumerate(state_list): commands, next1, next0 = states[state] states_by_index.append((commands,state_list.index(next1),state_list.index(next0))) # setup first state fragments = ['*(*>>>>>*<<<<<)>>>>>'] # reset states that don't match for index in range(len(states_by_index)): for bool in range(2): # at state_0_0 state_dist = index*3 + bool # move state to curstate fragments.append('>'*state_dist) fragments.append('(*') fragments.append( '<'*state_dist) fragments.append( '<<*>>') fragments.append( '>'*state_dist) fragments.append(')') fragments.append('<'*state_dist) # go to arr fragments.append('<<<') if bool == 0: #flip arr fragments.append('*') # compute match = arr & curstate fragments.append('(>)(>*<)<(<)>') if bool == 0: #flip arr fragments.append('*') # reset curstate fragments.append('>(*)') # move match to matchstate, go back to state_0_0 matchstate_dist = index*3 + 2 fragments.append('>(*>') fragments.append( '>'*matchstate_dist) fragments.append( '*') fragments.append( '<'*matchstate_dist) fragments.append('<)>') #fragments.append('|') # do the commands of the matching state for index,state in enumerate(states_by_index): for bool in range(2): # at state_0_0 matchstate_dist = index*3 + 2 # move matchstate to curstate fragments.append('>'*matchstate_dist) fragments.append('(*') fragments.append( '<'*matchstate_dist) fragments.append( '<<*>>') fragments.append( '>'*matchstate_dist) fragments.append(')') fragments.append('<'*matchstate_dist) # if curstate, reset curstate fragments.append('<<(*') # go to arr fragments.append('<') # do commands commands,next1,next0 = state for c in commands: if c in '<>': fragments.append(c*(3*len(states_by_index)+5)) else: fragments.append(c) # go to state_0_0 fragments.append('>>>') # set next states for dist in [next0*3, next1*3+1]: fragments.append('>'*dist) fragments.append('*') fragments.append('<'*dist) # go to curstate fragments.append('<<') # end if fragments.append(')') # go to state_0_0 fragments.append('>>') # go back to setup and set it fragments.append('<<<<<*') code = ''.join(fragments) return code print states_to_nanofuck(states, "SETUP") 
\$\endgroup\$
7
  • 3
    \$\begingroup\$ waits almost 2 years Is it later yet? \$\endgroup\$ Commented Jun 18, 2017 at 15:44
  • \$\begingroup\$ Just FYI, the link you provided is now dead.' \$\endgroup\$ Commented Dec 31, 2017 at 21:24
  • \$\begingroup\$ The link requires a download and I'm too lazy. Please fix. \$\endgroup\$ Commented Feb 8, 2018 at 20:06
  • \$\begingroup\$ @CalculatorFeline It's 13KB, that's much easier to handle as a download. \$\endgroup\$ Commented Jul 12, 2018 at 21:40
  • 1
    \$\begingroup\$ How can this be turing complete without loops? \$\endgroup\$ Commented Oct 20, 2021 at 22:39
9
\$\begingroup\$

PQRS – Safe! / Solution Provided

Basics

All implied instructions have four memory address operands:

P₀ Q₀ R₀ S₀ P₁ Q₁ R₁ S₁ ... Pᵥ₋₁ Qᵥ₋₁ Rᵥ₋₁ Sᵥ₋₁ 

Where v is the size of memory in quartets.

Pᵢ, Qᵢ, Rᵢ, Sᵢ are signed integers in your machine's native size (e.g. 16, 32 or 64 bits) which we will refer to as words.

For each quartet i, the implied operation, with [] denoting indirection, is:

if (([Pᵢ] ← [Qᵢ] − [Rᵢ]) ≤ 0) go to Sᵢ else go to Pᵢ₊₁ 

Note that Subleq is a subset of PQRS.

Subleq has been proven complete, so PQRS should be complete as well!

Program Structure

PQRS defines an initial header as follows:

H₀ H₁ H₂ H₃ H₄ H₅ H₆ H₇ 

H₀ H₁ H₂ H₃ is always the first instruction P₀ Q₀ R₀ S₀. H₀ to H₃ need to be defined at load time.

PQRS has rudimentary I/O, but sufficient for the challenge.

H₄ H₅: at program start, it reads in a maximum of H₅ ASCII characters from standard input and saves as words at index H₄ onwards. H₄ and H₅ need to be defined at load time. After reading, H₅ will be set to the number of characters read (and words saved).

H₆ H₇: at program termination, starting at index H₆, it prints all the bytes comprising the H₇ words to standard output as ASCII characters. H₆ and H₇ need to be defined before the program ends. Null bytes '\0' in the output will be skipped over.

Termination

Termination is achieved by setting Sᵢ out of bounds i < 0 or i ≥ v.

Tricks

Quartets Pᵢ Qᵢ Rᵢ Sᵢ need not be aligned or sequential, branching is permitted at sub-quartet intervals.

PQRS has indirection so, unlike Subleq, there is enough flexibility to implement subroutines.

Code can be self-modifying!

Interpreter

The interpreter is written in C:

#include <stdlib.h> #include <stdio.h> // The "architecture" enum {P, Q, R, S, START_OF_INPUT, LENGTH_OF_INPUT, START_OF_OUTPUT, LENGTH_OF_OUTPUT}; const int NEXT = S + 1; // Recommend optimized! #define OPTIMIZED #ifdef PER_SPECS // This may not work on all OSes and architectures - just too much memory needed! const int K = 1002000200; #else // OPTIMIZED // This provides enough space to run the test cases with challenge-optimized.pqrs const int K = 5200; #endif int main(int argc, char *argv[]) { int i, *M; char *p; FILE *program; // Allocate enough memory M = calloc(K, sizeof(int)); // Load the program for (i = 0, program = fopen(argv[1], "r"); i < K && !feof(program); i++) if (!fscanf(program, "%d", M + i)) break; fclose(program); // Read in the input for (i = 0; i < M[LENGTH_OF_INPUT] && !feof(stdin); i++) { int c = fgetc(stdin); if (c != EOF) M[M[START_OF_INPUT] + i] = c; else break; } M[LENGTH_OF_INPUT] = i; // Execute until terminated for (i = 0; i >= 0 && i < K; ) i = (M[M[P + i]] = M[M[Q + i]] - M[M[R + i]]) <= 0? M[S + i]: i + NEXT; // Write the output for (i = 0, p = (char *)(M + M[START_OF_OUTPUT]); i < M[LENGTH_OF_OUTPUT] * sizeof(int); i++) // Ignore '\0' if (p[i]) fputc(p[i], stdout); // Done free(M); return 0; } 

To use, save the above as pqrs.c then compile:

gcc -o pqrs pqrs.c 

Sample program

Echos input up to 40 characters, preceded by 'PQRS-'.

8 8 8 -1 14 40 9 45 0 80 81 82 83 45 

To run, save the above as echo.pqrs, then:

$ ./prqs echo.pqrs greetings[enter] [ctrl-D] PQRS-greetings 

Running the test cases:

$ ./pqrs challenge-optimized.pqrs < test-1.txt 1 $ ./pqrs challenge-optimized.pqrs < test-2.txt 11111111111111111 $ ./pqrs challenge-optimized.pqrs < test-3.txt [lots of ones!] $ ./pqrs challenge-optimized.pqrs < test-3.txt | wc 0 1 773 

All the test cases run extremely quickly, e.g. < 500 ms.

The challenge

PQRS can be deemed stable, so the challenge begins 2015-10-31 13:00 and ends 2015-11-08 13:00, times in UTC.

Good luck!

Solution

The language is quite similar to the one used in "Baby" – the world's first stored-program electronic digital machine. On that page is a program to find the highest factor of an integer in less than 32 words of (CRT!) memory!

I found writing the solution to conform to the specs was not compatible with the OS and machine I was using (a Linux Ubuntu derivative on slightly older hardware). It was just requesting more memory than available, and core dumping. On OSes with advanced virtual memory management or machines with at least 8 GB memory, you can probably run the solution per specs. I have provided both solutions.

It is very difficult to code in PQRS directly, akin to writing machine language, maybe even microcode. Instead it is easier to write in a kind of assembly language then "compile" it. Below is annotated assembly language for the solution optimized to run the test cases:

; ANNOTATED PQRS ASSEMBLER CODE ; MINIMAL SIZED BUFFERS TO RUN THE TEST CASES ;OFFSET LABEL P-OP Q-OP R-OP S-OP 0 TEMP ZERO ZERO L1 ; INPUT AND OUTPUT LOCATIONS AND SIZES 4 INPUT 4000 6 L0: OUTPUT 1000 ; GET CURRENT INPUT 8 L1: TEMP INPUT ASCII_ZERO L2 12 SUM SUM INC IGNORE 16 L1+1 L1+1 INC IGNORE 20 TEMP ZERO ZERO L1 ; CHECK IF END OF NUMBERS 24 L2: NUMBERS SUM ZERO L3 ; ADVANCE TO NEXT NUMBER 28 L2 L2 INC IGNORE ; ADVANCE COUNT 32 COUNT COUNT INC IGNORE 36 L1+1 L1+1 INC IGNORE ; CLEAR SUM AND GO BACK 40 SUM ZERO ZERO L1 ; SORT NUMBERS 44 L3: P NUMBERS ZERO LA 48 L4: Q NUMBERS+1 ZERO L9 ; COMPARE 52 TEMP Q P L8 ; SWAP IF OUT OF ORDER 56 L5+1 L3+1 ZERO IGNORE 60 L6 L3+1 ZERO IGNORE 64 L6+1 L4+1 ZERO IGNORE 68 L7 L4+1 ZERO IGNORE 72 L5: TEMP P ZERO IGNORE 76 L6: P Q ZERO IGNORE 80 L7: Q TEMP ZERO IGNORE ; INCREMENT INNER INDEX 84 L8: L4+1 L4+1 INC IGNORE 88 TEMP ZERO ZERO L3 ; INCREMENT OUTER INDEX AND RESET INNER INDEX 92 L9: L3+1 L3+1 INC IGNORE 96 L4+1 L3+1 INC IGNORE 100 TEMP ZERO ZERO L3 ; OUTPUT THIRD LARGEST NUMBER 104 L0+1 NUMBERS+2 ZERO IGNORE 108 LA: TEMP NUMBERS+2 ZERO EXIT 112 LB: OUTPUT ASCII_ONE ZERO IGNORE 116 LB LB INC IGNORE 120 NUMBERS+2 NUMBERS+2 DEC EXIT 124 TEMP ZERO ZERO LA ; SAFETY LOOP – JUST IN CASE IGNORE DOESN'T WORK AS PLANNED! 128 IGNORE: TEMP ZERO ZERO IGNORE ; CONSTANTS 132 ZERO: 0 133 INC: -1 134 DEC: 1 135 ASCII_ZERO: 48 136 ASCII_ONE: 49 ; VARIABLES 137 TEMP: [1] 138 SUM: [1] 139 COUNT: [1] 140 P: [1] 141 Q: [1] ; WORKING SPACE 142 NUMBERS: [10] ; I/O SPACE 152 INPUT: [4000] 4152 OUTPUT: [1000] 5152 

What it does is parse the input, converting unary to binary, then a bubble sort on the numbers with the values in decreasing order, then finally outputs the third largest value by converting binary back to unary.

Note that INC (increment) is negative and DEC (decrement) is positive! Where it's using L# or L#+1 as P- or Q-OPs, what's going on is that it is updating pointers: incrementing, decrementing, swapping, etc. The assembler was hand compiled to PQRS by substituting the labels with the offsets. Below is the PQRS optimized solution:

137 132 132 8 152 4000 4152 1000 137 152 135 24 138 138 133 128 9 9 133 128 137 132 132 8 142 138 132 44 24 24 133 128 139 139 133 128 9 9 133 128 138 132 132 8 140 142 132 108 141 143 132 92 137 141 140 84 73 45 132 128 76 45 132 128 77 49 132 128 80 49 132 128 137 140 132 128 140 141 132 128 141 137 132 128 49 49 133 128 137 132 132 44 45 45 133 128 49 45 133 128 137 132 132 44 7 144 132 128 137 144 132 -1 4152 136 132 128 112 112 133 128 144 144 134 -1 137 132 132 108 137 132 132 128 0 -1 1 48 49 

The code above can be saved as challenge-optimized.pqrs to run the test cases.

For completeness, here are the per specs source:

; ANNOTATED PQRS ASSEMBLER CODE ; FULL SIZED BUFFERS TO RUN ACCORDING TO SPECS ;OFFSET LABEL P-OP Q-OP R-OP S-OP 0 TEMP ZERO ZERO L1 ; INPUT AND OUTPUT LOCATIONS AND SIZES 4 INPUT 10^9 6 L0: OUTPUT 10^6 ; GET CURRENT INPUT 8 L1: TEMP INPUT ASCII_ZERO L2 12 SUM SUM INC IGNORE 16 L1+1 L1+1 INC IGNORE 20 TEMP ZERO ZERO L1 ; CHECK IF END OF NUMBERS 24 L2: NUMBERS SUM ZERO L3 ; ADVANCE TO NEXT NUMBER 28 L2 L2 INC IGNORE ; ADVANCE COUNT 32 COUNT COUNT INC IGNORE 36 L1+1 L1+1 INC IGNORE ; CLEAR SUM AND GO BACK 40 SUM ZERO ZERO L1 ; SORT NUMBERS 44 L3: P NUMBERS ZERO LA 48 L4: Q NUMBERS+1 ZERO L9 ; COMPARE 52 TEMP Q P L8 ; SWAP IF OUT OF ORDER 56 L5+1 L3+1 ZERO IGNORE 60 L6 L3+1 ZERO IGNORE 64 L6+1 L4+1 ZERO IGNORE 68 L7 L4+1 ZERO IGNORE 72 L5: TEMP P ZERO IGNORE 76 L6: P Q ZERO IGNORE 80 L7: Q TEMP ZERO IGNORE ; INCREMENT INNER INDEX 84 L8: L4+1 L4+1 INC IGNORE 88 TEMP ZERO ZERO L3 ; INCREMENT OUTER INDEX AND RESET INNER INDEX 92 L9: L3+1 L3+1 INC IGNORE 96 L4+1 L3+1 INC IGNORE 100 TEMP ZERO ZERO L3 ; OUTPUT THIRD LARGEST NUMBER 104 L0+1 NUMBERS+2 ZERO IGNORE 108 LA: TEMP NUMBERS+2 ZERO EXIT 112 LB: OUTPUT ASCII_ONE ZERO IGNORE 116 LB LB INC IGNORE 120 NUMBERS+2 NUMBERS+2 DEC EXIT 124 TEMP ZERO ZERO LA ; SAFETY LOOP – JUST IN CASE IGNORE DOESN'T WORK AS PLANNED! 128 IGNORE: TEMP ZERO ZERO IGNORE ; CONSTANTS 132 ZERO: 0 133 INC: -1 134 DEC: 1 135 ASCII_ZERO: 48 136 ASCII_ONE: 49 ; VARIABLES 137 TEMP: [1] 138 SUM: [1] 139 COUNT: [1] 140 P: [1] 141 Q: [1] ; WORKING SPACE 142 NUMBERS: [10^6] ; I/O SPACE 1000142 INPUT: [10^9] 1001000142 OUTPUT: [10^6] 1002000142 

And solution:

137 132 132 8 1000142 1000000000 1001000142 1000000 137 1000142 135 24 138 138 133 128 9 9 133 128 137 132 132 8 142 138 132 44 24 24 133 128 139 139 133 128 9 9 133 128 138 132 132 8 140 142 132 108 141 143 132 92 137 141 140 84 73 45 132 128 76 45 132 128 77 49 132 128 80 49 132 128 137 140 132 128 140 141 132 128 141 137 132 128 49 49 133 128 137 132 132 44 45 45 133 128 49 45 133 128 137 132 132 44 7 144 132 128 137 144 132 -1 1001000142 136 132 128 112 112 133 128 144 144 134 -1 137 132 132 108 137 132 132 128 0 -1 1 48 49 

To run the above, you will need to comment out #define OPTIMIZED and add #define PER_SPECS in pqrs.c and recompile.

This was a great challenge – really enjoyed the mental workout! Took me back to my old 6502 assembler days...

If I were to implement PQRS as a “real” programming language, I would probably add additional modes for direct and doubly indirect access in addition to indirect access, as well as position relative and position absolute, both with indirect access options for the branching!

\$\endgroup\$
2
  • 3
    \$\begingroup\$ You should have a solution prepared before posting. \$\endgroup\$ Commented Oct 30, 2015 at 20:40
  • 1
    \$\begingroup\$ Yes, the solution is ready. I understand there is some doubt because the language really is quite difficult to work in. For those who want a sneak preview, I can send it to you, provided you promise not to disclose it before the end of the challenge! \$\endgroup\$ Commented Nov 5, 2015 at 8:25
8
\$\begingroup\$

Compass Soup (cracked by cardboard_box)

Interpreter: C++

Compass Soup is kind of like a Turing machine with an infinite 2-dimensional tape. The main catch is that the instruction memory and data memory are in the same space, and the output of the program is the entire contents of that space.

enter image description here

How it works

A program is a 2-dimensional block of text. The program space starts with the entire source code placed with the first character at (0,0). The rest of the program space is infinite and is initialized with null characters (ASCII 0).

There are two pointers that can move around the program space:

  • The execution pointer has a location and a direction (North, South, East, or West). Each tick, the instruction under the execution pointer is executed, then the execution pointer moves in its current direction. The execution pointer starts moving east (positive x), at the location of the ! character or at (0,0) if that doesn't exist.
  • The data pointer has only a location. It is moved with the instructions x, X, y, and Y. It starts at the location of the @ character or at (0,0) if that doesn't exist.

Input

The contents of stdin are printed into the program space starting at the location of the > character, or at (0,0) if that doesn't exist.

Output

The program terminates when the execution pointer runs irretrievably out of bounds. The output is the entire contents of the program space at that time. It is sent to stdout and 'result.txt'.

Instructions

  • n - redirects the execution pointer North (negative y)
  • e - redirects the execution pointer East (positive x)
  • s - redirects the execution pointer South (positive y)
  • w - redirects the execution pointer West (negative x)
  • y - moves the data pointer North (negative y)
  • X - moves the data pointer East (positive x)
  • Y - moves the data pointer South (positive y)
  • x - moves the data pointer West (negative x)
  • p - writes the next character encountered by the execution pointer at the data pointer. That character isn't executed as an instruction.
  • j - checks the next character encountered by the execution pointer against the character under the data pointer. That character isn't executed as an instruction. If they are the same, the execution pointer jumps over the next character.
  • c - writes the null character at the data pointer.
  • * - breakpoint - just causes the interpreter to break.

All other characters are ignored by the execution pointer.

Interpreter

The interpreter takes the source file as an argument and input on stdin. It has a steppable debugger, which you can invoke with a breakpoint instruction in the code (*). When broken, the execution pointer is shown as ASCII 178 (darker shaded block) and the data pointer is shown as ASCII 177 (lighter shaded block).

#include <stdio.h> #include <iostream> #include <fstream> #include <string> #include <stdio.h> // Compass Soup programming language interpreter // created by Brian MacIntosh (BMacZero) // for https://codegolf.stackexchange.com/questions/61804/create-a-programming-language-that-only-appears-to-be-unusable // // 31 October 2015 struct Point { int x, y; Point(int ix, int iy) { x = ix; y = iy; }; bool operator==(const Point &other) const { return other.x == x && other.y == y; } bool operator!=(const Point &other) const { return other.x != x || other.y != y; } }; struct Bounds { int xMin, xMax, yMin, yMax; Bounds(int xmin, int ymin, int xmax, int ymax) { xMin = xmin; yMin = ymin; xMax = xmax; yMax = ymax; } bool contains(Point pt) { return pt.x >= xMin && pt.x <= xMax && pt.y >= yMin && pt.y <= yMax; } int getWidth() { return xMax - xMin + 1; } int getHeight() { return yMax - yMin + 1; } bool operator==(const Bounds &other) const { return other.xMin == xMin && other.xMax == xMax && other.yMin == yMin && other.yMax == yMax; } bool operator!=(const Bounds &other) const { return other.xMin != xMin || other.xMax != xMax || other.yMin != yMin || other.yMax != yMax; } }; int max(int a, int b) { return a > b ? a : b; } int min(int a, int b) { return a < b ? a : b; } Bounds hull(Point a, Bounds b) { return Bounds(min(a.x, b.xMin), min(a.y, b.yMin), max(a.x, b.xMax), max(a.y, b.yMax)); } Bounds hull(Bounds a, Bounds b) { return Bounds(min(a.xMin, b.xMin), min(a.yMin, b.yMin), max(a.xMax, b.xMax), max(a.yMax, b.yMax)); } Bounds programBounds(0,0,0,0); char** programSpace; Point execPtr(0,0); Point execPtrDir(1,0); Point dataPtr(0,0); Point stdInPos(0,0); bool breakpointHit = false; char breakOn = 0; /// reads the character from the specified position char read(Point pt) { if (programBounds.contains(pt)) return programSpace[pt.x - programBounds.xMin][pt.y - programBounds.yMin]; else return 0; } /// read the character at the data pointer char readData() { return read(dataPtr); } /// read the character at the execution pointer char readProgram() { return read(execPtr); } /// gets the bounds of the actual content of the program space Bounds getTightBounds(bool debug) { Bounds tight(0,0,0,0); for (int x = programBounds.xMin; x <= programBounds.xMax; x++) { for (int y = programBounds.yMin; y <= programBounds.yMax; y++) { if (read(Point(x, y)) != 0) { tight = hull(Point(x, y), tight); } } } if (debug) { tight = hull(dataPtr, tight); tight = hull(execPtr, tight); } return tight; } /// ensure that the program space encompasses the specified rectangle void fitProgramSpace(Bounds bounds) { Bounds newBounds = hull(bounds, programBounds); if (newBounds == programBounds) return; // allocate new space char** newSpace = new char*[newBounds.getWidth()]; // copy content for (int x = 0; x < newBounds.getWidth(); x++) { newSpace[x] = new char[newBounds.getHeight()]; for (int y = 0; y < newBounds.getHeight(); y++) { Point newWorldPos(x + newBounds.xMin, y + newBounds.yMin); newSpace[x][y] = read(newWorldPos); } } // destroy old space for (int x = 0; x < programBounds.getWidth(); x++) { delete[] programSpace[x]; } delete[] programSpace; programSpace = newSpace; programBounds = newBounds; } /// outputs the current program space to a file void outputToStream(std::ostream &stream, bool debug) { Bounds tight = getTightBounds(debug); for (int y = tight.yMin; y <= tight.yMax; y++) { for (int x = tight.xMin; x <= tight.xMax; x++) { char at = read(Point(x, y)); if (debug && x == execPtr.x && y == execPtr.y) stream << (char)178; else if (debug && x == dataPtr.x && y == dataPtr.y) stream << (char)177; else if (at == 0) stream << ' '; else stream << at; } stream << std::endl; } } /// writes a character at the specified position void write(Point pt, char ch) { fitProgramSpace(hull(pt, programBounds)); programSpace[pt.x - programBounds.xMin][pt.y - programBounds.yMin] = ch; } /// writes a character at the data pointer void write(char ch) { write(dataPtr, ch); } /// writes a line of text horizontally, starting at the specified position void writeLine(Point loc, std::string str, bool isSource) { fitProgramSpace(Bounds(loc.x, loc.y, loc.x + str.size(), loc.y)); for (unsigned int x = 0; x < str.size(); x++) { programSpace[x + loc.x][loc.y] = str[x]; // record locations of things if (isSource) { switch (str[x]) { case '>': stdInPos = Point(loc.x + x, loc.y); break; case '!': execPtr = Point(loc.x + x, loc.y); break; case '@': dataPtr = Point(loc.x + x, loc.y); break; } } } } void advanceExecPtr() { execPtr.x += execPtrDir.x; execPtr.y += execPtrDir.y; } void breakpoint() { breakpointHit = true; outputToStream(std::cout, true); std::cout << "[Return]: step | [Space+Return]: continue | [<char>+Return]: continue to <char>" << std::endl; while (true) { std::string input; std::getline(std::cin, input); if (input.size() == 0) { break; } else if (input.size() == 1) { if (input[0] == ' ') { breakpointHit = false; break; } else { breakOn = input[0]; breakpointHit = false; break; } } } } int main(int argc, char** argv) { if (argc != 2) { printf("Usage: CompassSoup <source-file>"); return 1; } // open source file std::ifstream sourceIn(argv[1]); if (!sourceIn.is_open()) { printf("Error reading source file."); return 1; } programSpace = new char*[1]; programSpace[0] = new char[1]; programSpace[0][0] = 0; // read starting configuration std::string line; int currentLine = 0; while (std::getline(sourceIn, line)) { writeLine(Point(0, currentLine), line, true); currentLine++; } sourceIn.close(); // take stdin std::string input; std::cout << ">"; std::cin >> input; std::cin.ignore(); writeLine(stdInPos, input, false); // execute while (programBounds.contains(execPtr)) { if (execPtrDir.x == 0 && execPtrDir.y == 0) { printf("Implementation error: execPtr is stuck."); break; } advanceExecPtr(); char command = readProgram(); // breakpoint control code if (breakpointHit || (breakOn != 0 && command == breakOn)) { breakOn = 0; breakpoint(); } switch (command) { case 'n': execPtrDir = Point(0,-1); break; case 'e': execPtrDir = Point(1,0); break; case 's': execPtrDir = Point(0,1); break; case 'w': execPtrDir = Point(-1,0); break; case 'x': dataPtr.x--; break; case 'X': dataPtr.x++; break; case 'y': dataPtr.y--; break; case 'Y': dataPtr.y++; break; case 'p': advanceExecPtr(); write(readProgram()); break; case 'j': advanceExecPtr(); if (readData() == readProgram()) { advanceExecPtr(); } break; case 'c': write(0); break; case '*': breakpoint(); break; } } std::ofstream outputFile("result.txt"); outputToStream(outputFile, false); outputToStream(std::cout, false); outputFile.close(); } 

Examples

Hello World

Hello, World! 

Cat

> 

Parity: accepts a string of characters terminated by a zero ('0'). Outputs yes on the first line of the output if the number of 1s in the input is odd, otherwise outputs |.

|> !--eXj1s-c-eXj0s-c-exj|s-pyXpeXps c | c | | | cn0j-w---n1j-w n---w 

Tips

You should use a good text editor and make judicious use of the functionality of the 'Insert' key, and use 'Alt-Drag' to add or delete text on multiple rows at once.

Solution

Here is my solution. It's not as nice as cardboard_box's because I had to make the source code delete itself. I was also hoping I could find a way to delete all of the code and leave only the answer, but I couldn't.

My approach was to split the different sequences of 1s onto different lines, then sort them by having the 1s all "fall" up until they hit another 1, and finally erase everything except the third line after the input.

  • The big block to the bottom-right of #A# reads 1s and copies them to the last line of the split until a 0 is read.
  • #B# checks for a second 0 and goes north to #D# there is one. Otherwise, #C# starts a new split line by putting | after the last one, and goes back to #A#.
  • The block at and above #F# is the gravity code. It walks to the last 1 of the first row and moves it up until it hits 1 or -. If it can't do that, it marks the row as finished by putting + before it.
  • #G# is erasing all the unneeded splits, and #H# is erasing stdin and all the code between the parentheses.

Code:

 s-----------------------w s-c-w s-c-w s---w e+- eXj)nc-eXj)nc-exj(ncyj(nn (n-----------------------------------------w )) ( #H# s---w | )) ( exj+ncyxn )) ( | )) ( s---w s-c-+w )) ( exj+ncy-eXj1nn )) ( | )) ( s---w s-c-+w s+pxw )) ( eyj-n-YY-eXj1nn | sn1jX--w e----s )) ( | Y x e+---s e---s ns1jyw )) ( ej+n------s j | nn+jYw-n+jxw-Yw | )) ( Y ec----s e---s| | 1 )) ( c ns1jX-wYcYYY-n-jyww | p )) ( ns+jxw #G# e--s Y )) ( e---n | s------w| )) ( | | ej-nn )) ( s--w e----s exc----eyj1n---n )) (#A# p e+---s s---w |#F#| )) (e----se---s 1 || | exj|n----p+YeXj1ns )) (ns-jXwn-jyw--w-nn1jXw c #D# n----w )) ( | | | | )) ( | n---------+---+-------------|pw s---w s---w )) ( | | | exp)XYs | eyj-nYeXj0ns) ( | s---ws---+w n-----+-----+---+------------+----------w)) ( | | || || e--yp)n e---+--s | ) ( | e-c-exj|neYj|nn | #C# | | p )) ( | | | s---w s---+w s---w s---+w )) ( | | #B# e+s | | | || | | | || )) (!ep-Yj0n-c----------Xj0nne----exj|n-eYj|nn exj|n-eYj|nn )) (-@ |> 
\$\endgroup\$
4
  • \$\begingroup\$ Cracked \$\endgroup\$ Commented Nov 8, 2015 at 20:21
  • \$\begingroup\$ Darn, so close! I'll share my solution when I get home tonight. \$\endgroup\$ Commented Nov 9, 2015 at 21:38
  • \$\begingroup\$ I can't get the parity program to work. Is there supposed to be a debug instruction at the beginning? If I step through it gets stuck in an infinite loop, Any idea what I might be doing wrong? \$\endgroup\$ Commented Nov 11, 2015 at 14:41
  • \$\begingroup\$ It looks like there was an extra c at the beginning that shouldn't have been there. I fixed it. Also added my solution to the problem. \$\endgroup\$ Commented Nov 11, 2015 at 18:50
7
\$\begingroup\$

Zinc, Cracked! by @Zgarb

Also available at GitHub.

You need Dart 1.12 and Pub. Just run pub get to download the only dependency, a parsing library.

Here's to hoping to this one lasts longer than 30 minutes! :O

The language

Zinc is oriented around redefining operators. You can redefine all the operators in the language easily!

The structure of a typical Zinc program looks like:

let <operator overrides> in <expression> 

There are only two data types: integers and sets. There is no such thing as a set literal, and empty sets are disallowed.

Expressions

The following are valid expressions in Zinc:

Literals

Zinc supports all the normal integer literals, like 1 and -2.

Variables

Zinc has variables (like most languages). To reference them, just use the name. Again like most languages!

However, there is a special variable called S that behaves kind of like Pyth's Q. When you first use it, it will read in a line from standard input and interpret it as a set of numbers. For instance, the input line 1234231 would turn into the set {1, 2, 3, 4, 3, 2, 1}.

IMPORTANT NOTE!!! In some cases, a literal at the end of an operator override is parsed incorrectly, so you have to surround it with parentheses.

Binary operations

The following binary operations are supported:

  • Addition via +: 1+1.
  • Subtraction via -: 1-1.
  • Multiplication via *: 2*2.
  • Division via /: 4/2.
  • Equality with =: 3=3.

Additionally, the following unary operation is also supported:

  • Length with #: #x.

Precedence is always right-associative. You can use parentheses to override this.

Only equality and length work on sets. When you try to get the length of an integer, you will get the number of digits in its string representation.

Set comprehensions

In order to manipulate sets, Zinc has set comprehensions. They look like this:

{<variable>:<set><clause>} 

A clause is either a when clause or a sort clause.

A when clause looks like ^<expression>. The expression following the caret must result in an integer. Using the when clause will take only the elements in the set for which expression is non-zero. Within the expression, the variable _ will be set to the current index in the set. It is roughly equivalent to this Python:

[<variable> for _, <variable> in enumerate(<set>) when <expression> != 0] 

A sort clause, which looks like $<expression>, sorts the set descending by the value of <expression>. It is equal to this Python:

sorted(<set>, key=lambda <variable>: <expression>)[::-1] 

Here are some comprehension examples:

  • Take only the elements of set s that equal 5:

    {x:s^x=5} 
  • Sort the set s by the value if its elements squared:

    {x:s$x*x} 

Overrides

Operator overrides allow you to redefine operators. They look like this:

<operator>=<operator> 

or:

<variable><operator><variable>=<expression> 

In the first case, you can define an operator to equal another operator. For instance, I can define + to actually subtract via:

+=- 

When you do this, you can redefine an operator to be a magic operator. There are two magic operators:

  • join takes a set and an integer and joins the contents of the set. For instance, joining {1, 2, 3} with 4 will result in the integer 14243.

  • cut also takes a set and an integer and will partition the set at every occurence of the integer. Using cut on {1, 3, 9, 4, 3, 2} and 3 will create {{1}, {9, 4}, {2}}...BUT any single-element sets are flattened, so the result will actually be {1, {9, 4}, 2}.

Here's an example that redefines the + operator to mean join:

+=join 

For the latter case, you can redefine an operator to the given expression. As an example, this defines the plus operation to add the values and then add 1:

x+y=1+:x+:y 

But what's +:? You can append the colon : to an operator to always use the builtin version. This example uses the builtin + via +: to add the numbers together, then it adds a 1 (remember, everything is right-associative).

Overriding the length operator looks kind of like:

#x=<expression> 

Note that almost all the builtin operations (except equality) will use this length operator to determine the length of the set. If you defined it to be:

#x=1 

every part of Zinc that works on sets except = would only operate on the first element of the set it was given.

Multiple overrides

You can override multiple operators by separating them with commas:

let +=-, *=/ in 1+2*3 

Printing

You cannot directly print anything in Zinc. The result of the expression following in will be printed. The values of a set will be concatenated with to separator. For instance, take this:

let ... in expr 

If expr is the set {1, 3, {2, 4}}, 1324 will be printed to the screen once the program finishes.

Putting it all together

Here's a simple Zinc program that appears to add 2+2 but causes the result to be 5:

let x+y=1+:x+:y in 1+2 

The interpreter

This goes in bin/zinc.dart:

import 'package:parsers/parsers.dart'; import 'dart:io'; // An error. class Error implements Exception { String cause; Error(this.cause); String toString() => 'error in Zinc script: $cause'; } // AST. class Node { Obj interpret(ZincInterpreter interp) => null; } // Identifier. class Id extends Node { final String id; Id(this.id); String toString() => 'Id($id)'; Obj interpret(ZincInterpreter interp) => interp.getv(id); } // Integer literal. class IntLiteral extends Node { final int value; IntLiteral(this.value); String toString() => 'IntLiteral($value)'; Obj interpret(ZincInterpreter interp) => new IntObj(value); } // Any kind of operator. class Anyop extends Node { void set(ZincInterpreter interp, OpFuncType func) {} } // Operator. class Op extends Anyop { final String op; final bool orig; Op(this.op, [this.orig = false]); String toString() => 'Op($op, $orig)'; OpFuncType get(ZincInterpreter interp) => this.orig ? interp.op0[op] : interp.op1[op]; void set(ZincInterpreter interp, OpFuncType func) { interp.op1[op] = func; } } // Unary operator (len). class Lenop extends Anyop { final bool orig; Lenop([this.orig = false]); String toString() => 'Lenop($orig)'; OpFuncType get(ZincInterpreter interp) => this.orig ? interp.op0['#'] : interp.op1['#']; void set(ZincInterpreter interp, OpFuncType func) { interp.op1['#'] = func; } } // Magic operator. class Magicop extends Anyop { final String op; Magicop(this.op); String toString() => 'Magicop($op)'; Obj interpret_with(ZincInterpreter interp, Obj x, Obj y) { if (op == 'cut') { if (y is! IntObj) { throw new Error('cannot cut int with non-int'); } if (x is IntObj) { return new SetObj(x.value.toString().split(y.value.toString()).map( int.parse)); } else { assert(x is SetObj); List<List<Obj>> res = [[]]; for (Obj obj in x.vals(interp)) { if (obj == y) { res.add([]); } else { res.last.add(obj); } } return new SetObj(new List.from(res.map((l) => l.length == 1 ? l[0] : new SetObj(l)))); } } else if (op == 'join') { if (x is! SetObj) { throw new Error('can only join set'); } if (y is! IntObj) { throw new Error('can only join set with int'); } String res = ''; for (Obj obj in x.vals(interp)) { if (obj is! IntObj) { throw new Error('joining set must contain ints'); } res += obj.value.toString(); } return new IntObj(int.parse(res)); } } } // Unary operator (len) expression. class Len extends Node { final Lenop op; final Node value; Len(this.op, this.value); String toString() => 'Len($op, $value)'; Obj interpret(ZincInterpreter interp) => op.get(interp)(interp, value.interpret(interp), null); } // Binary operator expression. class Binop extends Node { final Node lhs, rhs; final Op op; Binop(this.lhs, this.op, this.rhs); String toString() => 'Binop($lhs, $op, $rhs)'; Obj interpret(ZincInterpreter interp) => op.get(interp)(interp, lhs.interpret(interp), rhs.interpret(interp)); } // Clause. enum ClauseKind { Where, Sort } class Clause extends Node { final ClauseKind kind; final Node expr; Clause(this.kind, this.expr); String toString() => 'Clause($kind, $expr)'; Obj interpret_with(ZincInterpreter interp, SetObj set, Id id) { List<Obj> res = []; List<Obj> values = set.vals(interp); switch (kind) { case ClauseKind.Where: for (int i=0; i<values.length; i++) { Obj obj = values[i]; interp.push_scope(); interp.setv(id.id, obj); interp.setv('_', new IntObj(i)); Obj x = expr.interpret(interp); interp.pop_scope(); if (x is IntObj) { if (x.value != 0) { res.add(obj); } } else { throw new Error('where clause condition must be an integer'); } } break; case ClauseKind.Sort: res = values; res.sort((x, y) { interp.push_scope(); interp.setv(id.id, x); Obj x_by = expr.interpret(interp); interp.setv(id.id, y); Obj y_by = expr.interpret(interp); interp.pop_scope(); if (x_by is IntObj && y_by is IntObj) { return x_by.value.compareTo(y_by.value); } else { throw new Error('sort clause result must be an integer'); } }); break; } return new SetObj(new List.from(res.reversed)); } } // Set comprehension. class SetComp extends Node { final Id id; final Node set; final Clause clause; SetComp(this.id, this.set, this.clause); String toString() => 'SetComp($id, $set, $clause)'; Obj interpret(ZincInterpreter interp) { Obj setobj = set.interpret(interp); if (setobj is SetObj) { return clause.interpret_with(interp, setobj, id); } else { throw new Error('set comprehension rhs must be set type'); } } } // Operator rewrite. class OpRewrite extends Node { final Anyop op; final Node value; final Id lid, rid; // Can be null! OpRewrite(this.op, this.value, [this.lid, this.rid]); String toString() => 'OpRewrite($lid, $op, $rid, $value)'; Obj interpret(ZincInterpreter interp) { if (lid != null) { // Not bare. op.set(interp, (interp,x,y) { interp.push_scope(); interp.setv(lid.id, x); if (rid == null) { assert(y == null); } else { interp.setv(rid.id, y); } Obj res = value.interpret(interp); interp.pop_scope(); return res; }); } else { // Bare. if (value is Magicop) { op.set(interp, (interp,x,y) => value.interpret_with(interp, x, y)); } else { op.set(interp, (interp,x,y) => (value as Anyop).get(interp)(x, y)); } } return null; } } class Program extends Node { final List<OpRewrite> rws; final Node expr; Program(this.rws, this.expr); String toString() => 'Program($rws, $expr)'; Obj interpret(ZincInterpreter interp) { rws.forEach((n) => n.interpret(interp)); return expr.interpret(interp); } } // Runtime objects. typedef Obj OpFuncType(ZincInterpreter interp, Obj x, Obj y); class Obj {} class IntObj extends Obj { final int value; IntObj(this.value); String toString() => 'IntObj($value)'; bool operator==(Obj rhs) => rhs is IntObj && value == rhs.value; String dump() => value.toString(); } class SetObj extends Obj { final List<Obj> values; SetObj(this.values) { if (values.length == 0) { throw new Error('set cannot be empty'); } } String toString() => 'SetObj($values)'; bool operator==(Obj rhs) => rhs is SetObj && values == rhs.values; String dump() => values.map((x) => x.dump()).reduce((x,y) => x+y); List<Obj> vals(ZincInterpreter interp) { Obj lenobj = interp.op1['#'](interp, this, null); int len; if (lenobj is! IntObj) { throw new Error('# operator must return an int'); } len = lenobj.value; if (len < 0) { throw new Error('result of # operator must be positive'); } return new List<Obj>.from(values.getRange(0, len)); } } // Parser. class ZincParser extends LanguageParsers { ZincParser(): super(reservedNames: ['let', 'in', 'join', 'cut']); get start => prog().between(spaces, eof); get comma => char(',') < spaces; get lp => symbol('('); get rp => symbol(')'); get lb => symbol('{'); get rb => symbol('}'); get colon => symbol(':'); get plus => symbol('+'); get minus => symbol('-'); get star => symbol('*'); get slash => symbol('/'); get eq => symbol('='); get len => symbol('#'); get in_ => char(':'); get where => char('^'); get sort => char('\$'); prog() => reserved['let'] + oprw().sepBy(comma) + reserved['in'] + expr() ^ (_1,o,_2,x) => new Program(o,x); oprw() => oprw1() | oprw2() | oprw3(); oprw1() => (basicop() | lenop()) + eq + (magicop() | op()) ^ (o,_,r) => new OpRewrite(o,r); oprw2() => (id() + op() + id()).list + eq + expr() ^ (l,_,x) => new OpRewrite(l[1], x, l[0], l[2]); oprw3() => lenop() + id() + eq + expr() ^ (o,a,_,x) => new OpRewrite(o, x, a); magicop() => (reserved['join'] | reserved['cut']) ^ (s) => new Magicop(s); basicop() => (plus | minus | star | slash | eq) ^ (op) => new Op(op); op() => (basicop() + colon ^ (op,_) => new Op(op.op, true)) | basicop(); lenop() => (len + colon ^ (_1,_2) => new Lenop(true)) | len ^ (_) => new Lenop(); expr() => setcomp() | unop() | binop() | prim(); setcomp() => lb + id() + in_ + rec(expr) + clause() + rb ^ (_1,i,_2,x,c,_3) => new SetComp(i,x,c); clausekind() => (where ^ (_) => ClauseKind.Where) | (sort ^ (_) => ClauseKind.Sort); clause() => clausekind() + rec(expr) ^ (k,x) => new Clause(k,x); unop() => lenop() + rec(expr) ^ (o,x) => new Len(o,x); binop() => prim() + op() + rec(expr) ^ (l,o,r) => new Binop(l,o,r); prim() => id() | intlit() | parens(rec(expr)); id() => identifier ^ (i) => new Id(i); intlit() => intLiteral ^ (i) => new IntLiteral(i); } // Interpreter. class ZincInterpreter { Map<String, OpFuncType> op0, op1; List<Map<String, Obj>> scopes; ZincInterpreter() { var beInt = (v) { if (v is IntObj) { return v.value; } else { throw new Error('argument to binary operator must be integer'); } }; op0 = { '+': (_,x,y) => new IntObj(beInt(x)+beInt(y)), '-': (_,x,y) => new IntObj(beInt(x)-beInt(y)), '*': (_,x,y) => new IntObj(beInt(x)*beInt(y)), '/': (_,x,y) => new IntObj(beInt(x)/beInt(y)), '=': (_,x,y) => new IntObj(x == y ? 1 : 0), '#': (i,x,_2) => new IntObj(x is IntObj ? x.value.toString().length : x.values.length) }; op1 = new Map<String, OpFuncType>.from(op0); scopes = [{}]; } void push_scope() { scopes.add({}); } void pop_scope() { scopes.removeLast(); } void setv(String name, Obj value) { scopes[scopes.length-1][name] = value; } Obj getv(String name) { for (var scope in scopes.reversed) { if (scope[name] != null) { return scope[name]; } } if (name == 'S') { var input = stdin.readLineSync() ?? ''; var list = new List.from(input.codeUnits.map((c) => new IntObj(int.parse(new String.fromCharCodes([c]))))); setv('S', new SetObj(list)); return getv('S'); } else throw new Error('undefined variable $name'); } } void main(List<String> args) { if (args.length != 1) { print('usage: ${Platform.script.toFilePath()} <file to run>'); return; } var file = new File(args[0]); if (!file.existsSync()) { print('cannot open ${args[0]}'); return; } Program root = new ZincParser().start.parse(file.readAsStringSync()); ZincInterpreter interp = new ZincInterpreter(); var res = root.interpret(interp); print(res.dump()); } 

And this goes in pubspec.yaml:

name: zinc dependencies: parsers: any 

Intended solution

let #x=((x=S)*(-2))+#:x, /=cut in {y:{x:S/0$#:x}^_=2} 
\$\endgroup\$
9
  • 1
    \$\begingroup\$ Do I understand correctly that sets are ordered and can have duplicates, so they are basically lists? Also, if I join a mixed set like {1,{3,2}}, will there be an error? I can't install Dart right now, so I can't check myself. \$\endgroup\$ Commented Oct 30, 2015 at 14:46
  • \$\begingroup\$ @Zgarb Yes, sets are basically lists in this case. Joining mixed sets should be an error, but the interpreter actually crashes ATM... \$\endgroup\$ Commented Oct 30, 2015 at 15:48
  • \$\begingroup\$ How do I run the interpreter? If I just try dart bin/zinc.dart test.znc I get a syntax error: 'file:///D:/Development/languages/zinc/bin/zinc.dart': error: line 323 pos 41: unexpected token '?' ... var input = stdin.readLineSync() ?? ''; \$\endgroup\$ Commented Oct 30, 2015 at 19:40
  • 1
    \$\begingroup\$ Cracked. \$\endgroup\$ Commented Oct 30, 2015 at 20:07
  • 1
    \$\begingroup\$ @Zgarb Remember when, in the spec, I said all builtin operations except equality use the length operator? I overrode it to return -2+#:S when given S, which cut off the two trailing zeros. That was how I was hoping it would be solved. And ^ isn't supposed to reverse the set...that was a bug... \$\endgroup\$ Commented Oct 30, 2015 at 21:17
6
\$\begingroup\$

Acc!, Cracc'd by ppperry

This language has one looping structure, basic integer math, character I/O, and an accumulator (thus the name). Just one accumulator. Thus, the name.

Statements

Commands are parsed line by line. There are three types of command:

  1. Count <var> while <cond>

Counts <var> up from 0 as long as <cond> is nonzero, equivalent to C-style for(<var>=0; <cond>; <var>++). The loop counter can be any single lowercase letter. The condition can be any expression, not necessarily involving the loop variable. The loop halts when the condition's value becomes 0.

Loops require K&R-style curly braces (in particular, the Stroustrup variant):

Count i while i-5 { ... } 
  1. Write <charcode>

Outputs a single character with the given ASCII/Unicode value to stdout. The charcode can be any expression.

  1. Expression

Any expression standing by itself is evaluated and assigned back to the accumulator (which is accessible as _). Thus, e.g., 3 is a statement that sets the accumulator to 3; _ + 1 increments the accumulator; and _ * N reads a character and multiplies the accumulator by its charcode.

Note: the accumulator is the only variable that can be directly assigned to; loop variables and N can be used in calculations but not modified.

The accumulator is initially 0.

Expressions

An expression can include integer literals, loop variables (a-z), _ for the accumulator, and the special value N, which reads a character and evaluates to its charcode each time it is used. Note: this means you only get one shot to read each character; the next time you use N, you'll be reading the next one.

Operators are:

  • +, addition
  • -, subtraction; unary negation
  • *, multiplication
  • /, integer division
  • %, modulo
  • ^, exponentiation

Parentheses can be used to enforce precedence of operations. Any other character in an expression is a syntax error.

Whitespace and comments

Leading and trailing whitespace and empty lines are ignored. Whitespace in loop headers must be exactly as shown, with a single space between the loop header and opening curly brace as well. Whitespace inside expressions is optional.

# begins a single-line comment.

Input/Output

Acc! expects a single line of characters as input. Each input character can be retrieved in sequence and its charcode processed using N. Trying to read past the last character of the line causes an error. A character can be output by passing its charcode to the Write statement.

Interpreter

The interpreter (written in Python 3) translates Acc! code into Python and execs it.

import re, sys def main(): if len(sys.argv) != 2: print("Please supply a filename on the command line.", file=sys.stderr) return codeFile = sys.argv[1] with open(codeFile) as f: code = f.readlines() code = translate(code) exec(code, {"inputStream": (ord(char) for char in input())}) def translate(accCode): indent = 0 loopVars = [] pyCode = ["_ = 0"] for lineNum, line in enumerate(accCode): if "#" in line: # Strip comments line = line[:line.index("#")] line = line.strip() if not line: continue lineNum += 1 if line == "}": if indent: loopVar = loopVars.pop() if loopVar is not None: pyCode.append(" "*indent + loopVar + " += 1") indent -= 1 else: raise SyntaxError("Line %d: unmatched }" % lineNum) else: m = re.fullmatch(r"Count ([a-z]) while (.+) \{", line) if m: expression = validateExpression(m.group(2)) if expression: loopVar = m.group(1) pyCode.append(" "*indent + loopVar + " = 0") pyCode.append(" "*indent + "while " + expression + ":") indent += 1 loopVars.append(loopVar) else: raise SyntaxError("Line %d: invalid expression " % lineNum + m.group(2)) else: m = re.fullmatch(r"Write (.+)", line) if m: expression = validateExpression(m.group(1)) if expression: pyCode.append(" "*indent + "print(chr(%s), end='')" % expression) else: raise SyntaxError("Line %d: invalid expression " % lineNum + m.group(1)) else: expression = validateExpression(line) if expression: pyCode.append(" "*indent + "_ = " + expression) else: raise SyntaxError("Line %d: invalid statement " % lineNum + line) return "\n".join(pyCode) def validateExpression(expr): "Translates expr to Python expression or returns None if invalid." expr = expr.strip() if re.search(r"[^ 0-9a-z_N()*/%^+-]", expr): # Expression contains invalid characters return None elif re.search(r"[a-zN_]\w+", expr): # Expression contains multiple letters or underscores in a row return None else: # Not going to check validity of all identifiers or nesting of parens-- # let the Python code throw an error if problems arise there # Replace short operators with their Python versions expr = expr.replace("^", "**") expr = expr.replace("/", "//") # Replace N with a call to get the next input character expr = expr.replace("N", "inputStream.send(None)") return expr if __name__ == "__main__": main() 
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Cracked \$\endgroup\$ Commented Oct 31, 2015 at 18:08
5
\$\begingroup\$

GoToTape (Safe)

(Formerly know as Simp-plex.)

This language is simple. The main flow control is goto, the most natural and useful form of control.

Language specification

Data is stored on a tape and in an accumulator. It works entirely with unsigned integrates. Each character is command. The following are all of the commands:

  • Letters: a-z are goto statements, going to A-Z, respectively.
  • :: set the accumulator to the ASCII value to char from input.
  • ~: output the char for the ASCII value in the accumulator.
  • &: subtract one from the the accumulator if it is 1 or more, otherwise add one.
  • |: add one to the accumulator.
  • <: set the data pointer to 0.
  • +: increment the data cell at the data pointer; move the pointer +1.
  • -: subtract one from the data cell at the data pointer if it is positive; move the pointer +1.
  • [...]: run code n times where n is the number on the tape at the data pointer (cannot be nested).
  • /: skip next instruction if the accumulator is 0.

Interpreter(C++)

#include <iostream> #include <memory.h> #include <fstream> #include <iostream> #include <string> #include <sstream> using namespace std; int serch(char* str,char ch){ for(int i = 0;str[i];i++){ if(str[i]==ch) return i; } return -1; } void runCode(char* code){ int i = 0; char c; unsigned int* tape; tape = new unsigned int[1000003]; memset(tape,0,1000003*sizeof(int)); unsigned int p=0; unsigned int a=0; unsigned int n; unsigned int s; while(c=code[i]){ if('A'<=c && c<='Z'); if('a'<=c && c<='z')i=serch(code, c+'A'-'a'); if(':'==c)a=cin.get(); if('+'==c)tape[p++]++; if('-'==c)tape[p++] += tape[p]?-1:0; if('|'==c)a++; if('&'==c)a=a?a-1:1; if('<'==c)p=0; if('['==c){if(tape[p]){n=tape[p];s=i;}else i+=serch(code+i,']');}; if(']'==c)i=--n?i:s; if('~'==c)cout<<(char)a; if('/'==c)i+=a?0:1; if('$'==c)p=a; i++; } delete[](tape); } int main(int argc, char* argv[]) { if(argc == 2){ ifstream sorceFile (argv[1]); string code(static_cast<stringstream const&>(stringstream() << sorceFile.rdbuf()).str()); runCode((char*)code.c_str()); }else cout << "Code file must be included as a command-line argument \n"; return 0; } 

Have fun!

Solution

A:+&&&&&&&&&&/gbG&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&/a<aB<[|]C[&]-[|]/c<[|]D[&]-[|]/d<[|]+E[&]|||||||||||||||||||||||||||||||||||||||||||||||||~X&/x-[|]/e

\$\endgroup\$
13
  • 2
    \$\begingroup\$ Your C++ coding is killing me! Is there a reason you used calloc instead of new char, wrote a C-style while loop, used C-style memory management, make us re-compile the C++ file every time we change the code, and used 20 ifs instead of a switch? I'm not complaining, but my eyes are bleeding right now... :O \$\endgroup\$ Commented Oct 30, 2015 at 16:17
  • 3
    \$\begingroup\$ I have fixed the speck to meat the interpreter. \$\endgroup\$ Commented Oct 30, 2015 at 16:18
  • \$\begingroup\$ @kirbyfan64sos The code is bad. I kinda put this together quickly, and may not have done it as well as I should. the main function can be changed to take the code as input. in fact I think I will do that now... \$\endgroup\$ Commented Oct 30, 2015 at 16:24
  • 1
    \$\begingroup\$ The question says that interpreters should take a filename on the command line for the program. \$\endgroup\$ Commented Oct 30, 2015 at 18:48
  • 1
    \$\begingroup\$ @feersum thank you for the link. \$\endgroup\$ Commented Oct 30, 2015 at 20:55
3
\$\begingroup\$

Slindow (safe)

Slindow is a language inspired by this cops and robbers challenge. However, unlike the linked challenge (based on 3 consecutive items), this only involves two consequtive items, which simplifies the programming.

Transpilation process

Slindow processes in digraphs (aka 2 consequtive characters). Due to a bug that I'm too lazy to fix (anyway it's part of the challenge! :D ), this groups the instructions like this:

Grouping process: QWERTYU QW # Instruction #1 WE # Instruction #2 ER # Instruction #3 RT # Instruction #4 TY # Instruction #5 YU # Instruction #6 

Therefore the complete executed digraph chain is:

[QW,WE,ER,RT,TY,YU] 

The digraph mapping to Slindow/Base

Any undefined digraph throws an error and will be rejected by Slindow. Note that multiple digraphs can produce a single transpilation result. (The possible digraphs are separated by the space, since none of them contain the space.)

Mapping Digraphs | : U? UD !C ) : eU p@ D! 0 : DW p6 9z $ : ?w wD Dm ' : m@ m! m? * : @` `? @? ; : Wp Dq eV I : ^C $v #W [ : ve v? 9C { : Cv CW vp 1 : oV km C? _ : 64 4o Vq q9 z, ,k 

Slindow/Base

To simplify the language, Slindow automatically outputs the top of the stack at the end of the execution.

In the compilation-target Slindow the instructions are as follows:

  • I: Push a single input onto the stack
  • {...;: Pop the stack and perform each loop. Implicit operand pushed onto stack at every iteration.
  • [.A|.B;: Pop an item off the stack. If the integer representation of that is true, execute .A. Else, execute .B.
  • ): Increment the top of the stack.
  • |: Else statement accompanied with the if expression above.
  • $: Sort all items in the whole stack.
  • 0: Append a 0 onto the stack.
  • ;: Ending marker for all current-executing block-based structures.
  • 1: Append a 1 onto the stack.
  • _: Pop the top of the stack off.
  • ': Convert the top of the stack to its string representation.
  • *: Python multiplication. Can also be used to repeat strings.

Interpreter

# Your code goes here slindow_code = "" # Your input goes here input = "101011100" code = "" for x,i in enumerate(slindow_code): 	try: 		t = i + slindow_code[x+1] 	except: # Index out of bounds 		continue 	if t in ['wD', 'Dm', '?w']: 		code += "$" 	elif t in ['Dq', 'Wp', 'eV']: 		code += ";" 	elif t in ['Cv', 'CW', 'vp']: 		code += "{" 	elif t in ['@?', '`?', '@`']: 		code += "*" 	elif t in ['4o', 'Vq', ',k', 'q9', 'z,', '64']: 		code += "_" 	elif t in ['D!', 'eU', 'p@']: 		code += ")" 	elif t in ['UD', '!C', 'U?']: 		code += "|" 	elif t in ['9z', 'p6', 'DW']: 		code += "0" 	elif t in ['v?', '9C', 've']: 		code += "[" 	elif t in ['C?', 'km', 'oV']: 		code += "1" 	elif t in ['$v', '^C', '#W']: 		code += "I" 	elif t in ['m@', 'm?', 'm!']: 		code += "'" 	else: 		raise SyntaxError # Refuse to interpret # Uncomment this if you want to check # if your Slindoc/Base code is right. # print(code) # Slindow / Base interpreter stack = [0] out = "" indent = "" for i in code: 	if i == "I":out += indent + "stack.append(input)\n" 	if i == ")":out += indent + "stack.append(stack.pop()+1)\n" 	if i == "$":out += indent + "stack.sort()\n" 	if i in "01":out += indent + "stack.append("+i+")\n" 	if i == "_":out += indent + "stack.pop()\n" 	if i == "'":out += indent + 'stack.append(str(stack.pop()))\n' 	if i == "*":out += indent + "stack.append(stack.pop()*stack.pop())\n" 	if i == ";":indent = "" 	if i == "{": 		out += indent + "for i in stack.pop():\n" 		out += indent + "\tstack.append(i)\n" 		indent += "\t" 	if i == "[": 		out += indent + "if int(stack.pop()):\n" 		indent += "\t" 	if i == "|": 		indent = indent[:-1] 		out += indent + "else:\n" 		indent = indent + "\t" # print(out) exec(out) print(stack.pop()) 

Try it online!

Solution

^CveU?wDWp64oVq9z,km@` 

Decoded from digraphs:

I{[)|$$0;0__1__0__1'* 

Explanation

Golfed and explained, it looks like this:

I Push the input. { Foreach input: [ If int(current) == 1: ) Current += 1 | Else: $ Sort(stack) 0 stack.append(0) ; End all current looping structures. ___ Discard the trailing 0, the largest, and the second largest. 1' Push stringified 1. * Repeat the string TOS times. 
\$\endgroup\$
5
  • \$\begingroup\$ Nobody has ever tried to crack it !? \$\endgroup\$ Commented Apr 9, 2020 at 3:09
  • \$\begingroup\$ I would've tried if it weren't for that I accidentally memorized the answer from when you put it in the code. \$\endgroup\$ Commented Apr 9, 2020 at 5:26
  • \$\begingroup\$ @PkmnQ Teach me how to memorize ^CveU?wDWp64oVq9z,km@`. How are you that clever? \$\endgroup\$ Commented Apr 9, 2020 at 5:27
  • \$\begingroup\$ Ok, I should've been more clear, I memorized the first part, and noticed the NOP chain. I also memorized the Slindow/Base code. \$\endgroup\$ Commented Apr 9, 2020 at 5:27
  • \$\begingroup\$ This happens with old cops-and-robbers challenges. \$\endgroup\$ Commented Apr 9, 2020 at 18:14
2
\$\begingroup\$

This was a bad idea since almost all esoteric languages look unreadable (look at Jelly).
But here goes:

Pylongolf2 beta6

Pushing to the Stack

Pushing to the stack acts differently that in other languages.
The code 78 pushes 7 and 8 into the stack, however in Pylongolf it pushes 78.
In Pylongolf2 this is toggleable with Ü.

Commands

) Print the stack. Ü Toggle the method Pylongolf2 uses for pushing to stack. a The useless command, removes and adds the selected item in the same place. c Ask for input. n Convert string to a number. " Toggle string mode for pushing text to the stack. s Convert a number to a string. ╨ Encode the selected item (it must be a string). _ Duplicate the selected item next to itself. b Swap places between the selected item and the one before. d Set the selected item to the last one. m Move the selected item to the end of the stack. @ Select an item. (Number required after this command as an argument). w Wait a specified amount of time (the time is taken from the stack). = Compare the selected item to the one before. (WARNING. THIS DELETES THE 2 ITEMS AND PLACES A true OR A false) (V2 beta) ~ Print the stack nicely. (V2 beta) ² Square a number. (V3 beta) | Split a string to an array by the character after |. (V4 beta) ♀ Pop the array. (the contents are left in the stack) (V4 beta) > Begin a while statement. (V5 beta) < Loop back to the beginning of the while statement. (V5 beta) ! Break out of the while statements. (V5 beta) ? An if statement, does nothing if the selected item is a `true` boolean. (V6 beta) ¿ If an if statement is `false`, the interpreter skips everything to this character. (V6 beta) 

String Concatenation and Removing a Regex Pattern from a String

The + symbol concatenates strings.
You are able to use the - symbol to remove characters following a regex pattern from a string:

c╨2"[^a-zA-Z]"-~ 

This code takes input and removes all non-alphabetical characters by removing all patterns matching [^a-zA-Z].
The selected item must be the regex and the one before must be the string to edit.

If Statements

To do if statements, put a = to compare the selected item and the one after.
This places either a true or a false in it's place.
The command ? checks this boolean.
If it's true then it does nothing and the interpreter goes on.
If it's false then the interpreter skips to the closest ¿ character.

Taken from the Github page.

Interpreter for Pylongolf2 (Java):

package org.midnightas.pylongolf2; import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Random; import java.util.Scanner; public class Pylongolf { public static final void main(String[] args) throws Exception { String content = new String(Files.readAllBytes(Paths.get(new File(args[0]).toURI()))) + " "; boolean fullreadMode = true; List<Object> stack = new ArrayList<Object>(); List<Integer> whileStatements = new ArrayList<Integer>(); HashMap<String, Object> vars = new HashMap<String, Object>(); int ifStatements = 0; Scanner scanner = new Scanner(new UnclosableDecorator(System.in)); int selectedIndex = 0; for (int cl = 0; cl < content.length(); cl++) { char c = content.charAt(cl); if (isNumber(c)) { if (!fullreadMode) { stack.add(Double.parseDouble(c + "")); } else { String number = ""; for (int cl0 = cl; cl0 < content.length(); cl0++) { if (isNumber(content.charAt(cl0))) { number += content.charAt(cl0); } else { cl = cl0 - 1; stack.add(Double.parseDouble(number)); break; } } } } else if (c == ')') { System.out.println(Arrays.toString(stack.toArray())); } else if (c == 'Ü') { fullreadMode = !fullreadMode; } else if (c == '+') { if (stack.get(selectedIndex) instanceof Object[]) { Object[] obj = (Object[]) stack.remove(selectedIndex); Double dbl = new Double(0d); for (Object o : obj) { dbl += (Double) o; } stack.add(selectedIndex, dbl); } else { Object obj0 = stack.remove(selectedIndex); Object obj1 = stack.remove(selectedIndex); if (obj0 instanceof Number && obj1 instanceof Number) stack.add(((Number) obj0).doubleValue() + ((Number) obj1).doubleValue()); else if (obj0 instanceof String) { stack.add(obj0.toString() + obj1.toString()); } } } else if (c == '-') { Object obj0 = stack.remove(selectedIndex); Object obj1 = stack.remove(selectedIndex); if (obj0 instanceof Number && obj1 instanceof Number) stack.add(((Number) obj0).doubleValue() - ((Number) obj1).doubleValue()); else if (obj0 instanceof String && obj1 instanceof String) { stack.add(obj0.toString().replaceAll(obj1.toString(), "")); } } else if (c == '*') { Object obj0 = stack.remove(selectedIndex); Object obj1 = stack.remove(selectedIndex); if (obj0 instanceof Number && obj1 instanceof Number) stack.add(((Number) obj0).doubleValue() * ((Number) obj1).doubleValue()); } else if (c == '/') { Object obj0 = stack.remove(selectedIndex); Object obj1 = stack.remove(selectedIndex); if (obj0 instanceof Number && obj1 instanceof Number) stack.add(((Number) obj0).doubleValue() / ((Number) obj1).doubleValue()); } else if (c == 'a') { stack.add(selectedIndex, stack.remove(selectedIndex)); } else if (c == 'c') { stack.add(scanner.nextLine()); } else if (c == 'n') { if (stack.get(selectedIndex) instanceof String) { stack.add(selectedIndex, Double.parseDouble(stack.remove(selectedIndex).toString())); } else if (stack.get(selectedIndex) instanceof Object[]) { Object[] oldArray = (Object[]) stack.remove(selectedIndex); Object[] newArray = new Object[oldArray.length]; for (int i = 0; i < oldArray.length; i++) { newArray[i] = Double.parseDouble(oldArray[i].toString()); } stack.add(selectedIndex, newArray); } } else if (c == '"') { String string = "\""; for (int cl0 = cl + 1; cl0 < content.length(); cl0++) { string = string + content.charAt(cl0); if (content.charAt(cl0) == '"') { stack.add(string.substring(1, string.length() - 1)); cl = cl0; break; } } } else if (c == 's') { Object obj = stack.remove(selectedIndex); if (obj instanceof Double) { Double dbl = (Double) obj; if (dbl.doubleValue() == Math.floor(dbl)) { stack.add(selectedIndex, "" + dbl.intValue() + ""); } else { stack.add(selectedIndex, "" + dbl + ""); } } } else if (c == '╨') { cl++; char editmode = content.charAt(cl); if (editmode == '0') { stack.add(selectedIndex, rot13(stack.remove(selectedIndex).toString())); } else if (editmode == '1') { stack.add(selectedIndex, new StringBuilder(stack.remove(selectedIndex).toString()).reverse().toString()); } else if (editmode == '2') { stack.add(selectedIndex, stack.remove(selectedIndex).toString().toLowerCase()); } else if (editmode == '3') { stack.add(selectedIndex, stack.remove(selectedIndex).toString().toUpperCase()); } } else if (c == '_') { stack.add(selectedIndex, stack.get(selectedIndex)); } else if (c == 'b') { stack.add(selectedIndex + 1, stack.remove(selectedIndex)); } else if (c == 'd') { selectedIndex = stack.size() == 0 ? 0 : stack.size() - 1; } else if (c == 'm') { stack.add(stack.remove(selectedIndex)); } else if (c == '@') { String number = ""; for (int cl0 = cl + 1; cl0 < content.length(); cl0++) { if (isNumber(content.charAt(cl0))) number += content.charAt(cl0); else { cl = cl0 - 1; selectedIndex = Integer.parseInt(number); break; } } } else if (c == 'w') { String number = ""; for (int cl0 = cl + 1; cl0 < content.length(); cl0++) { if (isNumber(content.charAt(cl0))) number += content.charAt(cl0); else { cl = cl0 - 1; Thread.sleep(Long.parseLong(number)); break; } } } else if (c == '=') { Object obj0 = stack.remove(selectedIndex); Object obj1 = stack.remove(selectedIndex); stack.add(new Boolean(obj0.equals(obj1))); } else if (c == '~') { for (Object o : stack) System.out.print(o); System.out.println(); } else if (c == '²') { if (stack.get(selectedIndex) instanceof Double) { Double dbl = (Double) stack.remove(selectedIndex); stack.add(selectedIndex, dbl * dbl); } else if (stack.get(selectedIndex) instanceof Object[]) { Object[] obj = (Object[]) stack.remove(selectedIndex); Object[] newArray = new Object[obj.length]; for (int i = 0; i < obj.length; i++) { newArray[i] = Math.pow((Double) obj[i], 2); } stack.add((Object[]) newArray); } } else if (c == '|') { String string = (String) stack.remove(selectedIndex); cl++; char splitChar = content.charAt(cl); stack.add((Object[]) string.split(splitChar + "")); } else if (c == '♀') { for (Object obj : (Object[]) stack.remove(selectedIndex)) { stack.add(selectedIndex, obj); } } else if (c == '>') { whileStatements.add(new Integer(cl)); } else if (c == '<') { cl = whileStatements.get(whileStatements.size() - 1); } else if (c == '!') { whileStatements.remove(whileStatements.size() - 1); } else if (c == '?') { if (stack.get(selectedIndex) instanceof Boolean) { Boolean bool = (Boolean) stack.remove(selectedIndex); if (bool == false) { ifStatements++; for (int cl0 = cl; cl0 < content.length(); cl0++) { if (content.charAt(cl0) == '¿') { ifStatements--; cl = cl0; } } } } } else if (c == 't') { break; } else if (c == '(') { stack.remove(selectedIndex); } else if (c == ':') { cl++; char charToVar = content.charAt(cl); vars.put(charToVar + "", stack.remove(selectedIndex)); } else if (c >= 'A' && c <= 'Z') { stack.add(vars.get(c + "")); } else if (c == 'r') { stack.add(selectedIndex, (double) new Random().nextInt(((Double) stack.remove(selectedIndex)).intValue() + 1)); } } scanner.close(); } public static String rot13(String input) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < input.length(); i++) { char c = input.charAt(i); if (c >= 'a' && c <= 'm') c += 13; else if (c >= 'A' && c <= 'M') c += 13; else if (c >= 'n' && c <= 'z') c -= 13; else if (c >= 'N' && c <= 'Z') c -= 13; sb.append(c); } return sb.toString(); } public static boolean isNumber(char c) { return c >= '0' && c <= '9'; } } 
\$\endgroup\$
2
  • \$\begingroup\$ Is this supposed to be hard to use? :/ \$\endgroup\$ Commented Jun 18, 2017 at 15:45
  • \$\begingroup\$ Is this right? Take input, split by 0, sort, find 3rd maximum item. \$\endgroup\$ Commented Mar 30, 2020 at 14:10

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.