Tetris

来自Botzone Wiki
跳转至: 导航搜索

俄罗斯方块(Tetris)是Botzone平台上的双人回合制游戏

作者

裁判程序 zys 播放器 zhouhy

故事

(尚未完成,稍安勿躁)

书接上文,自那琼柰派入门考校之后,四位敝衣少年经历种种玄学坎坷,终于拜得上仙为师,却哪知,道门也不是一个远离纷争的所在。他们上了山入了门,没想到师门给他们的第一件任务,竟是远渡重洋查探魔门。

「本派密探报,有海外倭岛名唤『贾巴沃克』者,以望气之法查探,魔气四溢,其中必有蹊跷。尔等初入本派,须有作为,这便是你们立功的大好时机!」

「可是,我们…还什么都…」

「噫,莫急,为师自有秘宝可保你们平安~」

说着,少年们的面前,四只小方块闪着奇异的光芒,带着流光飘进了他们的衣带。

「记得,若有危难,切莫逞能,四宝合一,方有生机!」

……

风和日丽的贾巴沃克岛上,似乎有着一股奇怪的气场,让人感到说不出的烦闷。

「唔噗噗噗……成天与代码之类的死物打交道也真是无聊,不过看来,有人正带着有趣的风物来玩了呢」

(未完待续)

人类玩家操作说明

两种操作方式任选其一即可。

  • 鼠标
    • 点击对方场地上的图标可以给对手指定方块
    • 拖动块可以移动位置,也可以穿墙(仅限可以直接从顶部落到的地方)
    • 左键点击块可以使之坠落到底
    • 右键点击屏幕可以提交操作(此时方块必须落地)
  • 键盘
    • 数字1~7可以给对手指定方块
    • 上下左右键可以移动位置
    • 英文/键可以将方块坠落到底
    • 回车键可以提交操作(此时方块必须落地)
    • 英文.键可以枚举方块的下一个可能的初始状态(用于在场地顶部转不过来之类的情况)

游戏规则

本游戏为双人回合制游戏,每个玩家在独立的矩阵场地上进行游戏。

每回合,双方玩家同时决策,既需要控制自己的方块落地,又需要为对手指定下一回合的方块。

玩家消去行后,方块会转移到对方场地底部,从而给对方增加难度。

地图

每个玩家的场地是高20格、宽10格的矩阵。在程序中,坐标是(x, y)的形式,其中x是横坐标,y是纵坐标。最左下方的格子的坐标是(1, 1)。

方块

游戏中,方块共有七种类型,每种类型的方块有四种姿态(即方向)。

方块只能逆时针旋转(即上左下右的顺序进行旋转),每次旋转时需要保证中间经过的姿态和最终的姿态都是合法的。旋转时旋转中心不动。

Tetris.Blocks.png

上图的方块在程序中,从左到右的类型编号分别是数字0~6,上左下右四种方向分别对应着数字0~3。

方块的坐标即其旋转中心的坐标。

给对手指定方块

第一回合,双方的方块类型由玄学系统指定,而且是相同的类型。

除了第一回合之外,每个回合的方块类型都是由对方指定的。因此,每回合玩家都要给出对方下一个方块的类型。

指定方块时,必须保证对方的各类方块的数目的极差(即最大值减最小值)不超过2,否则将会被视为非法行为。

描述自己的方块落地点

每回合,玩家可以得知自己本回合控制的方块类型。

(20日前发布的规则中的落地序列依然有效,你可以选择不修改以前的程序而正常进行游戏,我们会取最后一个元组作为实际输入,但是我们提供了更简单的交互方式)

Tetris routeExplained.png

【常见问题】

我需要给出什么?

  • 你需要给出最终状态坐标,系统帮你算路径和路径起点,存在合法路径就算合法输入

最终状态有什么要求?

  • 从起点通过若干水平平移、竖直下降、旋转可达

起点有什么要求?

  • 可以从顶端“畅通无阻”落入
  • 起点也是系统帮你找的

这是不是意味着我的方块只能垂直落到场地里?

  • 不,这只意味着你的方块的运动路径需要从能垂直落到的地方开始,之后可以有平移旋转

……所以,什么样的最终状态才算合法?

  • 简单来说,就是“能从顶端出发、经过若干非升操作可达的状态”


【具体解释】

你只需要给出你的块的最终状态的横坐标x、纵坐标y、姿态o,系统会自动寻找一个能够从场地顶端到目标位置的、纵坐标单调不增的路径,其中动作包括逆时针旋转、水平移动和垂直向下移动

  • 系统寻找的路径的起点会保证整个块都在场地内,且可以从y=20的地方“畅通无阻”地落到起点。(所谓「从y=20的地方畅通无阻地落到起点」等价于对起点应用样例的checkDirectDropTo函数)
  • 路径起点的所有可能姿态都会被枚举,存在一种得到合法路径的可能性即可。
  • 如果找不到满足条件的路径,则属于非法输入,并会被判负。

方块的消除与转移

每回合方块落地后,如果有行已满,那么这些行将会被“消除”,其它的行下落补上空行。

被消除的行并不会消失,而是会去除最后一个落地的方块(即本回合刚刚造成这几行消除的方块),然后按照同样的顺序堆叠起来,放置在对方的场地底部。对方场地原有的方块就会被“顶起来”。

积分

玩家具有积分,但是仅用于平局时的处理。

对于每一回合,玩家一次性消去1、2、3、4行的积分分别是1、3、5、7。

总积分是上述积分的和。

胜负

一切非法行为会被立即判负,包括程序崩溃、超时、坐标越界、格式错误、找不到到落点的路径等。双方同时判负会被认定为平局。

在互相给出方块后,如果场地中无法容纳该方块,则会被判负。

在发生方块转移后,如果场地的最高方块高度超过边界(即大于20),则会被判负。

如果双方同时超过边界,或者同时放不下方块,那么积分高者胜。

如果双方积分相同,那么游戏平局。

游戏交互方式

Botzone上其他游戏一样,本游戏每步(每次程序运行)限时1秒。

如果希望在回合间传递数据,请参阅Bot#交互

提示

如果你不能理解以下交互方式,可以直接看#样例程序,按照说明填写代码,并修改其中

// 做出决策(你只需修改以下部分)

// 决策结束,输出结果(你只需修改以上部分)

之间的部分即可!

本游戏与Botzone上其他游戏一样,使用相同的交互方式:Bot#交互

调试时可以通过拖动进度条来快速调试。

简单交互

request
第一回合的request是一行两个数字t和c,空格分隔,t表示自己拿到的方块类型,n表示自己的颜色(0红1蓝)
以后的request是一行四个数字t、x、y、o,空格分隔,t表示自己拿到的方块类型,x和y表示对方方块落在的位置坐标,o表示对方方块的姿态
response
每个回合的response都是一行四个数字t、x、y、o,空格分隔,t表示给对方下回合的方块类型,x和y表示自己方块最终点的位置坐标,o表示最终点方块的姿态

定义了request和response后,玩家的输入总体格式可以参看Bot#简化交互

对于样例对局,第四回合色玩家的样例输入和样例输出如下:

输入

4
1 0
3 2 1 2
1 2 1 2
5 5 1 2
2 1 4 1
2 8 1 2
2 4 2 1


输出

2 3 2 2

JSON交互

每回合Bot收到的request不是字符串,而是一个JSON对象,格式如下:

第一回合

{
	"block": Number, // 表示玩家本回合块类型
	"color": Number  // 表示玩家颜色
}

其他回合

{
	"x": Number,    // x和y表示对方方块落在的位置坐标,o表示对方方块的姿态
	"y": Number,
	"o": Number,
	"block": Number // 表示给对手的下回合块类型
}

Bot所需要输出的response也是JSON对象,格式如下:

{
	"x": Number,    // x和y表示自己方块最后的位置坐标,o表示这时方块的姿态
	"y": Number,
	"o": Number,
	"block": Number // 表示给对手的下回合块类型
}

对于样例对局,第四回合色玩家的样例输入和样例输出如下:(实际是单行紧缩的)

输入

{
	"requests": [
		{
			"block": 1,
			"color": 1
		},
		{
			"block": 3,
			"o": 2,
			"x": 2,
			"y": 1
		},
		{
			"block": 5,
			"o": 2,
			"x": 5,
			"y": 1
		},
		{
			"block": 2,
			"o": 2,
			"x": 8,
			"y": 1
		}
	],
	"responses": [
		{
			"x": 2,
			"y": 1,
			"o": 2,
			"block": 1
		},
		{
			"x": 1,
			"y": 4,
			"o": 1,
			"block": 2
		},
		{
			"x": 4,
			"y": 2,
			"o": 1,
			"block": 2
		}
	]
}

输出

{
	"response": {
		"x": 6,
		"y": 2,
		"o": 0,
		"block": 3
	}
}

样例程序

更新历史:

  • 简化交互方式
  • 修正transfer函数的大于等于号
  • 修正elimBonus数组的定义
  • 修正JSON样例的rotation函数

请注意展开按钮在右侧!-----=====≡≡≡≡≡>

简单交互样例程序

/**
 * Tetris 简单交互样例程序
 * https://wiki.botzone.org/index.php?title=Tetris
 * 更新于2017年4月20日:
 * 修正了rotation函数、将交互方式修改为新规则的格式,还有transfer函数里`if (h2 >= MAPHEIGHT)`改为`if (h2 > MAPHEIGHT)`
 */
// 注意:x的范围是1~MAPWIDTH,y的范围是1~MAPHEIGHT
// 数组是先行(y)后列(c)
// 坐标系:原点在左下角
 
#include <iostream>
#include <string>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <ctime>
using namespace std;
 
#define MAPWIDTH 10
#define MAPHEIGHT 20
 
// 我所在队伍的颜色(0为红,1为蓝,仅表示队伍,不分先后)
int currBotColor;
int enemyColor;
 
// 先y后x,记录地图状态,0为空,1为以前放置,2为刚刚放置,负数为越界
// (2用于在清行后将最后一步撤销再送给对方)
int gridInfo[2][MAPHEIGHT + 2][MAPWIDTH + 2] = { 0 };
 
// 代表分别向对方转移的行
int trans[2][4][MAPWIDTH + 2] = { 0 };
 
// 转移行数
int transCount[2] = { 0 };
 
// 运行eliminate后的当前高度
int maxHeight[2] = { 0 };
 
// 总消去行数的分数之和
int elimTotal[2] = { 0 };
 
// 一次性消去行数对应分数
const int elimBonus[] = { 0, 1, 3, 5, 7 };
 
// 给对应玩家的各类块的数目总计
int typeCountForColor[2][7] = { 0 };
 
const int blockShape[7][4][8] = {
	{ { 0,0,1,0,-1,0,-1,-1 },{ 0,0,0,1,0,-1,1,-1 },{ 0,0,-1,0,1,0,1,1 },{ 0,0,0,-1,0,1,-1,1 } },
	{ { 0,0,-1,0,1,0,1,-1 },{ 0,0,0,-1,0,1,1,1 },{ 0,0,1,0,-1,0,-1,1 },{ 0,0,0,1,0,-1,-1,-1 } },
	{ { 0,0,1,0,0,-1,-1,-1 },{ 0,0,0,1,1,0,1,-1 },{ 0,0,-1,0,0,1,1,1 },{ 0,0,0,-1,-1,0,-1,1 } },
	{ { 0,0,-1,0,0,-1,1,-1 },{ 0,0,0,-1,1,0,1,1 },{ 0,0,1,0,0,1,-1,1 },{ 0,0,0,1,-1,0,-1,-1 } },
	{ { 0,0,-1,0,0,1,1,0 },{ 0,0,0,-1,-1,0,0,1 },{ 0,0,1,0,0,-1,-1,0 },{ 0,0,0,1,1,0,0,-1 } },
	{ { 0,0,0,-1,0,1,0,2 },{ 0,0,1,0,-1,0,-2,0 },{ 0,0,0,1,0,-1,0,-2 },{ 0,0,-1,0,1,0,2,0 } },
	{ { 0,0,0,1,-1,0,-1,1 },{ 0,0,-1,0,0,-1,-1,-1 },{ 0,0,0,-1,1,-0,1,-1 },{ 0,0,1,0,0,1,1,1 } }
};// 7种形状(长L| 短L| 反z| 正z| T| 直一| 田格),4种朝向(上左下右),8:每相邻的两个分别为x,y
 
 
class Tetris
{
public:
	const int blockType;   // 标记方块类型的序号 0~6
	int blockX;            // 旋转中心的x轴坐标
	int blockY;            // 旋转中心的y轴坐标
	int orientation;       // 标记方块的朝向 0~3
	const int(*shape)[8]; // 当前类型方块的形状定义
 
	int color;
 
	Tetris(int t, int color) : blockType(t), shape(blockShape[t]), color(color)
	{ }
 
	inline Tetris &set(int x = -1, int y = -1, int o = -1)
	{
		blockX = x == -1 ? blockX : x;
		blockY = y == -1 ? blockY : y;
		orientation = o == -1 ? orientation : o;
		return *this;
	}
 
	// 判断当前位置是否合法
	inline bool isValid(int x = -1, int y = -1, int o = -1)
	{
		x = x == -1 ? blockX : x;
		y = y == -1 ? blockY : y;
		o = o == -1 ? orientation : o;
		if (o < 0 || o > 3)
			return false;
 
		int i, tmpX, tmpY;
		for (i = 0; i < 4; i++)
		{
			tmpX = x + shape[o][2 * i];
			tmpY = y + shape[o][2 * i + 1];
			if (tmpX < 1 || tmpX > MAPWIDTH ||
				tmpY < 1 || tmpY > MAPHEIGHT ||
				gridInfo[color][tmpY][tmpX] != 0)
				return false;
		}
		return true;
	}
 
	// 判断是否落地
	inline bool onGround()
	{
		if (isValid() && !isValid(-1, blockY - 1))
			return true;
		return false;
	}
 
	// 将方块放置在场地上
	inline bool place()
	{
		if (!onGround())
			return false;
 
		int i, tmpX, tmpY;
		for (i = 0; i < 4; i++)
		{
			tmpX = blockX + shape[orientation][2 * i];
			tmpY = blockY + shape[orientation][2 * i + 1];
			gridInfo[color][tmpY][tmpX] = 2;
		}
		return true;
	}
 
	// 检查能否逆时针旋转自己到o
	inline bool rotation(int o)
	{
		if (o < 0 || o > 3)
			return false;
 
		if (orientation == o)
			return true;
 
		int fromO = orientation;
		while (true)
		{
			if (!isValid(-1, -1, fromO))
				return false;
 
			if (fromO == o)
				break;
 
			fromO = (fromO + 1) % 4;
		}
		return true;
	}
};
 
// 围一圈护城河
void init()
{
	int i;
	for (i = 0; i < MAPHEIGHT + 2; i++)
	{
		gridInfo[1][i][0] = gridInfo[1][i][MAPWIDTH + 1] = -2;
		gridInfo[0][i][0] = gridInfo[0][i][MAPWIDTH + 1] = -2;
	}
	for (i = 0; i < MAPWIDTH + 2; i++)
	{
		gridInfo[1][0][i] = gridInfo[1][MAPHEIGHT + 1][i] = -2;
		gridInfo[0][0][i] = gridInfo[0][MAPHEIGHT + 1][i] = -2;
	}
}
 
namespace Util
{
 
	// 检查能否从场地顶端直接落到当前位置
	inline bool checkDirectDropTo(int color, int blockType, int x, int y, int o)
	{
		const int *def = blockShape[blockType][o];
		for (; y <= MAPHEIGHT; y++)
			for (int i = 0; i < 4; i++)
			{
				int _x = def[i * 2] + x, _y = def[i * 2 + 1] + y;
				if (_y > MAPHEIGHT)
					continue;
				if (_y < 1 || _x < 1 || _x > MAPWIDTH || gridInfo[color][_y][_x])
					return false;
			}
		return true;
	}
 
	// 消去行
	void eliminate(int color)
	{
		int &count = transCount[color] = 0;
		int i, j, emptyFlag, fullFlag;
		maxHeight[color] = MAPHEIGHT;
		for (i = 1; i <= MAPHEIGHT; i++)
		{
			emptyFlag = 1;
			fullFlag = 1;
			for (j = 1; j <= MAPWIDTH; j++)
			{
				if (gridInfo[color][i][j] == 0)
					fullFlag = 0;
				else
					emptyFlag = 0;
			}
			if (fullFlag)
			{
				for (j = 1; j <= MAPWIDTH; j++)
				{
					// 注意这里只转移以前的块,不包括最后一次落下的块(“撤销最后一步”)
					trans[color][count][j] = gridInfo[color][i][j] == 1 ? 1 : 0;
					gridInfo[color][i][j] = 0;
				}
				count++;
			}
			else if (emptyFlag)
			{
				maxHeight[color] = i - 1;
				break;
			}
			else
				for (j = 1; j <= MAPWIDTH; j++)
				{
					gridInfo[color][i - count][j] =
						gridInfo[color][i][j] > 0 ? 1 : gridInfo[color][i][j];
					if (count)
						gridInfo[color][i][j] = 0;
				}
		}
		maxHeight[color] -= count;
		elimTotal[color] += elimBonus[count];
	}
 
	// 转移双方消去的行,返回-1表示继续,否则返回输者
	int transfer()
	{
		int color1 = 0, color2 = 1;
		if (transCount[color1] == 0 && transCount[color2] == 0)
			return -1;
		if (transCount[color1] == 0 || transCount[color2] == 0)
		{
			if (transCount[color1] == 0 && transCount[color2] > 0)
				swap(color1, color2);
			int h2;
			maxHeight[color2] = h2 = maxHeight[color2] + transCount[color1];
			if (h2 > MAPHEIGHT)
				return color2;
			int i, j;
 
			for (i = h2; i > transCount[color1]; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color2][i][j] = gridInfo[color2][i - transCount[color1]][j];
 
			for (i = transCount[color1]; i > 0; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color2][i][j] = trans[color1][i - 1][j];
			return -1;
		}
		else
		{
			int h1, h2;
			maxHeight[color1] = h1 = maxHeight[color1] + transCount[color2];//从color1处移动count1去color2
			maxHeight[color2] = h2 = maxHeight[color2] + transCount[color1];
 
			if (h1 > MAPHEIGHT) return color1;
			if (h2 > MAPHEIGHT) return color2;
 
			int i, j;
			for (i = h2; i > transCount[color1]; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color2][i][j] = gridInfo[color2][i - transCount[color1]][j];
 
			for (i = transCount[color1]; i > 0; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color2][i][j] = trans[color1][i - 1][j];
 
			for (i = h1; i > transCount[color2]; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color1][i][j] = gridInfo[color1][i - transCount[color2]][j];
 
			for (i = transCount[color2]; i > 0; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color1][i][j] = trans[color2][i - 1][j];
 
			return -1;
		}
	}
 
	// 颜色方还能否继续游戏
	inline bool canPut(int color, int blockType)
	{
		Tetris t(blockType, color);
		for (int y = MAPHEIGHT; y >= 1; y--)
			for (int x = 1; x <= MAPWIDTH; x++)
				for (int o = 0; o < 4; o++)
				{
					t.set(x, y, o);
					if (t.isValid() && checkDirectDropTo(color, blockType, x, y, o))
						return true;
				}
		return false;
	}
 
	// 打印场地用于调试
	inline void printField()
	{
#ifndef _BOTZONE_ONLINE
		static const char *i2s[] = {
			"~~",
			"~~",
			"  ",
			"[]",
			"##"
		};
		cout << "~~:墙,[]:块,##:新块" << endl;
		for (int y = MAPHEIGHT + 1; y >= 0; y--)
		{
			for (int x = 0; x <= MAPWIDTH + 1; x++)
				cout << i2s[gridInfo[0][y][x] + 2];
			for (int x = 0; x <= MAPWIDTH + 1; x++)
				cout << i2s[gridInfo[1][y][x] + 2];
			cout << endl;
		}
#endif
	}
}
 
 
int main()
{
	// 加速输入
	istream::sync_with_stdio(false);
	srand(time(NULL));
	init();
 
	int turnID, blockType;
	int nextTypeForColor[2];
	cin >> turnID;
 
	// 先读入第一回合,得到自己的颜色
	// 双方的第一块肯定是一样的
	cin >> blockType >> currBotColor;
	enemyColor = 1 - currBotColor;
	nextTypeForColor[0] = blockType;
	nextTypeForColor[1] = blockType;
	typeCountForColor[0][blockType]++;
	typeCountForColor[1][blockType]++;
 
	// 然后分析以前每回合的输入输出,并恢复状态
	// 循环中,color 表示当前这一行是 color 的行为
	// 平台保证所有输入都是合法输入
	for (int i = 1; i < turnID; i++)
	{
		int currTypeForColor[2] = { nextTypeForColor[0], nextTypeForColor[1] };
		int x, y, o;
		// 根据这些输入输出逐渐恢复状态到当前回合
 
		// 先读自己的输出,也就是自己的行为
		// 自己的输出是自己的最后一步
		// 然后模拟最后一步放置块
		cin >> blockType >> x >> y >> o;
 
		// 我当时把上一块落到了 x y o!
		Tetris myBlock(currTypeForColor[currBotColor], currBotColor);
		myBlock.set(x, y, o).place();
 
		// 我给对方什么块来着?
		typeCountForColor[enemyColor][blockType]++;
		nextTypeForColor[enemyColor] = blockType;
 
		// 然后读自己的输入,也就是对方的行为
		// 裁判给自己的输入是对方的最后一步
		cin >> blockType >> x >> y >> o;
 
		// 对方当时把上一块落到了 x y o!
		Tetris enemyBlock(currTypeForColor[enemyColor], enemyColor);
		enemyBlock.set(x, y, o).place();
 
		// 对方给我什么块来着?
		typeCountForColor[currBotColor][blockType]++;
		nextTypeForColor[currBotColor] = blockType;
 
		// 检查消去
		Util::eliminate(0);
		Util::eliminate(1);
 
		// 进行转移
		Util::transfer();
	}
 
 
	int blockForEnemy, finalX, finalY, finalO;
 
	// 做出决策(你只需修改以下部分)
 
	// 遇事不决先输出(平台上编译不会输出)
	Util::printField();
 
	// 贪心决策
	// 从下往上以各种姿态找到第一个位置,且为了简便,只考虑能够直着落下的情况
	Tetris block(nextTypeForColor[currBotColor], currBotColor);
	for (int y = 1; y <= MAPHEIGHT; y++)
		for (int x = 1; x <= MAPWIDTH; x++)
			for (int o = 0; o < 4; o++)
			{
				if (block.set(x, y, o).isValid() &&
					Util::checkDirectDropTo(currBotColor, block.blockType, x, y, o))
				{
					finalX = x;
					finalY = y;
					finalO = o;
					goto determined;
				}
			}
 
determined:
	// 再看看给对方什么好
 
	int maxCount = 0, minCount = 99;
	for (int i = 0; i < 7; i++)
	{
		if (typeCountForColor[enemyColor][i] > maxCount)
			maxCount = typeCountForColor[enemyColor][i];
		if (typeCountForColor[enemyColor][i] < minCount)
			minCount = typeCountForColor[enemyColor][i];
	}
	if (maxCount - minCount == 2)
	{
		// 危险,找一个不是最大的块给对方吧
		for (blockForEnemy = 0; blockForEnemy < 7; blockForEnemy++)
			if (typeCountForColor[enemyColor][blockForEnemy] != maxCount)
				break;
	}
	else
	{
		blockForEnemy = rand() % 7;
	}
 
	// 决策结束,输出结果(你只需修改以上部分)
 
	cout << blockForEnemy << " " << finalX << " " << finalY << " " << finalO;
 
	return 0;
}

JSON交互样例程序

本地编译和调试的方式请查看 JSONCPP

/**
 * Tetris JSON交互样例程序
 * https://wiki.botzone.org/index.php?title=Tetris
 * 更新于2017年4月20日:
 * 修正了rotation函数、将交互方式修改为新规则的格式,还有transfer函数里`if (h2 >= MAPHEIGHT)`改为`if (h2 > MAPHEIGHT)`
 */
// 注意:x的范围是1~MAPWIDTH,y的范围是1~MAPHEIGHT
// 数组是先行(y)后列(c)
// 坐标系:原点在左下角

#include <iostream>
#include <string>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include "jsoncpp/json.h"
using namespace std;

#define MAPWIDTH 10
#define MAPHEIGHT 20

// 我所在队伍的颜色(0为红,1为蓝,仅表示队伍,不分先后)
int currBotColor;
int enemyColor;

// 先y后x,记录地图状态,0为空,1为以前放置,2为刚刚放置,负数为越界
// (2用于在清行后将最后一步撤销再送给对方)
int gridInfo[2][MAPHEIGHT + 2][MAPWIDTH + 2] = { 0 };

// 代表分别向对方转移的行
int trans[2][4][MAPWIDTH + 2] = { 0 };

// 转移行数
int transCount[2] = { 0 };

// 运行eliminate后的当前高度
int maxHeight[2] = { 0 };

// 总消去行数的分数之和
int elimTotal[2] = { 0 };

// 一次性消去行数对应分数
const int elimBonus[] = { 0, 1, 3, 5, 7 };

// 给对应玩家的各类块的数目总计
int typeCountForColor[2][7] = { 0 };

const int blockShape[7][4][8] = {
	{ { 0,0,1,0,-1,0,-1,-1 },{ 0,0,0,1,0,-1,1,-1 },{ 0,0,-1,0,1,0,1,1 },{ 0,0,0,-1,0,1,-1,1 } },
	{ { 0,0,-1,0,1,0,1,-1 },{ 0,0,0,-1,0,1,1,1 },{ 0,0,1,0,-1,0,-1,1 },{ 0,0,0,1,0,-1,-1,-1 } },
	{ { 0,0,1,0,0,-1,-1,-1 },{ 0,0,0,1,1,0,1,-1 },{ 0,0,-1,0,0,1,1,1 },{ 0,0,0,-1,-1,0,-1,1 } },
	{ { 0,0,-1,0,0,-1,1,-1 },{ 0,0,0,-1,1,0,1,1 },{ 0,0,1,0,0,1,-1,1 },{ 0,0,0,1,-1,0,-1,-1 } },
	{ { 0,0,-1,0,0,1,1,0 },{ 0,0,0,-1,-1,0,0,1 },{ 0,0,1,0,0,-1,-1,0 },{ 0,0,0,1,1,0,0,-1 } },
	{ { 0,0,0,-1,0,1,0,2 },{ 0,0,1,0,-1,0,-2,0 },{ 0,0,0,1,0,-1,0,-2 },{ 0,0,-1,0,1,0,2,0 } },
	{ { 0,0,0,1,-1,0,-1,1 },{ 0,0,-1,0,0,-1,-1,-1 },{ 0,0,0,-1,1,-0,1,-1 },{ 0,0,1,0,0,1,1,1 } }
};// 7种形状(长L| 短L| 反z| 正z| T| 直一| 田格),4种朝向(上左下右),8:每相邻的两个分别为x,y


class Tetris
{
public:
	const int blockType;   // 标记方块类型的序号 0~6
	int blockX;            // 旋转中心的x轴坐标
	int blockY;            // 旋转中心的y轴坐标
	int orientation;       // 标记方块的朝向 0~3
	const int(*shape)[8]; // 当前类型方块的形状定义

	int color;

	Tetris(int t, int color) : blockType(t), shape(blockShape[t]), color(color)
	{ }

	inline Tetris &set(int x = -1, int y = -1, int o = -1)
	{
		blockX = x == -1 ? blockX : x;
		blockY = y == -1 ? blockY : y;
		orientation = o == -1 ? orientation : o;
		return *this;
	}

	// 判断当前位置是否合法
	inline bool isValid(int x = -1, int y = -1, int o = -1)
	{
		x = x == -1 ? blockX : x;
		y = y == -1 ? blockY : y;
		o = o == -1 ? orientation : o;
		if (o < 0 || o > 3)
			return false;

		int i, tmpX, tmpY;
		for (i = 0; i < 4; i++)
		{
			tmpX = x + shape[o][2 * i];
			tmpY = y + shape[o][2 * i + 1];
			if (tmpX < 1 || tmpX > MAPWIDTH ||
				tmpY < 1 || tmpY > MAPHEIGHT ||
				gridInfo[color][tmpY][tmpX] != 0)
				return false;
		}
		return true;
	}

	// 判断是否落地
	inline bool onGround()
	{
		if (isValid() && !isValid(-1, blockY - 1))
			return true;
		return false;
	}

	// 将方块放置在场地上
	inline bool place()
	{
		if (!onGround())
			return false;

		int i, tmpX, tmpY;
		for (i = 0; i < 4; i++)
		{
			tmpX = blockX + shape[orientation][2 * i];
			tmpY = blockY + shape[orientation][2 * i + 1];
			gridInfo[color][tmpY][tmpX] = 2;
		}
		return true;
	}

	// 检查能否逆时针旋转自己到o
	inline bool rotation(int o)
	{
		if (o < 0 || o > 3)
			return false;

		if (orientation == o)
			return true;

		int fromO = orientation;
		while (true)
		{
			if (!isValid(-1, -1, fromO))
				return false;

			if (fromO == o)
				break;

			fromO = (fromO + 1) % 4;
		}
		return true;
	}
};

// 围一圈护城河
void init()
{
	int i;
	for (i = 0; i < MAPHEIGHT + 2; i++)
	{
		gridInfo[1][i][0] = gridInfo[1][i][MAPWIDTH + 1] = -2;
		gridInfo[0][i][0] = gridInfo[0][i][MAPWIDTH + 1] = -2;
	}
	for (i = 0; i < MAPWIDTH + 2; i++)
	{
		gridInfo[1][0][i] = gridInfo[1][MAPHEIGHT + 1][i] = -2;
		gridInfo[0][0][i] = gridInfo[0][MAPHEIGHT + 1][i] = -2;
	}
}

namespace Util
{

	// 检查能否从场地顶端直接落到当前位置
	inline bool checkDirectDropTo(int color, int blockType, int x, int y, int o)
	{
		const int *def = blockShape[blockType][o];
		for (; y <= MAPHEIGHT; y++)
			for (int i = 0; i < 4; i++)
			{
				int _x = def[i * 2] + x, _y = def[i * 2 + 1] + y;
				if (_y > MAPHEIGHT)
					continue;
				if (_y < 1 || _x < 1 || _x > MAPWIDTH || gridInfo[color][_y][_x])
					return false;
			}
		return true;
	}

	// 消去行
	void eliminate(int color)
	{
		int &count = transCount[color] = 0;
		int i, j, emptyFlag, fullFlag;
		maxHeight[color] = MAPHEIGHT;
		for (i = 1; i <= MAPHEIGHT; i++)
		{
			emptyFlag = 1;
			fullFlag = 1;
			for (j = 1; j <= MAPWIDTH; j++)
			{
				if (gridInfo[color][i][j] == 0)
					fullFlag = 0;
				else
					emptyFlag = 0;
			}
			if (fullFlag)
			{
				for (j = 1; j <= MAPWIDTH; j++)
				{
					// 注意这里只转移以前的块,不包括最后一次落下的块(“撤销最后一步”)
					trans[color][count][j] = gridInfo[color][i][j] == 1 ? 1 : 0;
					gridInfo[color][i][j] = 0;
				}
				count++;
			}
			else if (emptyFlag)
			{
				maxHeight[color] = i - 1;
				break;
			}
			else
				for (j = 1; j <= MAPWIDTH; j++)
				{
					gridInfo[color][i - count][j] =
						gridInfo[color][i][j] > 0 ? 1 : gridInfo[color][i][j];
					if (count)
						gridInfo[color][i][j] = 0;
				}
		}
		maxHeight[color] -= count;
		elimTotal[color] += elimBonus[count];
	}

	// 转移双方消去的行,返回-1表示继续,否则返回输者
	int transfer()
	{
		int color1 = 0, color2 = 1;
		if (transCount[color1] == 0 && transCount[color2] == 0)
			return -1;
		if (transCount[color1] == 0 || transCount[color2] == 0)
		{
			if (transCount[color1] == 0 && transCount[color2] > 0)
				swap(color1, color2);
			int h2;
			maxHeight[color2] = h2 = maxHeight[color2] + transCount[color1];
			if (h2 > MAPHEIGHT)
				return color2;
			int i, j;

			for (i = h2; i > transCount[color1]; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color2][i][j] = gridInfo[color2][i - transCount[color1]][j];

			for (i = transCount[color1]; i > 0; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color2][i][j] = trans[color1][i - 1][j];
			return -1;
		}
		else
		{
			int h1, h2;
			maxHeight[color1] = h1 = maxHeight[color1] + transCount[color2];//从color1处移动count1去color2
			maxHeight[color2] = h2 = maxHeight[color2] + transCount[color1];

			if (h1 > MAPHEIGHT) return color1;
			if (h2 > MAPHEIGHT) return color2;

			int i, j;
			for (i = h2; i > transCount[color1]; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color2][i][j] = gridInfo[color2][i - transCount[color1]][j];

			for (i = transCount[color1]; i > 0; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color2][i][j] = trans[color1][i - 1][j];

			for (i = h1; i > transCount[color2]; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color1][i][j] = gridInfo[color1][i - transCount[color2]][j];

			for (i = transCount[color2]; i > 0; i--)
				for (j = 1; j <= MAPWIDTH; j++)
					gridInfo[color1][i][j] = trans[color2][i - 1][j];

			return -1;
		}
	}

	// 颜色方还能否继续游戏
	inline bool canPut(int color, int blockType)
	{
		Tetris t(blockType, color);
		for (int y = MAPHEIGHT; y >= 1; y--)
			for (int x = 1; x <= MAPWIDTH; x++)
				for (int o = 0; o < 4; o++)
				{
					t.set(x, y, o);
					if (t.isValid() && checkDirectDropTo(color, blockType, x, y, o))
						return true;
				}
		return false;
	}

	// 打印场地用于调试
	inline void printField()
	{
#ifndef _BOTZONE_ONLINE
		static const char *i2s[] = {
			"~~",
			"~~",
			"  ",
			"[]",
			"##"
		};
		cout << "~~:墙,[]:块,##:新块" << endl;
		for (int y = MAPHEIGHT + 1; y >= 0; y--)
		{
			for (int x = 0; x <= MAPWIDTH + 1; x++)
				cout << i2s[gridInfo[0][y][x] + 2];
			for (int x = 0; x <= MAPWIDTH + 1; x++)
				cout << i2s[gridInfo[1][y][x] + 2];
			cout << endl;
		}
#endif
	}
}


int main()
{
	// 加速输入
	istream::sync_with_stdio(false);
	srand(time(NULL));
	init();

	int turnID, blockType;
	int nextTypeForColor[2];

	// 读入JSON
	string str;
	getline(cin, str);
	Json::Reader reader;
	Json::Value input;
	reader.parse(str, input);

	// 先读入第一回合,得到自己的颜色
	// 双方的第一块肯定是一样的
	turnID = input["responses"].size() + 1;
	auto &first = input["requests"][(Json::UInt) 0];

	blockType = first["block"].asInt();
	currBotColor = first["color"].asInt();

	enemyColor = 1 - currBotColor;
	nextTypeForColor[0] = blockType;
	nextTypeForColor[1] = blockType;
	typeCountForColor[0][blockType]++;
	typeCountForColor[1][blockType]++;


	// 然后分析以前每回合的输入输出,并恢复状态
	// 循环中,color 表示当前这一行是 color 的行为
	// 平台保证所有输入都是合法输入
	for (int i = 1; i < turnID; i++)
	{
		int currTypeForColor[2] = { nextTypeForColor[0], nextTypeForColor[1] };
		int x, y, o;
		// 根据这些输入输出逐渐恢复状态到当前回合

		// 先读自己的输出,也就是自己的行为
		// 自己的输出是一个序列,但是只有最后一步有用
		// 所以只保留最后一步
		// 然后模拟最后一步放置块
		auto &myOutput = input["responses"][i - 1];
		blockType = myOutput["block"].asInt();
		x = myOutput["x"].asInt();
		y = myOutput["y"].asInt();
		o = myOutput["o"].asInt();

		// 我当时把上一块落到了 x y o!
		Tetris myBlock(currTypeForColor[currBotColor], currBotColor);
		myBlock.set(x, y, o).place();

		// 我给对方什么块来着?
		typeCountForColor[enemyColor][blockType]++;
		nextTypeForColor[enemyColor] = blockType;

		// 然后读自己的输入,也就是对方的行为
		// 裁判给自己的输入是对方的最后一步
		auto &myInput = input["requests"][i];
		blockType = myInput["block"].asInt();
		x = myInput["x"].asInt();
		y = myInput["y"].asInt();
		o = myInput["o"].asInt();

		// 对方当时把上一块落到了 x y o!
		Tetris enemyBlock(currTypeForColor[enemyColor], enemyColor);
		enemyBlock.set(x, y, o).place();

		// 对方给我什么块来着?
		typeCountForColor[currBotColor][blockType]++;
		nextTypeForColor[currBotColor] = blockType;

		// 检查消去
		Util::eliminate(0);
		Util::eliminate(1);

		// 进行转移
		Util::transfer();
	}


	int blockForEnemy, finalX, finalY, finalO;

	// 做出决策(你只需修改以下部分)

	// 遇事不决先输出(平台上运行不会输出)
	Util::printField();

	// 贪心决策
	// 从下往上以各种姿态找到第一个位置,且为了简便,只考虑能够直着落下的地方
	Tetris block(nextTypeForColor[currBotColor], currBotColor);
	for (int y = 1; y <= MAPHEIGHT; y++)
		for (int x = 1; x <= MAPWIDTH; x++)
			for (int o = 0; o < 4; o++)
			{
				if (block.set(x, y, o).isValid() &&
					Util::checkDirectDropTo(currBotColor, block.blockType, x, y, o))
				{
					finalX = x;
					finalY = y;
					finalO = o;
					goto determined;
				}
			}

determined:
	// 再看看给对方什么好

	int maxCount = 0, minCount = 99;
	for (int i = 0; i < 7; i++)
	{
		if (typeCountForColor[enemyColor][i] > maxCount)
			maxCount = typeCountForColor[enemyColor][i];
		if (typeCountForColor[enemyColor][i] < minCount)
			minCount = typeCountForColor[enemyColor][i];
	}
	if (maxCount - minCount == 2)
	{
		// 危险,找一个不是最大的块给对方吧
		for (blockForEnemy = 0; blockForEnemy < 7; blockForEnemy++)
			if (typeCountForColor[enemyColor][blockForEnemy] != maxCount)
				break;
	}
	else
	{
		blockForEnemy = rand() % 7;
	}

	// 决策结束,输出结果(你只需修改以上部分)

	Json::Value output;
	Json::FastWriter writer;

	output["response"]["block"] = blockForEnemy;

	output["response"]["x"] = finalX;
	output["response"]["y"] = finalY;
	output["response"]["o"] = finalO;

	cout << writer.write(output);

	return 0;
}