代码高尔夫:玩俄罗斯方块游戏

83

基础知识:

考虑以下俄罗斯方块和空白游戏场:

                                            0123456789
    I   O    Z    T    L    S    J         [          ]
                                           [          ]
    #   ##   ##   ###  #     ##   #        [          ]
    #   ##    ##   #   #    ##    #        [          ]
    #                  ##        ##        [          ]
    #                                      [          ]
                                           [==========]

游戏场的尺寸是固定的。顶部的数字只是用来表示列编号(也可见输入)。

输入:

1. 给定一个特定的游戏场(基于上述),其中可能已经填充了一些俄罗斯方块(这可以在单独的文件中或通过stdin提供)。

示例输入:

[          ]
[          ]
[          ]
[          ]
[ #    #  #]
[ ## ######]
[==========]

2. 给定一个字符串,描述了要插入(并下落)的俄罗斯方块以及其应插入的列(由空格分隔)。俄罗斯方块不需要旋转。输入可以从stdin读取。

示例输入:

T2 Z6 I0 T7

您可以假定输入是“格式良好”的(或在其不符合要求时产生未定义的行为)。
输出
渲染结果字段(“完整”行必须消失),并打印得分计数(每个丢失的行占10分)。
基于上面的示例输入,示例输出如下:
[ ] [ ] [ ] [# ###] [# ### ] [##### ####] [==========] 10
获胜者:
最短的解决方案(按代码字符计数)。使用示例很好。玩得开心!

5
@omouse: 请查看meta.stackoverflow.com - 代码高尔夫通常是被允许的(以社区维基形式)。 - ChristopheD
18
那就要通过投票关闭来解决问题。通过举报问题来吸引版主可能不太明智,因为社区一次又一次地容忍代码高尔夫(请参见代码高尔夫标签和元讨论;这并不新鲜)。我建议你谨慎行事。 - Mark Peters
8
@omouse:离题并不等同于垃圾信息。即使您无法投票关闭,那个垃圾信息标记也是不必要的。 - BoltClock
3
我在等一个APL专家!我打赌他只需要用3.5个符号就能做到。 - n8wrl
3
尺寸应该是固定的,但样本输入和空白区域的高度不同。那么它应该是多高? - Nabb
显示剩余24条评论
14个回答

27

GolfScript - 181个字符

不需要换行。输出在标准输出中,虽然一些错误存在于stderr中。
\10 应该被替换为相应的ASCII字符,以使程序成为181个字符。

{):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*

样例输入/输出:

$ cat inp
[          ]
[          ]
[          ]
[          ]
[ #    #  #]
[ ## ######]
[==========]
T2 Z6 I0 T7
$ cat inp|golfscript tetris.gs 2>/dev/null
[          ]
[          ]
[          ]
[#      ###]
[#     ### ]
[##### ####]
[==========]
10

俄罗斯方块压缩:
俄罗斯方块的各个形状被存储为三个八进制数字。这是一种简单的二进制表示,例如:T=[7,2,0], S=[6,3,0], J=[2,2,3]。在压缩中,I 形状使用[1],但稍后会明确将其设置为[1,1,1,1](即代码中的 4*)。所有这些数组都连接成一个单一的数组,然后转换为整数,再转换为字符串(使用基数126来最小化非可打印字符、长度和不遇到UTF8)。这个字符串非常短:"R@1(XBc_"

解压缩很简单。我们首先进行基数126转换,然后进行基数8转换("~\10"{base}/,即遍历"~\10"并对每个元素进行基数转换)。得到的数组被分成3组,I形状的数组是固定的(3/~4*)。然后将每个元素转换为二进制,并(删除零后)用字符串" #"中该索引处的字符替换每个二进制位(2base{" #"=}%...-1% - 注意我们需要反转数组,否则2会变成"# "而不是" #")。

棋盘/方块格式,放置方块
棋盘只是一个字符串数组,每行一个字符串。最初没有对其进行任何处理,因此我们可以使用输入上的n/(来生成它。方块也是字符串数组,左侧填充空格以表示它们的X位置,但没有尾随空格。通过将方块前置到数组中,并不断测试是否发生碰撞来放置方块。

碰撞测试是通过迭代方块中的所有字符并与棋盘上相同位置的字符进行比较来完成的。我们希望将#+=#+#视为碰撞,因此我们测试((piecechar&3)&boardchar)是否非零。在执行此迭代时,我们还使用((piecechar&3)|boardchar)更新(副本)棋盘,这样可以正确设置对#++#+[的值。如果在将方块向下移动一行后出现碰撞,则使用此更新的棋盘。

删除填充行非常简单。我们删除所有使"= "&返回false的行。填充行既没有=也没有,因此合取式将是一个空字符串,等同于false。然后我们计算已删除的行数,将该数添加到得分中,并在前面添加相应数量的"[ ... ]"。我们通过将网格的第一行替换为#来紧凑地生成它。
奖励 由于我们计算每个方块下落时的板子外观,因此我们可以将它们保存在堆栈上而不是删除它们!只需多三个字符,我们就可以输出所有这些位置(如果我们将板子状态单独间隔,则只需要两个字符)。
{):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*

这里正在进行一些极限的代码高尔夫比赛(我没想到这可以在不到200个字符的情况下完成)。干得好! - ChristopheD
8
太棒了。我希望我能理解GolfScript。等等... 不,我不想。 - P Daddy

26

Perl, 353 chars

(需使用 Perl 5.10 版本以上才能使用定义或运算符 //)。

从标准输入中获取所有输入。
请注意,^Q 表示 ASCII 17 (DC1/XON),^C 表示 ASCII 3,^@ 表示 ASCII 0 (NUL)。

while(<>){push@A,[split//]if/]/;while(/\w/g){for$i(0..6){for($f=0,$j=4;$j--;){$c=0;map{if($_){$i--,$f=$j=3,redo if$A[$k=$i+$j][$C=$c+$'+1]ne$";$A[$k][$C]="#"if$f}$c++}split//,unpack"b*",chr vec"3^@'^@c^@^Q^C6^@\"^C^Q^Q",index(OTZLSJI,$&)*4+$j,4;$s+=10,@A[0..$k]=@A[$k,0..$k-1],map{s/#/ /}@{$A[0]},$i++if 9<grep/#/,@{$A[$k]}}last if$f}}}print+(map@$_,@A),$s//0,$/

注释版:

while(<>){
    # store the playfield as an AoA of chars
    push@A,[split//]if/]/;
    # while we're getting pieces
    while(/\w/g){
            # for each line of playfield
            for$i(0..6){
                    # for each line of current piece
                    for($f=0,$j=4;$j--;){
                            # for each column of current piece
                            $c=0;
                            map{
                                    if($_){
                                            # if there's a collision, restart loop over piece lines
                                            # with a mark set and playfield line decremented
                                            $i--,$f=$j=3,redo if$A[$k=$i+$j][$C=$c+$'+1]ne$";
                                            # if we already found a collision, draw piece
                                            $A[$k][$C]="#"if$f
                                    }
                                    $c++
                            # pieces are stored as a bit vector, 16 bits (4x4) per piece,
                            # expand into array of 1's and 0's
                            }split//,unpack"b*",chr vec"3^@'^@c^@^Q^C6^@\"^C^Q^Q",index(OTZLSJI,$&)*4+$j,4;
                            # if this playfield line is full, remove it. Done by array slicing
                            # and substituting all "#"'s in line 0 with " "'s
                            $s+=10,@A[0..$k]=@A[$k,0..$k-1],map{s/#/ /}@{$A[0]},$i++if 9<grep/#/,@{$A[$k]}
                    }
                    # if we found a collision, stop iterating over the playfield and get next piece from input
                    last if$f
            }
    }
}
# print everything
print+(map@$_,@A),$s//0,$/

编辑1:一些严肃的高尔夫球比赛,修复了输出错误。
编辑2:内联了一些内容,将两个循环合并为一个,净节省(鼓掌声……)3个字符,混杂着高尔夫球比赛。
编辑3:消除了一些常见的子表达式,略微合并了一些常量,并调整了正则表达式。
编辑4:将俄罗斯方块的表示形式更改为打包的位向量,混杂着高尔夫球比赛。
编辑5:从俄罗斯方块字母直接翻译到数组索引,使用不可打印字符,混杂着高尔夫球比赛。
编辑6:修复了在r3(编辑2)中引入的清除顶行错误,由Nakilon发现。使用更多的不可打印字符。
编辑7:使用vec获取俄罗斯方块数据。利用游戏区域有固定尺寸的事实。将if语句=>if修改器,编辑2中循环合并开始产生效果。为0分情况使用//
编辑8:修复了在r6(编辑5)中引入的另一个错误,由Nakilon发现。
编辑9:在清除行时不创建新的引用,只是通过数组切片移动引用。将两个map合并为一个。更聪明的正则表达式。更聪明的for循环。混杂着高尔夫球比赛。
编辑10:内联俄罗斯方块数组,添加了注释版本。


非常好地工作(并且对于这个非平凡的问题已经有了很好的字符计数)。一个小特殊情况是我的perl(perl,v5.10.0构建于darwin-thread-multi-2level)似乎会将结果打印两次(输入通过管道传递)。 - ChristopheD
@ChristopheD:修复了重复的输出,我在主循环中打印,但只针对没有游戏区域的行。你可能有一个多余的换行符 :) - ninjalj
1
我还没有放弃 Perl!xD(虽然现在我也想看看其他的解决方案..) - poke
如果你没有更改我从 404chars 中复制粘贴的俄罗斯方块数据,那么你的程序将在这个测试中失败:http://paste.org.ru/?6ep1on - Nakilon
你是手动将注释转换为迷你版,还是有一个程序可以帮你完成这个过程?请在这里回答:https://dev59.com/PVHTa4cB1Zd3GeqPOQ9a - 700 Software
显示剩余6条评论

24

Ruby — 427 408 398 369 359

t=[*$<]
o=0
u=->f{f.transpose}
a=u[t.reverse.join.scan /#{'( |#)'*10}/]
t.pop.split.map{|w|m=(g='I4O22Z0121T01201L31S1201J13'[/#{w[0]}\d+/].scan(/0?\d/).zip a.drop w[1].to_i).map{|r,b|(b.rindex ?#or-1)-r.size+1}.max
g.map{|r,b|b.fill ?#,m+r.size,r.to_i}
v=u[a]
v.reject!{|i|i-[?#]==[]&&(o+=10;v)<<[' ']*10}
a=u[v]}
puts u[a].reverse.map{|i|?[+i*''+?]},t[-1],o

非常好的解决方案!我得看看你是如何编码俄罗斯方块形状的(这种方式看起来非常紧凑)。 - ChristopheD
3
我非常希望能够看到这段代码的详细解释。它看起来很棒...但我无法理解它。 - nocksock
1
@Nils Riedemann,我现在正在撰写一份说明,但正在考虑是现在发布还是在获胜者公布后发布。无论如何,我会发布并回答所有问题,因为这是一个社区维基,其主要目的是有用的。 - Nakilon
在Debian的ruby 1.9.2dev(2010年7月30日)上,这在你的测试案例http://paste.org.ru/?6ep1on中失败。此外,它是否始终将游戏区域扩展到10行? - ninjalj
@ninjalj,ruby 1.9.2p0(2010年8月18日)[i386-mingw32]http://paste.org.ru/?1qnjhj。看起来不错。我想10的宽度是俄罗斯方块的标准吧。 - Nakilon
我的输入文件有一个多余的换行符。至于这10行,我指的是行而不是列,当我尝试原始示例时,输出游戏区域高度为10行。 - ninjalj

17

Bash shell脚本 (304个字符)


更新: 修复了涉及到顶部行的拼图的错误。此外,现在输出被发送到标准输出,作为奖励,可以运行脚本来继续玩游戏(在这种情况下,您必须自己计算总分)。

这包括不可打印的字符,因此我提供了十六进制转储。将其保存为tetris.txt

0000000: 7461 696c 202d 3120 245f 7c7a 6361 743e  tail -1 $_|zcat>
0000010: 753b 2e20 750a 1f8b 0800 35b0 b34c 0203  u;. u.....5..L..
0000020: 5590 516b 8330 10c7 dff3 296e 4c88 ae64  U.Qk.0....)nL..d
0000030: a863 0c4a f57d 63b0 07f7 b452 88d1 b4da  .c.J.}c....R....
0000040: 1a5d 5369 91a6 df7d 899a d05d 5e72 bfbb  .]Si...}...]^r..
0000050: fbff 2fe1 45d5 0196 7cff 6cce f272 7c10  ../.E...|.l..r|.
0000060: 387d 477c c4b1 e695 855f 77d0 b29f 99bd  8}G|....._w.....
0000070: 98c6 c8d2 ef99 8eaa b1a5 9f33 6d8c 40ec  ...........3m.@.
0000080: 6433 8bc7 eeca b57f a06d 27a1 4765 07e6  d3.......m'.Ge..
0000090: 3240 dd02 3df1 2344 f04a 0d1d c748 0bde  2@..=.#D.J...H..
00000a0: 75b8 ed0f 9eef 7bd7 7e19 dd16 5110 34aa  u.....{.~...Q.4.
00000b0: c87b 2060 48a8 993a d7c0 d210 ed24 ff85  .{ `H..:.....$..
00000c0: c405 8834 548a 499e 1fd0 1a68 2f81 1425  ...4T.I....h/..%
00000d0: e047 bc62 ea52 e884 42f2 0f0b 8b37 764c  .G.b.R..B....7vL
00000e0: 17f9 544a 5bbd 54cb 9171 6e53 3679 91b3  ..TJ[.T..qnS6y..
00000f0: 2eba c07a 0981 f4a6 d922 89c2 279f 1ab5  ...z....."..'...
0000100: 0656 c028 7177 4183 2040 033f 015e 838b  .V.(qwA. @.?.^..
0000110: 0d56 15cf 4b20 6ff3 d384 eaf3 bad1 b9b6  .V..K o.........
0000120: 72be 6cfa 4b2f fb03 45fc cd51 d601 0000  r.l.K/..E..Q....

然后,在bash命令提示符下,最好使用已安装为vielvis

$ xxd -r tetris.txt tetris.sh
$ chmod +x tetris.sh
$ cat << EOF > b
> [          ]
> [          ]
> [          ]
> [          ]
> [ #    #  #]
> [ ## ######]
> [==========]
> EOF
$ ./tetris.sh T2 Z6 I0 T7 2>/dev/null
-- removed stuff that is not in standard out --
[          ]
[          ]
[          ]
[#      ###]
[#     ### ]
[##### ####]
[==========]
10

工作原理

该代码的自解压方式类似于使用 gzexe 脚本压缩的可执行程序。俄罗斯方块的方块被表示为 vi 编辑器命令序列。使用字符计数来检测碰撞,使用行计数来计算得分。

未压缩的代码:

echo 'rej.j.j.:wq!m'>I
echo '2rejh.:wq!m'>O
echo '2rej.:wq!m'>Z
echo '3rejh1.:wq!m'>T
echo 'rej.j2.:wq!m'>L
echo 'l2rej2h.:wq!m'>S
echo 'lrej.jh2.:wq!m'>J
for t
do for y in `seq 1 5`
do echo -n ${y}jk$((${t:1}+1))l|cat - ${t:0:1}|vi b>0
grep ========== m>0||break
[ `tr -cd '#'<b|wc -c` = `tr -cd '#'<m|wc -c` ]||break
tr e '#'<m>n
done
cat n>b
grep -v '##########' b>m
$((S+=10*(`wc -l < b`-`wc -l < m`)))
yes '[          ]'|head -7|cat - m|tail -7>b
done
cat b
echo $S

高尔夫之前的原始代码:

#!/bin/bash

mkpieces() {
    pieces=('r@j.j.j.' '2r@jh.' '2r@j.' '3r@jh1.' 'r@j.j2.' 'l2r@j2h.' 'lr@j.jh2.')
    letters=(I O Z T L S J)

    for j in `seq 0 9`; do
        for i in `seq 0 6`; do
            echo "jk$(($j+1))l${pieces[$i]}:wq! temp" > ${letters[$i]}$j
        done
    done
}

counthashes() {
    tr -cd '#' < $1 | wc -c
}

droppiece() {
    for y in `seq 1 5`; do
        echo -n $y | cat - $1 | vi board > /dev/null
        egrep '={10}' temp > /dev/null || break
        [ `counthashes board` -eq `counthashes temp` ] || break
        tr @ "#" < temp > newboard
    done
    cp newboard board
}

removelines() {
    egrep -v '#{10}' board > temp
    SCORE=$(($SCORE + 10 * (`wc -l < board` - `wc -l < temp`)))
    yes '[          ]' | head -7 | cat - temp | tail -7 > board
}

SCORE=0
mkpieces
for piece; do
    droppiece $piece
    removelines
done
cat board
echo $SCORE

1
一个bash文件,解压并运行vi..不确定这种丑陋的做法是否合法..但是它非常令人印象深刻,+1。向您致敬。 - Michael Anderson
需要极长的时间才能完成,然后对测试用例“T2 Z6 I0 T7 T2 Z6 T2 I5 I1 I0 T4 O8 T1 T6 T3 Z0 I9 I6 O7 T3 I2 O0 J8 L6 O7 O4 I3 J8 S6 O1 I0 O4”(与示例输入相同的板)生成错误的输出。此外,当通过管道传输时,会产生成千上万行垃圾行,并且板的结果可能应该在那里。 - Nabb
如果安装 Elvis 而不是 Vim 作为 vi,速度会快得多。 - PleaseStand
2
@Nabb:我只用三个字符的代价解决了所有这些问题。 - PleaseStand
哇,那是对Bash的一些相当令人印象深刻的滥用。 - P Daddy

13

Python: 504 519个字符

(Python 3解决方案) 目前需要按照顶部所示的格式设置输入(输入代码不计入字符数)。稍后我会扩展到从文件或stdin中读取。 现在可以使用提示符工作,只需将输入粘贴到其中(总共8行)。

R=range
f,p=[input()[1:11]for i in R(7)],p
for(a,b)in input().split():
 t=[' '*int(b)+r+' '*9for r in{'I':'#,#,#,#','O':'##,##','Z':'##, ##','T':'###, # ','L':'#,#,##','S':' ##,##','J':' #, #,##'}[a].split(',')]
 for r in R(6-len(t),0,-1):
  for i in R(len(t)):
   if any(a==b=='#'for(a,b)in zip(t[i],f[r+i])):break
  else:
   for i in R(0,len(t)):
    f[r+i]=''.join(a if b!='#'else b for(a,b)in zip(t[i],f[r+i]))
    if f[r+i]=='#'*10:del f[r+i];f[0:0]=[' '*10];p+=10
   break
print('\n'.join('['+r+']'for r in f[:7]),p,sep='\n')

不确定我能在那里节省更多。将字符转换为位域会丢失很多字符,但这比使用字符串节省更多的字符。此外,我不确定我能在那里去除更多的空格,但我稍后会尝试一下。
不能再减少了;在使用基于位域的解决方案之后,我又回到了字符串,因为我找到了一种更好的压缩方法(比位域多节省了8个字符!)。但是由于我忘记包含L并且点内有错误,我的字符计数只增加了...也许我以后会找到更好的压缩方法,但我认为我接近尽头了。原始和注释代码请参见下文:

原始版本:

field = [ input()[1:11] for i in range(7) ] + [ 0, input() ]
# harcoded tetrominoes
tetrominoes = {'I':('#','#','#','#'),'O':('##','##'),'Z':('##',' ##'),'T':('###',' # '),'L':('#','#','##'),'S':(' ##','##'),'J':(' #',' #','##')}
for ( f, c ) in field[8].split():
    # shift tetromino to the correct column
    tetromino = [ ' ' * int(c) + r + ' ' * 9 for r in tetrominoes[f] ]

    # find the correct row to insert
    for r in range( 6 - len( tetromino ), 0, -1 ):
        for i in range( len( tetromino ) ):
            if any( a == b == '#' for (a,b) in zip( tetromino[i], field[r+i] ) ):
                # skip the row if some pieces overlap
                break
        else:
            # didn't break, insert the tetromino
            for i in range( 0, len( tetromino ) ):
                # merge the tetromino with the field
                field[r+i] = ''.join( a if b != '#' else b for (a,b) in zip( tetromino[i], field[r+i] ) )

                # check for completely filled rows
                if field[r+i] == '#' * 10:
                    # remove current row
                    del field[r+i]
                    # add new row
                    field[0:0] = [' '*10]
                    field[7] += 10
            # we found the row, so abort here
            break
# print it in the requested format
print( '\n'.join( '[' + r + ']' for r in field[:7] ) )
# and add the points = 10 * the number of redundant lines at the end
print( str( field[7] ) )

我认为这不正确。没有规定只有底部的线可以消失,但根据您的评论,您只检查了那一行。 - Michael Madsen
请按照任务要求输入内容,可以从文件或标准输入中获取。 - Nakilon
6
你不觉得即使是被压缩的Python代码也相当易读吗? - EMP
@Evgeny,只有与Perl或Malbolge相比才行) - Nakilon
好吧,我是指相对于其他代码高尔夫答案而言“可读性”更强! - EMP
显示剩余3条评论

13

Ruby 1.9,357 355 353 339 330 310 309个字符

d=0
e=[*$<]
e.pop.split.map{|f|f="L\003\003\007J\005\005\007O\007\007Z\007\013S\013\007I\003\003\003\003T\017\005"[/#{f[j=0]}(\W*)/,1].bytes.map{|z|?\0+?\0*f[1].hex+z.to_s(2).tr("01"," #")[1,9]}
k,f,i=i,[p]+f,e.zip(f).map{|l,m|l.bytes.zip(m.to_s.bytes).map{|n,o|j|=n&3&q=o||0;(n|q).chr}*""}until j>0
e=[]
e+=k.reject{|r|r.sum==544&&e<<r.tr(?#,?\s)&&d+=10}}
puts e,d

请注意,需要替换\000转义字符(包括第三行中的空字节)为其实际的不可打印等价物。

示例输入:

[          ]
[          ]
[          ]
[          ]
[ #    #  #]
[ ## ######]
[==========]
T2 Z6 I0 T7

使用方法:

ruby1.9 tetris.rb < input
或者
ruby1.9 tetris.rb input

另一种让俄罗斯方块下落并保持所有玻璃在数组中的方法,即使有边框...很好。现在你将成为Ruby/Perl的领袖。附言:我不知道?\s - Nakilon

12

C,727 [...] 596 581 556 517 496 471 461 457个字符

这是我的第一个代码高尔夫比赛,我认为字符数可以更少,如果有经验的高尔夫选手能给我一些提示就好了。

当前版本也可以处理不同维度的游戏区域。输入可以在DOS / Windows和Unix格式中都有换行符。

优化之前的代码非常直观,俄罗斯方块存储在4个整数中,这些整数被解释为(7 * 3) x 4位数组,游戏区域按原样存储,方块下落并在开始和每次方块下落后删除完整的行。

我不确定如何计算字符数,所以我使用了去除所有不必要换行符的代码文件大小。

编辑 596 => 581:感谢KitsuneYMG,除了%ls建议外,其他都完美无缺。此外,我注意到可以使用putch而不是putchargetch不知道为什么不起作用),并且删除了#define G中的所有括号。

编辑 581 => 556:对于剩余的for和嵌套的F循环,我不满意,所以进行了一些合并、更改和删除循环,相当混乱但绝对值得。

编辑 556 => 517:最后找到了使a成为int数组的方法。一些N;c合并,不再使用break

编辑 496 => 471:游戏区域宽度和高度现在固定了。

编辑 471 => 461:进行了一些小修改,再次使用putchar,因为putch不是标准函数。

编辑:错误修复,完整的行在方块下落之前被删除而不是之后,因此可能会在末尾留下完整的行。修复不改变字符计数。

#define N (c=getchar())
#define G T[j%4]&1<<t*3+j/4
#define X j%4*w+x+j/4
#define F(x,m) for(x=0;x<m;x++)
#define W while
T[]={916561,992849,217,1},C[99],c,i,j,s,t,x,A,a[99],w=13;
main(){F(j,7)C["IJLSTZO"[j]]=j;
F(j,91)a[j]=N;
W(N>w){t=C[c];x=N-86;
W(c){F(j,12)if(G&&X>1?a[X]-32:0)c=0;
F(j,12)if(G&&X>w&&!c)a[X-w]=35;x+=w;}N;
F(i,6){A=0;t=i*w;F(x,w)A|=(a[t+x]==32);
if(!A){s++;F(j,t)a[t+w-j]=a[t-j];
x=1;W(a[x]-93)a[x++]=32;}}}
F(i,91)putchar(a[i]);printf("%i0",s);}

1
你不能将 for 定义为 #define F(x,m) for(x=0;x++<m;) 吗?它在C#上可以运行... :P - BrunoLM
1
如果你的代码写得正确,如果你编译为C语言,就不需要包含stdio.h头文件(除非我漏掉了什么?)。这样可以节省一些字符 :) - please delete me
1
你可以将N的定义替换为(c=getchar()),并删除所有的c=N行,这样可以节省6个字符。 除非我对此有误解,否则你应该将t减少到585。 - KitsuneYMG
这段代码不起作用。使用Nabb的测试用例(“T2 Z6 I0 T7 T2 Z6 T2 I5 I1 I0 T4 O8 T1 T6 T3 Z0 I9 I6 O7 T3 I2 O0 J8 L6 O7 O4 I3 J8 S6 O1 I0 O4”,与示例输入相同的板)时,输出中有完整的行。 - PleaseStand
1
类型默认为整数,至少对于C89的变量是这样的。 - ninjalj
显示剩余9条评论

8

Python 2.6+ - 334 322 316个字符

397 368 366个未压缩字符

#coding:l1
exec'xÚEPMO!½ï¯ i,P*Ýlš%ì­‰=‰Ö–*†­þz©‰:‡—Lò¾fÜ”bžAù,MVi™.ÐlǃwÁ„eQL&•uÏÔ‹¿1O6ǘ.€LSLÓ’¼›î”3òšL¸tŠv[ѵl»h;ÁºŽñÝ0Àë»Ç‡ÛûH.ª€¼âBNjr}¹„V5¾3Dë@¼¡•gO. ¾ô6 çÊsÃЮürÃ1&›ßVˆ­ùZ`Ü€ÿžcx±ˆ‹sCàŽ êüRô{U¯ZÕDüE+³ŽFA÷{CjùYö„÷¦¯Î[0þøõ…(Îd®_›â»E#–Y%’›”ëýÒ·X‹d¼.ß9‡kD'.decode('zip')

必须有一个换行符,我将其计为一个字符。

浏览器代码页可能会阻止成功复制和粘贴此代码,因此您可以选择从此代码生成文件:

s = """
23 63 6F 64 69 6E 67 3A 6C 31 0A 65 78 65 63 27 78 DA 45 50 4D 4F 03 21
10 BD EF AF 20 69 2C 50 2A 02 DD 6C 9A 25 EC AD 07 8D 89 07 3D 89 1C D6
96 2A 86 05 02 1B AD FE 7A A9 89 3A 87 97 4C F2 BE 66 DC 94 62 9E 41 F9
2C 4D 56 15 69 99 0F 2E D0 6C C7 83 77 C1 16 84 65 51 4C 26 95 75 CF 8D
1C 15 D4 8B BF 31 4F 01 36 C7 98 81 07 2E 80 4C 53 4C 08 D3 92 BC 9B 11
EE 1B 10 94 0B 33 F2 9A 1B 4C B8 74 8A 9D 76 5B D1 B5 6C BB 13 9D 68 3B
C1 BA 8E F1 DD 30 C0 EB BB C7 87 DB FB 1B 48 8F 2E 1C AA 80 19 BC E2 42
4E 6A 72 01 7D B9 84 56 35 BE 33 44 8F 06 EB 40 BC A1 95 67 4F 08 2E 20
BE F4 36 A0 E7 CA 73 C3 D0 AE FC 72 C3 31 26 9B DF 56 88 AD F9 5A 60 DC
80 FF 9E 63 78 B1 88 8B 73 43 E0 8E A0 EA FC 52 F4 7B 55 8D AF 5A 19 D5
44 FC 45 2B B3 8E 46 9D 41 F7 7B 43 6A 12 F9 59 F6 84 F7 A6 01 1F AF CE
5B 30 FE F8 F5 85 28 CE 64 AE 5F 9B E2 BB 45 23 96 59 25 92 9B 94 EB FD
10 D2 B7 58 8B 64 BC 2E DF 39 87 6B 44 27 2E 64 65 63 6F 64 65 28 27 7A
69 70 27 29
"""

with open('golftris.py', 'wb') as f:
    f.write(''.join(chr(int(i, 16)) for i in s.split()))

测试

俄罗斯方块

[          ]
[          ]
[          ]
[          ]
[ #    #  #]
[ ## ######]
[==========]
T2 Z6 I0 T7

换行符必须是Unix风格(只有换行符)。最后一行可以选择性地带有尾随换行符。

测试方法:

> python golftris.py < intetris
[          ]
[          ]
[          ]
[#      ###]
[#     ### ]
[##### ####]
[==========]
10

该代码解压缩了原始代码,并使用exec执行它。这个被解压的代码大小为366个字符,看起来像这样:

import sys
r=sys.stdin.readlines();s=0;p=r[:1];a='[##########]\n'
for l in r.pop().split():
 n=int(l[1])+1;i=0xE826408E26246206601E>>'IOZTLSJ'.find(l[0])*12;m=min(zip(*r[:6]+[a])[n+l].index('#')-len(bin(i>>4*l&31))+3for l in(0,1,2))
 for l in range(12):
  if i>>l&2:c=n+l/4;o=m+l%4;r[o]=r[o][:c]+'#'+r[o][c+1:]
 while a in r:s+=10;r.remove(a);r=p+r
print''.join(r),s

需要换行,每个换行符占一个字符。

不要试图阅读此代码。变量名称是随机选择的,旨在寻求最高压缩率(使用不同的变量名称,我看到压缩后有342个字符)。下面是更易理解的版本:

import sys

board = sys.stdin.readlines()
score = 0
blank = board[:1] # notice that I rely on the first line being blank
full  = '[##########]\n'

for piece in board.pop().split():
    column = int(piece[1]) + 1 # "+ 1" to skip the '[' at the start of the line

    # explanation of these three lines after the code
    bits = 0xE826408E26246206601E >> 'IOZTLSJ'.find(piece[0]) * 12
    drop = min(zip(*board[:6]+[full])[column + x].index('#') -
               len(bin(bits >> 4 * x & 31)) + 3 for x in (0, 1, 2))

    for i in range(12):
        if bits >> i & 2: # if the current cell should be a '#'
            x = column + i / 4
            y = drop + i % 4
            board[y] = board[y][:x] + '#' + board[y][x + 1:]

    while full in board:      # if there is a full line,
        score += 10           # score it,
        board.remove(full)    # remove it,
        board = blank + board # and replace it with a blank line at top
        
print ''.join(board), score

关键在于我说过要解释的三行神秘代码。
方块的形状被编码在十六进制数中。每个俄罗斯方块都被认为占据一个3x4的单元格网格,其中每个单元格可以是空白(空格)或填充(#)。然后,每个方块用3个十六进制数字进行编码,每个数字描述一个4个单元格的列。最低有效数字描述最左边的列,并且每个数字的最低有效位描述每个列中最上面的单元格。如果某一位为0,则该单元格为空白,否则为“#”。例如,“I”型方块编码为“00F”,其中最低有效数字的四位设置为编码最左边列中的四个“#”,而“T”型方块编码为“131”,其中顶部两个位在中间设置。
然后将整个十六进制数向左移动一位(乘以2)。这将使我们忽略最下面的位。稍后我会解释为什么。
因此,给定输入的当前方块,我们找到索引,其中包含描述其形状的12位,然后将其向下移位,以便bits变量的1-12位(跳过第0位)描述当前方块。
drop的赋值确定了方块落到网格顶部以下的行数。第一行找到玩家场地每列顶部有多少空单元格,而第二行找到方块每列中最低的填充单元格。zip函数返回一个元组列表,其中每个元组由输入列表中每个项目的第n个单元格组成。因此,使用示例输入板,zip(board[:6] + [full])将返回:
[
 ('[', '[', '[', '[', '[', '[', '['),
 (' ', ' ', ' ', ' ', ' ', ' ', '#'),
 (' ', ' ', ' ', ' ', '#', '#', '#'),
 (' ', ' ', ' ', ' ', ' ', '#', '#'),
 (' ', ' ', ' ', ' ', ' ', ' ', '#'),
 (' ', ' ', ' ', ' ', ' ', '#', '#'),
 (' ', ' ', ' ', ' ', ' ', '#', '#'),
 (' ', ' ', ' ', ' ', '#', '#', '#'),
 (' ', ' ', ' ', ' ', ' ', '#', '#'),
 (' ', ' ', ' ', ' ', ' ', '#', '#'),
 (' ', ' ', ' ', ' ', '#', '#', '#'),
 (']', ']', ']', ']', ']', ']', ']')
]

我们从该列表中选择与适当列对应的元组,并找到该列中第一个'#'的索引。这就是为什么在调用zip之前附加了一个“完整”行的原因,因此当该列为空时,index将具有合理的返回值(而不是抛出异常)。
然后,为了找到每个方块的最低'#',我们移位并掩码描述该列的四个位,然后使用bin函数将其转换为一串由一和零组成的字符串。 bin函数仅返回有效位,因此我们只需要计算该字符串的长度即可找到最低占用单元格(最高有效位)。bin函数还会在前面添加'0b',因此我们需要减去它。我们还忽略了最低有效位。这就是十六进制数向左移位一位的原因。这是为了解决空列的问题,其字符串表示形式与仅填满顶部单元格的列(例如T形方块)的长度相同。
例如,如前所述,I方块的列为F00bin(0xF)'0b1111'。忽略'0b'后,我们有一个长度为4,这是正确的。但是bin(0x0)0b0。忽略'0b'后,我们仍然有一个长度为1,这是不正确的。为了解决这个问题,我们在末尾添加了一个附加位,以便我们可以忽略这个无关紧要的位。因此,代码中的+3用于解决'0b'在开头占用的额外长度和末尾无关紧要的位占用的额外长度。
所有这些都发生在三列((0,1,2))的生成器表达式中,并且我们取min结果以找到该方块在任何三列中接触之前可以下落的最大行数。
其余的内容通过阅读代码应该很容易理解,但是在这些赋值之后的for循环将该方块添加到棋盘中。在此之后,while循环删除满行,在顶部替换为空行,并计算得分。最后,将棋盘和分数打印到输出。

6

Python,298个字符

目前击败了所有非奇怪编程语言的解决方案(Perl,Ruby,C,bash...)


......甚至不使用代码压缩技巧。

import os
r=os.read
b='[%11c\n'%']'*99+r(0,91)
for k,v in r(0,99).split():
    t=map(ord,' -:G!.:; -:; !-.!"-. !". !./')['IJLOSTZ'.find(k)*4:][:4];v=int(v)-31
    while'!'>max(b[v+j+13]for j in t):v+=13
    for j in t:b=b[:v+j]+'#'+b[v+j+1:]
    b=b.replace('[##########]\n','')
print b[-91:],1060-10*len(b)/13

在测试示例中

[          ]
[          ]
[          ]
[          ]
[ #    #  #]
[ ## ######]
[==========]
T2 Z6 I0 T7

它输出

[          ]
[          ]
[          ]
[#      ###]
[#     ### ]
[##### ####]
[==========]
10

PS. 在Nakilon指出的情况下修复了一个错误,但代价是+5。


这是一些非常令人印象深刻的代码。除非有更好的方法,否则需要添加14个字符才能修复它(http://ideone.com/zeuYB),但即使如此,它仍然比除了GolfScript和Bash之外的所有东西都要好。这绝对是一个聪明的解决方案。 - P Daddy
是的,绝对是一个非常好的解决方案! - ChristopheD
@Nakilon:谢谢,显然我错过了那个。修正 @ 成本 293->298。 - Nas Banov
@P Daddy,谢谢 - 我找到了一种方法,在bash和工具链下进行修复,以保证我说“所有非奇怪的”是正确的 :) - Nas Banov
@Nabb:为了让代码更短,它是根据一些限制编写的。例如最多33个俄罗斯方块和99次行下落。可以轻松扩展,代价是+3。或者以低廉的价格 : ),限制可以完全解除。顺便说一句,这是一个很好的例子,说明拥有测试集会澄清规范(我在评论中一直在打扰ChristopherD)。 - Nas Banov

5

Golfscript 260字符版

我确定这可以改进,我对Golfscript算法语言还不太熟悉。

[39 26.2/0:$14{.(}:?~1?15?1?14 2??27?13.!14?2?27?14 1]4/:t;n/)\n*:|;' '/-1%.,:c;~{)18+:&;'XIOZTLSJX'\%~;,1-t\={{.&+.90>{;.}*|\=32=!{&13-:&;}*}%}6*{&+}/|{\.@<'#'+\)|>+}4*{'['\10*']'++}:
;n/0\~n+:|;0\{.'#'
={;)}{n+|+:|;}if\.}do;' '
n+\.@*|+\$+:$;.,1-<:|;}c*|n?$*

行末是相关的(行末不应该有一个)。无论如何,这里是我使用的一些测试用例:

> cat init.txt 
[          ]
[          ]
[          ]
[          ]
[ #    #  #]
[ ## ######]
[==========]
T2 Z6 I0 T7> cat init.txt | ruby golfscript.rb tetris.gsc
[          ]
[          ]
[          ]
[#      ###]
[#     ### ]
[##### ####]
[==========]
10
> cat init.txt [ ] [ ] [ ] [ ] [ # # #] [ ## ##### ] [==========] I0 O7 Z1 S4> cat init.txt | ruby golfscript.rb tetris.gsc [ ] [ ] [ ] [# ] [### #### ] [### ##### ] [==========] 10
> cat init.txt [ ] [ ] [ ] [ ## ### ] [ # # ] [ ## ######] [==========] T7 I0 I3> cat init.txt | ruby golfscript.rb tetris.gsc [ ] [ ] [ ] [ ] [# # ] [## # # # ] [==========] 20

请注意,输入文件中没有行尾符,行尾符会破坏脚本。


2
我认为GolfScript不是一个真正的竞赛语言... 它只是一个库,专门用于高尔夫任务... 这个库的大小可能会被添加到golfscript代码的大小中... - Nakilon
4
@Nakilon - 你不能对任何不是用原始机器语言编写的东西说出类似的话吗? :) Python解释器只是一个库,将其大小加入到您的条目中。 </sarcasm> - bta
2
@Nakilon:那只是解释器而已。它可以用任何其他语言编写;你仍然会说Golfscript不是一种真正的语言吗? - Michael Foukarakis
1
@Nabb:谢谢,我想我错过了一些技巧...不要难过,我也没有费心理解我的代码 :)。 - coderaj
1
@Michael Foukarakis,我可以在1分钟内编写自己的解释器来解决这个任务,那又怎样? - Nakilon
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接