编程Tetris块(从字面上看)


33

在游戏方块,有7种类型的砖或的TETR minoes,其在数学上被称为的TetR ö minoes,因为它们都与4个平方段制成:

俄罗斯方块砖

具有名称I,J,L,O,S,T和Z,它们对应于它们的近似形状。计算90°旋转时,总共有19种独特的形状:

I
I
I
I

IIII

 J
 J
JJ

JJJ
  J

JJ
J
J

J
JJJ

L
L
LL

  L
LLL

LL
 L
 L

LLL
L

OO
OO

 SS
SS

S
SS
 S

TTT
 T

T
TT
T

 T
TTT

 T
TT
 T

ZZ
 ZZ

 Z
ZZ
Z

挑战

编写一个矩形代码块,作为这19个形状的基本段。当此代码排列成一个形状时,应形成一个程序,输出与该形状相关的单个大写字母。这必须适用于所有19种形状。

在19种形状中,有些出现的前导空白区域完全用空格()填充。尾随的空白区域没有填充任何东西(因此程序并不总是完全矩形)。

假设这是您的代码块:

ABC
123

然后,将块安排到S Tetris中的任何一种都将是一个打印程序S

   ABCABC
   123123
ABCABC
123123

ABC
123
ABCABC
123123
   ABC
   123

(请注意,所有前导空白都填充有空格字符,并且没有行尾有空格。)

相同的想法适用于所有其他六个零件及其各自的旋转。

笔记

  • 所有19个最终程序都将以相同的编程语言运行。
  • 如果需要,您可以在所有程序中添加一个尾随换行符(不只是某些,全部或没有)。
  • 您的代码块中可能包含不是行终止符的任何字符(包括空格)。
  • 用可选的尾随换行符将字母输出到stdout(或您的语言的最接近的替代品)。

计分

代码块面积最小(宽度乘以高度)的提交将获胜。这实质上意味着最短的代码获胜,这就是为什么将其标记为。决胜局获得最高的投票答案。

ABC\n123示例的面积为3×2 = 6。

片段

给定一个代码块,此代码片段将生成所有19个程序:

<script>function drawShape(X,n,v){for(var t="",e=0;e<v.length;e++)for(var l=0;l<n.length;l++){for(var r=0;r<v[e].length;r++)t+="X"===v[e][r]?n[l]:X[l];t+="\n"}return t}function go(){var X=document.getElementById("input").value;if(0!=X.length){var n=X.replace(/./g," ").split("\n");X=X.split("\n");for(var v="I (v1):|I (v2):|J (v1):|J (v2):|J (v3):|J (v4):|L (v1):|L (v2):|L (v3):|L (v4):|O:|S (v1):|S (v2):|T (v1):|T (v2):|T (v3):|T (v4):|Z (v1):|Z (v2):".split("|"),t="X\nX\nX\nX|XXXX| X\n X\nXX|XXX\n  X|XX\nX\nX|X\nXXX|X\nX\nXX|  X\nXXX|XX\n X\n X|XXX\nX|XX\nXX| XX\nXX|X\nXX\n X|XXX\n X|X\nXX\nX| X\nXXX| X\nXX\n X|XX\n XX| X\nXX\nX".split("|"),e="",l=0;l<v.length;l++)e+=v[l]+"\n\n"+drawShape(n,X,t[l].split("\n"))+"\n";e=e.substring(0,e.length-2),document.getElementById("output").value=e}}</script><style>html *{font-family: monospace;}</style>Code Block:<br><textarea id='input' rows='8' cols='64'>ABC&#010;123</textarea><br><button type='button' onclick='go()'>Go</button><br><br>All 19 Programs:<br><textarea id='output' rows='24' cols='64'></textarea>


那么长宽比是2到3?或者可以是其他尺寸吗?另外,程序至少要做什么?假定空程序不计算在内,但是什么都不输出的程序却不起作用。
ASCIIThenANSI

@ASCIIThenANSI任何宽度和高度都可以。我想象将需要大于2 * 3的东西。共有19个程序,每个程序块的排列方式为19种不同的四聚体形状之一。当这些程序之一运行时,它将输出相应的俄罗斯方块字母。
加尔文的爱好2015年

哇!多么了不起的挑战!我们使用哪种语言有关系吗?
theonlygusti 2015年

@theonlygusti本网站上的几乎所有问题都允许使用任何语言。也不例外。
加尔文的爱好2015年

@ Calvin'sHobbies是的,我知道;我只是将代码片段误解为运行JavaScript答案的控制器。显然,它只是排列代码块。
theonlygusti 2015年

Answers:


16

<> <(鱼)-12 * 32 = 384

我本来打算寻求一个更优雅的解决方案,但最终还是以某种蛮力结束了我:

c  0  g84*%\
c2*0  g84*%\
0  84*g84*%\
c  84*g84*%\
c2*84*g84*%\
0  88*g84*%\
c  88*g84*%\
?v         \
;>?v~~?vv   
"L" o;  >   
"S" o; >~?v 
"T" o;    > 
;  >~?v"L"o 
;     >"J"o 
?v         \
 >~?v~~?vv  
"I"  o;  >  
"J"  o; >   
    \~~?vv  
"T"  o;  >  
"Z"  o; >   
?v         \
 >?v"J"o;   
   >?v"Z"o; 
"L"o;>?!v   
"J"o;   >?v 
"T"o;     > 
?v?v"I"o;  >
   >"L"o;   
 >?v"T"o;   
   >?v"O"o; 
     >"S"o; 

这很简单,它以3x3的正方形检查代码中的文本,并使用结果查看哪个tetrimino对应于代码的形状。我并没有花很多力气去打高尔夫球。

此处尝试代码(在使用代码段将其塑造为俄罗斯方块形状之后)

Z形(v1)中的代码示例


14

C(gcc)26x20 = 520 25x19 = 475 23x17 = 391

#ifndef M            //
#define M(a,b)a##b   //
#define W(z,x)M(z,x) //
char*s,*S[]={"!!!!c",//
"8M !7! M8 878","77",//
"7!!MO887","788OM!!7"//
,"N7!78","7N87!"},r[5//
],*p=r;i=7;main(){for//
(;i--;)strstr(S[i],r)//
&&putchar("ITOJLSZ"[i//
]);}                 //
#endif               //
__attribute__((      //
constructor(__LINE__)//
))W(f,__LINE__)(){s= //
"                     \
";*p++=strlen(s)+12;}//

最近,我被告知GNU的功能属性,最有趣的是该constructor属性,它使我在解决此问题的早期方法中以更round回的方式更简洁地实现了我正在做的事情。

这个想法的主旨与之前相同:构建一个字符串并在列表中搜索以标识代码所放置的俄罗斯方块块。这是通过调用函数来完成的,每个函数都会在字符串中添加一个字符。复杂性过去是,现在仍然是功能数量变化。

使用定义功能可以attribute((constructor(x)))使该功能在main()输入之前运行,x而优先级为可选(较低意味着可以更早运行)。这消除了对函数指针的需要,从而使我们可以删除宏,一些声明和调用链。

__LINE__由于保留了0-100优先级,因此使用优先级比较困难。但是,它不会导致错误,仅会引起警告,并且打高尔夫球时会产生很多警告,那么还有什么呢?

这本来可以省去另一列以根本不使用优先级,但是执行顺序似乎尚未定义。(在这种情况下,它们是相反的,但其他测试尚无定论。)

L v2的示例在这里

较旧,更便携的方法

#ifndef M              //
#define M(a,b) a##b    //
#define W(z,x)M(z,x)   //
#define F W(f,__LINE__)//
#define A W(a,__LINE__)//
char r[5],*S[]={"####k"//
,";<<US##;",";##SU<<;",//
";;",";T<;#","<S #;# S"//
"< <;<","T;#;<"},*s,*p=//
r;i;typedef(*T)();T a17//
,a36,a55,a74;main(){for//
(a17(),a36&&a36(),a55&&//
a55(),a74&&a74();i--;) //
strstr(S[i],r)&&putchar//
("ILJOZTS"[i]);}i=7;   //
#endif                 //
F();T A=F;F(){s=       //
"                       \
";*p++=strlen(s)+12;}  //

我在此站点上解决的最喜欢的问题之一。

首先,我要弄清楚每个块都会以某种方式确定其自身的坐标。使用__LINE__,行很容易,并且可以通过使用字符串文字的长度来找到水平相邻块的数量,如下所示:

char*s=//char*s=//
"       ""       "
;        ;        

取结果字符串的长度,然后除以适当的数字即可得到宽度。可悲的是,此方法看不到块之前的任何空白空间。我仍然怀疑字符串会是解决方案,因为空格在字符串之外几乎没有意义,例如a+++bvs a+ ++b.。我曾简要考虑过类似的内容,但没有提出任何有用的内容。另一种可能性是让标识符在遇到块的地方“粘合”在一起:

A  BA  B

如果这仍然可以提供有趣的解决方案,我不会感到惊讶。

尽管它很简单,但是我还是花了很多时间才能找到基于此块片段的字符串解决方案:

s=//
"  \
";//

如果该片段没有水平邻居,则第二行上的换行符将由反斜杠转义,从而创建一个长度为2的字符串。但是,如果该片段确实具有邻居,则反斜杠将替代在行首处的引号下一个块中的2个:

s=//s=//
"  \"  \
";//";//

这将创建长度为5的字符串“ \”。

更重要的是,这还允许检测块之前的空白空间:

    s=//
    "  \
    ";//

再次,换行符被转义,并且左侧空白块的空白包含在长度为6的结果字符串“”中。

我们总共需要担心一排共有七种不同的块配置,它们都构成了唯一长度的字符串:

2 "  "
---
s=//
"  \
";//

5 "  \"  "
---
s=//s=//
"  \"  \
";//";//

6 "      "
---
    s=//
    "  \
    ";//

9 "  \"      "
----
    s=//s=//
    "  \"  \
    ";//";//

10 "          "
---
        s=//
        "  \
        ";//

8 "  \"  \"  "
---
s=//s=//s=//
"  \"  \"  \
";//";//";//

11 "  \"  \"  \"  "
----
s=//s=//s=//s=//
"  \"  \"  \"  \
";//";//";//";//

最终的块当然不会具有如此短的长度,但是原理是相同的,而不管块的大小如何。这还有一个好处,那就是不需要单独的检测宽度的机制。通过将与该字符串的长度相对应的字符添加到结果字符串中,这19种配置中的每一个都会生成一个唯一的字符串,仅在运行完所有块之后才需要将其与合适的列表进行比较。

排序后,下一个大问题是如何“访问”每一行块。在C语言中,我们仅限于可以在函数外部完成的工作。我们还需要main()出现,但只有一次。后者可以通过#defines 轻松实现,但是如果我们希望后续块的代码在中main(),则该问题在于如何知道何时放置最后一个大括号。毕竟,我们不知道实际上将使用多少行块。因此,我们需要具有main()静态特性,而其余部分必须具有动态特性。

如果其他块行是独立的,则它们必须是函数,但是我们需要确保每个函数的名称都是唯一的,同时也要具有足够的可预测性才能从中调用main()。我们还需要一种机制来知道实际上要调用哪些函数。生成唯一名称可以通过帮助程序宏解决:

#define M(a,b) a##b     //
#define W(z,x)M(z,x)    //
#define F W(f,__LINE__) //
#define A W(a,__LINE__) //

调用F将创建一个标识符,其名称以f开头并以行号结尾。A功能相同,但带有前缀as,用于解决方案的第二部分,即函数指针。我们声明了四个这样的指针:

typedef(*T)();T a17,a36,a55,a74;

由于将它们声明为全局变量,因此可以方便地将它们设置为NULL。稍后,每个块行将具有以下代码:

F();T A=F;F()

这将首先声明一个函数,定义适当的函数指针以指向该函数(我们只能定义一次全局变量,但即使它初始化为NULL,之前的声明也不会算作定义),然后定义实际的功能。这允许main()调用任何非NULL的函数指针(a17永远不会为NULL):

a17(),a36&&a36(),a55&&a55(),a74&&a74()

这样做将构建字符串r,然后在字符串表中查找该字符串,如果找到,则输出适当的字母。

唯一剩下的技巧是,只要可以避免歧义或可以合并重叠的字符串,就可以缩短要匹配的字符串列表。

L v2的示例在这里


6

x86 opcode(.com),86 82字节

测试人员:

org 100h
macro e {
db $F6,$04,$DF,$78,$13,$75,$08,$00,$C0,$40,$83,$C6,$52,$EB,$F1,$88
db $C2,$00,$D0,$00,$D0,$46,$EB,$E8,$05,$02,$40,$73,$ED,$E8,$26,$00
db $50,$08,$43,$4D,$2C,$0C,$1C,$15,$A5,$14,$10,$13,$3F,$27,$20,$0F
db $51,$1D,$29,$49,$49,$4A,$4A,$4A,$4A,$4C,$4C,$4C,$4C,$4F,$53,$53
db $54,$54,$54,$54,$5A,$5A,$5F,$AE,$75,$FD,$8A,$55,$12,$B4,$02,$CD
db $21,$C3
}

macro n { db 82 dup $20 }

macro s { db 10 }

n
e
s
n
e
s
e
e  

资源:

BOF:    ;mov bx, 100h
p:      test [si], byte $DF
        js _a ; exist
        jnz _b ; newline
_z:     add al, al
        inc ax
q:      add si, EOF-BOF
        jmp p
_b:     mov dl, al
        add al, dl
        add al, dl
        inc si
        jmp p
_a:     add ax, 4002h
        jnc q
        call y
        db 80,8,67,77,44,12,28,21,165,20,16,19,63,39,32,15,81,29,41
        db 'IIJJJJLLLLOSSTTTTZZ'
y:      pop di
        scasb
        jnz y+1
        mov dl,[di+18]
        mov ah,2
        int $21
        ret
EOF:

在win7dos哪里运行初始化AX = 0,SI = 100,BX = 0 参考文献


如果您愿意减少支持的环境的数量,则可以假定SI = 100h并使用该寄存器而不是BX进行索引,以mov bx, 100h从开始删除3个字节。
gastropner '18

@gastropner完成并修复了一个我没有注意到的点
l4m2 '18年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.