Welcome to the Monastery | |
PerlMonks |
SOLUTION: Space Invadersby domm (Chaplain) |
on Jun 04, 2002 at 08:01 UTC ( [id://171425]=obfuscated: print w/replies, xml ) | Need Help?? |
This is a de-obfuscation of Space Invaders.
Overview As this is a rather long text, and the obfuscation isn't an easy one, I'll provide here a short overview on how Space Invaders works, followed by a detailed description. The game consists of two main parts:
The map is in fact one large string (containing line brakes, so it looks two-dimensional when printed). It is generated by the routine j, which gets passed an array consisting of various parts of this string. Move the stuff on the map This is done using a set of substitutions on the map-string. E.g. to move a space invader to the right, this regex is used: $map=~s/p / p/gs; There are are three different types of substitutions, handled by the routines c, x and z.Some other features of this obfu Some other tricks used are:
Adding line breaks: After adding some line breaks and comments, the code looks like this:
One trick that's resolved here is the using of ' instead of :: (as in Term'Readkey) or between sub and the subname (as in sub'a). Lets look at the different sections and explain each one a little bit: Term::ReadKey
Not much obfu here. Term::ReadKey is used for simple terminal control. Using it in 'cbrake' mode allows me to read in each key as it is pressed, as opposed to the standard perl way of reading from STDIN, where you have to wait for a linebrake to terminate input (not very handy for a game .. ) Init Stuff
Lets go through this line by line: $,=$/; sets $, the OUTPUT FIELD SEPARATOR to the value of $/, the INPUT RECORD SEPARATOR. The default of $/ is newline. Setting $, to newline will print a newline between each value passed to print. $/=++$b+$b; sets $/ to 2. Why 2, I hear you ask. Well, thanks to the dark magic of the pre- and post-autoincrement of $b. Why $b? Because $b (and $a) are the only variable names you don't have declare into a package (using my or the full package name) when running under strict. And why is that? Because $a and $b are used when sorting. $=='8'; sets $= to 8. $= is only used here so I don't have to declare a varname. $b=$=x28; sets $b to '88888888888888888888888888888'. As you remember, $= is '8', so the line can be read as $b = '8' x 28. You do know the x operator, do you? $*=$=x2; sets $* to '88' $b and $* will be used later to init the game board @!=(qw(4 _ " a 1 1 0 0 )); loads some values into the @! array. Again a special array name is used to trick strict. The first four values are later shifted and used to split some strings. The last four are used to save the number of space invaders killed, the number of moves, the game board and the clear screan command. $![7]=`clear`; saves the return value of the clear system call in @!. This speeds up the game a little bit, as only one system call is needed. If we need to clear the screen later on, we only need to print $!7 (or wherever the value will be located then, but see later...) That was the easey init part... Prepare the data used for the map and for some code
OK, this is a little complicated.
push(@!,split shift(@!), obviously pushes something on @!. But what? So, the code can be written clearer like this:
OK, but what gets pushed on @!? If you split the quoted string on 4's, you get this array:
Still not that clear, but that's all we can do now. So, we'll continue.. Start doing something..
Subroutine j - Init the Map j(split shift@!,pop@!); calls the subroutine j. You might recognise the split shift pop from above. Here we split the last element of @! (the block starting with 'SpaceInvaders'.. using the first element of @! (_, discarding both. That is, the following array gets passed to j: Or, a little bit more readable (I substituded some vars with their value):
If you replace the '8' with space, you can get a first glimpse of the map. Now, the sub: After you know the data getting passed to it, it is more or less clear what happens:
The first value of the array gets assigned to $_. The last line, @_&&j(@_) calls j again if there are still values in the array. If all values have been shifted off the array, we're finshed with the map and return to our regularly scheduled program.
Subroutine a - Make some subs a(split shift@!,pop@!);
You should know this by now. Split the last element of @! using the first, i.e. split this thing (Note that this should be quoted) Now, let's look at a
Here we generate a sub using eval. But it's kind of hard to see how those subs will look like in all this shift-pop-slashing. I'll show you one finished sub with the poped params in red:
sub x { eval 's/'.(shift).'/'.(shift).'/gs';@_&&x(@_) } Some things to note here:
Add some more data push(@!,split shift@!,pop@!); Once again the push-split-pop thing, this time a even more wierd, as we push onto the same array that we shift and pop on. So, this gets pushed onto @!: Still looks like line noise. Prepare some stuff
Assign $a (the map, you remember...) to $_, so that the substitutions will work remove line break and two spaces from @!. This removes the eyes of the Pacman-Ghost-Form and the line break from the source codes. Ah, those are maps in void context. Don't do this. (Once we have perl6, we'll be able to alter all elements of an array using the cool hyper-operator. This will allow for interesting new obfus, I'm sure...). Subroutine p - Let the game begin
OK, after all this boring initialising and code generating, let's grill some aliens .. $b=ReadKey -1; gets the pressed key and stores in $b. $b used to hold a lot of '8's, but the map is finsihed now, so we can overwrite it. $![1]%3==0&&x(split$=,$![6]); If the modulus of splited on $= ('8'), i.e.: (formatted in pairs). So, "\*" gets replaced by "8", "p8" by "8p", etc. The last two are more complex: /p(#.{59})$=/$=$1q/ replace "p" followed by (start capturing parenthesis) "#" and 59 other chars, followed by '8' with '8', the captures stuff, and 'q'. As you might have guessed, this happens when a space invaders reaches the right border and moves on to next line, changing his direction (i.e. is now a 'q') So, this line moves the space invaders every three moves. If the game is to easy or hard, you can change the %3 to %2 or %4 for faster or slower space invaders.
Those work more or less the same as described above: z moves the cannon and fires it. Note that z takes three arguments, two regexes and one condition. The cannon only moves if you pressed the right key. x again just moves some things. In this case it prevents the cannon from reaching the left and rigth border and removes the cannon shot from the top border. This can happen every move, not just ever third. c moves the cannon shot. In c, the substitution uses the emodifier, thus evaling the second part of the substitution. Because of this we can count the hits here. $![1]++; counts the moves. select (@-,@-,@-,0.05); sleeps for 0.05 seconds. ($![2]=$_)=~s/$=/ /g; change all the '8' to spaces and stores the map in $!2 /pW|Wq/&&h('Game over');. If a space invader reaches the cannon, you've lost. Welcome to our new alien overlords. $_!~/p|q/&&h('You saved our planet. Yeah');. If no invaders are left, you did it. print $![3],$![2];. This prints a clear screan and the map &p. Repeat.
Boring leftovers
This prints the end message and the number of invaders killed.
Sets the ReadMode back to normal. Without this, your terminal will behave rather strange after the game. This is registerd as an END block, so it gets calles even if you press CTRL-C. Hope you enjoyed this as much as I did ... -- #!/usr/bin/perl -w just another perl hacker print+seek(DATA,$=*.3,@-)?~~<DATA>:$:__DATA__
Back to
Obfuscated Code
|
|