GolfScript - 181 characters
Newlines are not necessary. Output is in standard output, although some errors are present in stderr.
\10
should be replaced by the corresponding ASCII character for the program to be 181 characters.
{):X!-{2B{" #"=}%X" ":f*+-1%}%:P;:>.{\!:F;>P{\(@{3&\(@.2$&F|:F;|}%\+}%\+F![f]P+:P
;}do;{"= "&},.,7^.R+:R;[>0="#"/f*]*\+}0"R@1(XBc_""~\10"{base}:B/3/~4*"nIOZTLSJR "
";:"*~;n%)n*~ 10R*+n*
Sample I/O:
$ cat inp
[ ]
[ ]
[ ]
[ ]
[ # # #]
[ ## ######]
[==========]
T2 Z6 I0 T7
$ cat inp|golfscript tetris.gs 2>/dev/null
[ ]
[ ]
[ ]
[# ###]
[# ### ]
[##### ####]
[==========]
10
Tetromino compression:
Pieces are stored as three base 8 digits. This is a simple binary representation, e.g.T=[7,2,0], S=[6,3,0], J=[2,2,3]
. [1]
is used for the I
piece in compression, but this is explicitly set to [1,1,1,1]
later (i.e. the 4*
in the code). All of these arrays are concatenated into a single array, which is converted into an integer, and then a string (base 126 to minimize non-printable characters, length, and not encounter utf8). This string is very short: "R@1(XBc_"
.
Decompression is then straightforward. We first do a base 126 conversion followed by a base 8 conversion ("~\10"{base}/
, i.e. iterate through "~\10"
and do a base conversion for each element). The resulting array is split into groups of 3, the array for I
is fixed (3/~4*
). We then convert each element to base 2 and (after removing zeros) replace each binary digit with the character of that index in the string " #"
(2base{" #"=}%...-1%
- note that we need to reverse the array otherwise 2
would become "# "
instead of " #"
).
Board/piece format, dropping pieces
The board is simply an array of strings, one for each line. No work is initially done on this, so we can generate it with n/(
on the input. Pieces are also arrays of strings, padded with spaces to the left for their X position, but without trailing spaces. Pieces are dropped by prepending to the array, and continuously testing whether there is a collision.
Collision testing is done by iterating through all characters in the piece, and comparing against the character of the same position on the board. We want to regard #
+=
and #
+#
as collisions, so we test whether ((piecechar&3)&boardchar) is nonzero. While doing this iteration, we also update (a copy of) the board with ((piecechar&3)|boardchar), which correctly sets the value for pairs #
+
,
+#
,
+[
. We use this updated board if there is a collision after moving the piece down another row.
Removing filled rows is quite simple. We remove all rows for which "= "&
return false. A filled row will have neither =
or
, so the conjunction will be a blank string, which equates to false. Then we count the number of rows that have been removed, add the count to the score and prepend that many "[ ... ]"
s. We generate this compactly by taking the first row of the grid and replacing #
with
.
Bonus
Since we compute what the board would look like in each position of the piece as it falls, we can keep these on the stack instead of deleting them! For a total of three characters more, we can output all these positions (or two characters if we have the board states single spaced).
{):X!-{2B{" #"=}%X" ":f*+-1%}%:P;:>.{>[f]P+:P(!:F;{\(@{3&\(@.2$&F|:F;|}%\+}%\+F!}
do;{"= "&},.,7^.R+:R;[>0="#"/f*]*\+}0"R@1(XBc_""~\10"{base}:B/3/~4*"nIOZTLSJR "
";:"*~;n%)n*~ ]{n*n.}/10R*