在一次对printf的调用中实现Tic-Tac-Toe

2020-06-07 16:26:09

在对printf的单个调用中实现tic-tac-toe。为IOCCC 2020撰写。

#include<;stdio.h>;#定义N(A)";%";#a";$hhn";#定义O(a,b)";%10$";d";N(B)#定义U";%10$.*37$d";#定义G(A)";%";#a";$s。#定义H(a,b)G(A)G(B)#定义T(A)a#定义s(A)T(A)T(A)#定义A(A)s(A)T(A)a#定义n(A)A(A)a#定义D(A)n(A)A(A)#定义C(A)D(A)a#定义RC(C(N(12)G(12)#定义o(a,b,c)C(H(。a)D(G(A))C(H(b,b)G(B))n(G(B))O(32,c)R#定义SS O(78,55)R";\n\033[2J\n%26$s";;#定义E(a,b,c,d)H(a,b)G(C)O(253,11)R G(11)O(255,11)R H(11,d)N(D)O(253,35)R#定义S(a,b)O(254,11)H(a,b)N(68)R G(68)O(255,68)N(12)H(12)H(12,68)G(67)N(67)char*fmt=O(。39)N(40)N(41)N(42)N(43)N(66)N(69)N(24)O(22,65)O(5,70)O(8,44)N(45)N(46)N(47)N(48)N(49)N(50)N(51)N(52)N(53)O(28,54)O(5,55)O(2,56)O(3,57)O(4,58)O(13,73)O(4,71)N(72)O(20,59)N(60)N(61)N(62)N(63)N(64)R R E(1,2,3,13)E(4,5,6,13)E(7,8,9,13)E(1,4,7,13)E(2,5,8,13)E(3,6,9,13)E(1,5,9,13)E(3,5,7,13)E(14,15,16,23)E(17,18,19,23)E(20,21,22,23)E(14,17,20,23)E(15,18,21,23)E(16,19,22,23)E(14,18,22,23)E(16,18,20,23)R U O(255,38)R G(38)O(255,36)R H(13,23)O(255,11)R H(11,36)O(254,36)R G(36)O(255,36)R S(1,14)S(2,15)S(3,16)S(4,17)S(5,18)S(6,19)S(7,20)S(8,21)S(9,22)H(13,23)H(36,67)N(11)R G(11)";";O(255,25)R s(C(G(11)n(G(11))G(11)N(54)R C(";AA";)s(A(G(25)T(G(25))N(69)Ro(14,1,26)o(15,2,27)o(16,3,28)o(17,4,29)o(18,5,30)o(19,6,31)o(20,7,32)o(21,8,33)o(22,9,34)n(C(U))N(68)R H(36),13)G(23)N(11)R C(D(G(11)D(G(11))G(68)N(68)R G(68)O(49,35)R H(13,23)G(67)N(11)R C(H(11,11)G(11))A(G(11))C(H(36,36)G(36))s(G(36))O(32,58)R C(D(G(36)A(G(36))SS#定义参数d+6,d+8,d+10,d+12,d+14,d+16,d+18,d+20,d+22,0,d+46,d+52,d+48,d+24,d 26,d+28,d+30,d+32,d+34,d+36,d+38,d+40,d+50,(scanf。d+(6\2)+18*(1-d[2]%2)+d[4]*2),d,d+66,d+68,d+70,d+78,d+80,d+82,d+90,d+92,d+94,d+97,d+54,d[2],d+2,d+71,d+77,d+83,d+89,d+95,d+72,d+73,d+74\,d[2],d+2,d+71,d+77,d+83,d+89,d+95,d+72,d+73,d+74\,d[2]。d+102,d+99,d+67,d+69,d+79,d+81,d+91,d+93,d+98,d+103,d+58,d+60,d+98,d+126,d+127,\d+128,d+129字符d[538]={1,0,10,0,10};int main(){while(*d)printf(fmt,arg);}。

如果这是您喜欢的类型,您可能也会喜欢printbf。

这里,fmt是单个字符串,arg是printf的一系列参数。

虽然它的主要目的是充当一个真正的调试器,但是printf也恰好是图灵完成的。(参见“控制流弯曲:论控制流完整性的有效性”),我们在一篇实际出版的学术论文中介绍了这一点。有时你可以逃脱惩罚的事情。)。

我们利用这一事实在这个printf调用(以及读取用户输入的scanf()调用)中完全实现tic-tac-toe逻辑。

因为它将打印出0000000005(大小为10的5个填充),然后写入写入x的字节数。

我们使用printf执行任意计算,将内存视为二进制数组-每对字节一位:

一位由序列xx00表示,其中xx是任何非零字节。

但是假设1位的strlen(X)是1,0位的strlen(X)是0,我们有。

游戏本身被表示为一个18位的棋盘,每个玩家9位,还有一个在玩家1和玩家2之间交替的回合计数器。

为了检测谁赢了,我们实现以下逻辑。让A、B和C指针指向连续三个要测试的方块,D是在是否有AWIN的情况下要保存的位置。

";%A$s%B$s%C$s%1$253d%11$HHN";//r11=!(*A&;*B&;*C)零";%11$s%1$255d%11hhn";//r11=!r11ZERO";%11$s%D$s%D$HN";//*D=*D|r11。

也就是说,如果一行中有三个,我们将*D设置为1。对于两个玩家,我们对所有可能的三合一配置重复这一点。

ZERO宏用以下表达式确保写出的字节数为0/256。

其中,参数1是指向后跟空字节的临时变量的指针。

这是可行的,因为如果当前计数是0 mod 256,则";%1$HHN";将向参数1写入零,然后";%1$s";将永远不会发出任何文本。另一方面,如果计数不是0模256,则会将一个-1长度字符串写入参数1,然后";%1$s";会将计数加1。通过重复这个256次,我们最终将达到0-256。

为了决定打印什么,我们必须将内存中的位数组转换为X和O来打印。这实际上相当简单。给出1$指向博弈者1的正方形的指针,2$指向博弈者2的指针,以及3$指向棋盘字符串的指针,我们可以计算。

如果两者都不为真,则输出';';;如果R1为,则输出';X';;如果R2为,则输出';O';。

为了能够最终显示面板,同时仍然只使用一条printf语句,我们用以下语句结束该语句。

它是清除屏幕的转义序列,然后打印参数26。参数26是指向内存中的char*的指针,最初是未定义的,但在printf语句中,我们将构造该字符串,使其看起来像atic-tac-toe板。

取决于是否轮到P1或P2移动,游戏结束并等待某个新的开始,或者游戏结束并打成平局。

事实证明,这并不像看起来那么难。使用与前面相同的技巧,我们将BYTE FOR设置为。

字节';I&39;和';S&39;可以始终相同,我们对';E&39;/';N&39;执行相同的操作。

我们对scanf()格式字符串进行同样的动态创建,但原因不同。我们首先要运行printf()来显示第一块棋盘,然后轮流运行scanf()和printf()读取,然后显示移动。重要的是,我们不想在游戏结束时进行最终扫描。它应该直接退出。

但这将使我们需要的对printf的调用数量增加一倍。所以我们改为这样实现它。

(实际上,我们实际上将scanf()作为参数传递以避免外部陈述,但它具有相同的效果。)。

请注意,现在没有初始printf()。为了确保程序不会在第一个printf()之前阻塞,但我们将scanf()格式初始化为空字符串,以便它立即返回而不会阻塞。printf()调用第一次运行时,它写出";%hhd";以创建创建scanf()格式字符串。

这个项目显然是一些开创性的成就,像这样的成就是以前从未见过的。因此,如果你想在任何地方使用这个程序,它都是按照GPL v3授权的。

此程序是自由软件:您可以根据自由软件基金会第3版发布的GNU通用公共许可证条款对其进行重新分发和/或修改。

分发本程序的目的是希望它有用,但不提供任何担保;甚至不提供适销性或是否适用于特定目的的默示担保。有关更多详细信息,请参阅GNUGeneral Public License。

您应该已经收到了GNU通用公共许可证的副本以及此程序。如果没有,请参阅http://www.gnu.org/licenses/.