题面

查看题面

题目背景

鸭棋是一种风靡鸭子界的棋类游戏。事实上,它与中国象棋有一些相似之处,但规则不尽相同。在这里,我们将为你介绍鸭棋的规则。

鸭棋在一个 \(10\times 9\)\(10\)\(9\) 列)的网格棋盘上进行,网格上的每个格点都可以有棋子停留。对弈双方一方执红(red)棋、另一方执蓝(blue)棋,双方轮流执行操作,轮到一位玩家操作时,他必须选择一枚自己的棋子,并按照规则进行一步移动。

鸭棋发明者鸭子德规定一局鸭棋由红方执先手,并设计了初始棋盘布局如下

image

棋子类型与走子规则

棋子分为 \(7\) 类,下面介绍了它们的名字以及它们的移动规则。介绍移动规则时,我们默认棋子所处位置为 \(\left( x,y\right)\)(表示第 \(x\) 行的第 \(y\) 列,下同),并列出它可以到达的位置:

  • captain):可达的位置共 \(4\) 个,包括 \(\left(x\pm 1,y\right)\)\(\left(x,y\pm 1\right)\)
  • guard):可达的位置共 \(4\) 个,包括 \(\left(x\pm 1,y\pm 1\right)\)\(\left(x\pm 1,y\mp 1\right)\)
  • elephant):可达的位置至多 \(4\) 个,对于任意 \(s_x,s_y\in \left\{ 1,-1\right\}\),分别有:
    • 如果位置 \(\left(x+s_x\times 1 ,y+ s_y\times 1\right)\)无任意一方的棋子停留,则 \(\left( x+s_x \times 2,y+s_y \times 2\right)\) 为一个可达的位置。
  • horse):可达的位置至多 \(8\) 个,对于任意 \(s_x,s_y\in \left\{ 1,-1\right\}\),分别有:
    • 如果位置 \(\left(x+s_x\times 1 ,y\right)\)无任意一方的棋子停留,则 \(\left( x+s_x \times 2,y+s_y \times 1\right)\) 为一个可达的位置。
    • 如果位置 \(\left(x ,y+ s_y \times 1 \right)\)无任意一方的棋子停留,则 \(\left( x+s_x \times 1,y+s_y \times 2\right)\) 为一个可达的位置。
  • car):可在不跨越其他棋子的前提下,到达同行或同列的所有其他位置。
  • duck):可达的位置至多 \(8\) 个,对于任意 \(s_x,s_y\in \left\{ 1,-1\right\}\),分别有:
    • 如果位置 \(\left(x+s_x\times 2 ,y+s_y \times 1\right),\left(x+s_x\times 1 ,y\right)\) 上均无任意一方的棋子停留,则 \(\left( x+s_x \times 3,y+s_y \times 2\right)\) 为一个可达的位置。
    • 如果位置 \(\left(x+s_x \times 1 ,y+ s_y \times 2 \right),\left(x ,y+ s_y \times 1 \right)\) 上均无任意一方的棋子停留,则 \(\left( x+s_x \times 2,y+s_y \times 3\right)\) 为一个可达的位置。
  • soldier):可达的位置共 \(8\) 个,包括 \(\left(x\pm 1,y\right)\)\(\left(x,y\pm 1\right)\)\(\left(x\pm 1,y\pm 1\right)\)\(\left(x\pm 1,y\mp 1\right)\)

除上面描述的规则之外,棋子移动还有如下额外规则:

  • 不能将棋子移动到棋盘外的某个位置。
  • 玩家不能将棋子移动到已经停留了己方棋子的位置。
  • 如果玩家将棋子移动到了一个已经停留了对方棋子的位置,那么原本停留在该位置上的这个对方棋子将被移出游戏。

胜利条件与将军局面

玩家在这个游戏中的目标是将对方的移出游戏。一旦一方的被移出游戏,则另一方立即宣告胜利。

对于一个棋盘的状态,如果存在一方有一步合法的操作能够将另一方的移出游戏,则我们说当前局面是一个将军的局面。需要友情提示的是,根据定义,将军局面的形成包括(但不限于)如下这些可能:

  1. 一方将一枚棋子移动到可以攻击对方的位置

  2. 在己方受到威胁时不采取措施躲避

  3. 主动将移动至会受到攻击的位置

除此之外,需要特别说明的是,游戏结束后,由于双方不可再操作,因此不可能出现将军局面,即便此时另一方王处于被「攻击」的位置。

题目描述

今年的 IDCC(International Duck Chess Competition,国际鸭棋大赛)正在如火如荼地进行着。你观摩了一场精彩绝伦的比赛,但你对对弈过程的记忆已经模糊不清了,只有系统留下的他们的操作序列,序列中的每个操作为当前操作者试图移动某个位置的棋子至另一个位置。你希望用这个序列,来复现出整局棋局的对弈过程。即,对于每步操作,你需要首先判其是否合法,若合法,则进一步求出

  1. 这步操作移动了哪个棋子。
  2. 这步操作后,是否存在棋子被移出游戏,如有则还需求出被移出游戏的棋子。
  3. 这步操作后,是否形成将军局面。
  4. 这步操作后,游戏是否结束。

可能包含的不合法情况如下:

  • 此步移动的初始位置无己方棋子停留。
  • 此步移动的初始位置有己方棋子停留,但移动不符合规则。
  • 游戏已经结束。

序列中的不合法操作是需要被忽略的。比如,如果轮到红方移动,此时序列中的当前操作恰好是不合法的,则这个操作将被忽略,序列中的下一步操作将成为红方这步的操作(如仍不合法则继续忽略,直至出现合法的操作)。

输入格式

第一行一个非负整数 \(Q\),表示操作序列的长度。接下来依次描述每个操作。

接下来 \(Q\) 行,每行 \(4\) 个整数 \(x_s, y_s, x_t, y_t\)\(0\leq x_s,x_t < 10\)\(0\leq y_s,y_t < 9\)),描述一个欲将 \(\left(x_s,y_s\right)\) 处棋子移动到 \(\left(x_t,y_t\right)\) 的操作。在这里,我们规定左下角(即红方摆放的位置,图见「题目背景」)为 \(\left(0,0\right)\)

保证 \(Q\leq 1000\)

输出格式

输出 \(Q\) 行,对于每个操作依次输出复现结果。每行输出一个操作的结果:

  • 如果该操作为不合法操作,则请输出 Invalid command
  • 如果为合法操作,则依次回答「题目描述」中的问题 \(1\sim 4\)
    • 被移动的棋子用 [颜色] [类型](注意中间包含空格)来描述,请使用它们的英文名称(见「题目背景」)。如,红象为 red elephant,蓝王为 blue captain
    • 被移出游戏的棋子的描述方式与上面类似。特别地,如果无棋子被移出游戏,则该问题的答案为 NA
    • yesno 分别表示形成、不形成将军局面。
    • yesno 分别表示游戏结束、游戏不结束。
    • ;(分号)将所有问题的答案隔开。
    • 比如,四个问题的答案分别为:被移动的棋子是蓝车,无棋子被移出游戏,形成将军局面,游戏未结束。则该行应输出 blue car;NA;yes;no

版权信息

来自 THUPC(THU Programming Contest,清华大学程序设计竞赛)2019。

其他资源可在 Github 查看。

思路

大模拟。

棋子棋盘表示

棋子我们使用一个数字来表示:

棋子名 数字 棋子名 数字
红王 11 蓝王 21
红士 12 蓝士 22
红象 13 蓝象 23
红马 14 蓝马 24
红车 15 蓝车 25
红鸭 16 蓝鸭 26
红兵 17 蓝兵 27
(空) 0

这样子表示的优点如下:

  • 将十位数取出来就可以知道这个棋子是哪一方的。
  • 将个位数取出来就可以知道这个棋子是什么类型的(与哪一方的无关)。

然后需要写一个数字转英文棋子名的函数:

string get_name(int chs){
	// 获得棋子英文名
	string player,type;
	if(chs/10==1) player="red";
	if(chs/10==2) player="blue";
	if(chs%10 == 1) type="captain";
	if(chs%10 == 2) type="guard";
	if(chs%10 == 3) type="elephant";
	if(chs%10 == 4) type="horse";
	if(chs%10 == 5) type="car";
	if(chs%10 == 6) type="duck";
	if(chs%10 == 7) type="soldier";
	return player + " " + type;
}

棋盘简单的使用一个二维数组。不过要注意红方在上,黑方在下。

初始局面代码:

int board[10][9] = {
	{15,14,13,12,11,12,13,14,15},
	{00,00,00,00,00,00,00,00,00},
	{16,00,00,00,00,00,00,00,16},
	{17,00,17,00,17,00,17,00,17},
	{00,00,00,00,00,00,00,00,00},
	{00,00,00,00,00,00,00,00,00},
	{27,00,27,00,27,00,27,00,27},
	{26,00,00,00,00,00,00,00,26},
	{00,00,00,00,00,00,00,00,00},
	{25,24,23,22,21,22,23,24,25}
};// 棋盘

判断棋步(一)

现在我们来实现判断试图将一个坐标的棋子移到另一个坐标的行为是否合法。

首先我们先判断一个棋子是否可以落到棋盘上的某一个位置。这一点在鸭棋中可能用处不大,但在在中国象棋中尤为重要(象有不能过河、将士有不能出九宫的限制)。大致需要判断:

  • 是否出界
  • 落子的位置是否有己方的棋子。

代码:

int is_landing_legal(int chs,int dx,int dy){
	// 棋子 chs 是否可以落到 (dx,dy) 返回 0:不可以 1:可以 2:可以且吃了子
	if(dx<0||dx>=10||dy<0||dy>=9) return 0;// 出界
	else if(board[dx][dy]==0) return 1;// 没有棋子自然可以放上去
	else if(board[dx][dy]/10 != chs/10) return 2;// 有棋子但不同方可以吃
	else return 0;// 有己方棋子不能走
}

然后就是主判断了。无论怎么样,我们都可以写出一个整体框架:

int is_moving_legal(int chs,int sx,int sy,int dx,int dy){
	// 棋子 chs 从 (sx,sy) 走到 (dx,dy) 是否合法 0:不可以 1:可以 2:可以且吃了子
	if(sx==dx && sy==dy) return 0;
	if(chs==0) return 0;
	int landing = is_landing_legal(chs,dx,dy);
	if(!landing) return 0;
    这里写生成棋子走法,看是不是可以找到走法
    return 0;
}

判断棋步(二)

上一节我们实现了判断棋步是否合法的框架,这一节我们写具体判断逻辑。

  • captain):可达的位置共 \(4\) 个,包括 \(\left(x\pm 1,y\right)\)\(\left(x,y\pm 1\right)\)

由于王的移动比较简单,我们可以写一个偏移值数组 captain_delta

typedef vector<pair<int,int> > moves;

moves captain_delta = {{1,0},{0,1},{-1,0},{0,-1}};// 王的偏移值

然后遍历所有可能的偏移值,判断是否走的是这一步即可:

if(chs%10 == 1){// 王
	for(pair<int,int> delta : captain_delta){
		if(sx+delta.first==dx && sy+delta.second==dy){
			return landing;
		}
	}
	return 0;
}

  • guard):可达的位置共 \(4\) 个,包括 \(\left(x\pm 1,y\pm 1\right)\)\(\left(x\pm 1,y\mp 1\right)\)

由于士的移动也很简单,我们可以写一个偏移值数组 guard_delta

moves guard_delta = {{1,1},{1,-1},{-1,1},{-1,-1}};// 士的偏移值

然后遍历所有可能的偏移值,判断是否走的是这一步即可:

if(chs%10 == 2){// 士
	for(pair<int,int> delta : guard_delta){
		if(sx+delta.first==dx && sy+delta.second==dy){
			return landing;
		}
	}
	return 0;
}

  • elephant):可达的位置至多 \(4\) 个,对于任意 \(s_x,s_y\in \left\{ 1,-1\right\}\),分别有:
    • 如果位置 \(\left(x+s_x\times 1 ,y+ s_y\times 1\right)\)无任意一方的棋子停留,则 \(\left( x+s_x \times 2,y+s_y \times 2\right)\) 为一个可达的位置。

有人象也写了偏移值,不过这样又要写象眼的偏移值,非常麻烦,而且容易错。我们可以直接用题目给我们的定义,枚举 \(s_x,s_y\) 判断即可:

if(chs%10 == 3){// 象
	for(int _sx : {1,-1}){
		for(int _sy : {1,-1}){
			if(board[sx+_sx][sy+_sy]!=0) continue;
			if((sx+(_sx<<1))==dx && (sy+(_sy<<1))==dy){
				return landing;
			}
		}
	}
	return 0;
}

  • horse):可达的位置至多 \(8\) 个,对于任意 \(s_x,s_y\in \left\{ 1,-1\right\}\),分别有:
    • 如果位置 \(\left(x+s_x\times 1 ,y\right)\)无任意一方的棋子停留,则 \(\left( x+s_x \times 2,y+s_y \times 1\right)\) 为一个可达的位置。
    • 如果位置 \(\left(x ,y+ s_y \times 1 \right)\)无任意一方的棋子停留,则 \(\left( x+s_x \times 1,y+s_y \times 2\right)\) 为一个可达的位置。

和象一样,我们也可以直接枚举 \(s_x,s_y\)。然后判断即可。记得判断蹩马腿的情况。

if(chs%10 == 4){// 马
	for(int _sx : {1,-1}){
		for(int _sy : {1,-1}){
			if(board[sx+_sx][sy]==0){
				if((sx+(_sx<<1))==dx && (sy+_sy)==dy){
					return landing;
				}
			}
			if(board[sx][sy+_sy]==0){
				if((sx+_sx)==dx && (sy+(_sy<<1))==dy){
					return landing;
				}
			}
		}
	}
	return 0;
}

  • car):可在不跨越其他棋子的前提下,到达同行或同列的所有其他位置。

先判断是否在同一列或同一行,如果同一行,就固定行,看看这之间(记得不包括两边)有没有棋子。同一列同理。

if(chs%10 == 5){// 车
	bool flag=0;
	if(sx==dx){
		for(int i=min(sy,dy)+1;i<max(sy,dy);i++){
			if(board[sx][i]!=0){
				flag=1;
				break;
			}
		}
		if(!flag) return landing;
		else return 0;
	}
	else if(sy==dy){
		for(int i=min(sx,dx)+1;i<max(sx,dx);i++){
			if(board[i][sy]!=0) {
				flag=1;
				break;
			}
		}
		if(!flag) return landing;
		else return 0;
	}
	else return 0;
}

  • duck):可达的位置至多 \(8\) 个,对于任意 \(s_x,s_y\in \left\{ 1,-1\right\}\),分别有:
    • 如果位置 \(\left(x+s_x\times 2 ,y+s_y \times 1\right),\left(x+s_x\times 1 ,y\right)\) 上均无任意一方的棋子停留,则 \(\left( x+s_x \times 3,y+s_y \times 2\right)\) 为一个可达的位置。
    • 如果位置 \(\left(x+s_x \times 1 ,y+ s_y \times 2 \right),\left(x ,y+ s_y \times 1 \right)\) 上均无任意一方的棋子停留,则 \(\left( x+s_x \times 2,y+s_y \times 3\right)\) 为一个可达的位置。

使用象和马的方法即可,也是枚举 \(s_x,s_y\),然后记得判断鸭爪?

if(chs%10 == 6){// 鸭
	for(int _sx : {1,-1}){
		for(int _sy : {1,-1}){
			if(board[sx+_sx*2][sy+_sy*1]==0 && board[sx+_sx][sy] == 0){
				if((sx+(_sx*3))==dx && (sy+_sy*2)==dy){
					return landing;
				}
			}
			if(board[sx][sy+_sy]==0 && board[sx+_sx*1][sy+_sy*2] == 0){
				if((sx+_sx*2)==dx && (sy+(_sy*3))==dy){
					return landing;
				}
			}
		}
	}
	return 0;
}

  • soldier):可达的位置共 \(8\) 个,包括 \(\left(x\pm 1,y\right)\)\(\left(x,y\pm 1\right)\)\(\left(x\pm 1,y\pm 1\right)\)\(\left(x\pm 1,y\mp 1\right)\)

兵的走法比较简单,使用偏移值法。

moves soldier_delta = {{1,0},{-1,0},{0,1},{0,-1},{1,1},{-1,-1},{1,-1},{-1,1}};// 兵的偏移值


if(chs%10 == 7){// 兵
	for(pair<int,int> delta : soldier_delta){
		if(sx+delta.first==dx && sy+delta.second==dy){
			return landing;
		}
	}
	return 0;
}

判断将军

其实有更高效的方法,就是把王当成攻击子力,看能不能攻击到其他子。但这样判断 象眼/马腿/鸭爪 比较麻烦。

我们采用暴力的方法,先找到王的位置,然后暴力枚举所有棋子,看看走到王的位置是否合法。

bool is_checking(int player){
	// 是否将军 player=1 红方被将 player=2 蓝方被将
	int cx=-1,cy=-1;
	for(int i=0;i<10&&(cx==-1);i++){
		for(int j=0;j<9&&(cy==-1);j++){
			if(board[i][j]==(player*10+1)){
				cx=i;cy=j;
			}
		}
	}
	if(cx==-1&&cy==-1) return false;
	for(int i=0;i<10;i++){
		for(int j=0;j<9;j++){
			if(i==cx&&j==cy) continue;
			if(board[i][j]==0) continue;
			if(is_moving_legal(board[i][j],i,j,cx,cy)){
				return true;
			}
		}
	}
	return false;
}

判断结束

如果没有王,那么就结束了。可以暴力扫每一个位置看看是不是王,如果都不是就结束了。

bool is_finished(int player){
	// 是否结束 player=1 蓝方胜利 player=2 红方胜利
	int cx=-1,cy=-1;
	for(int i=0;i<10&&(cx==-1);i++){
		for(int j=0;j<9&&(cy==-1);j++){
			if(board[i][j]==(player*10+1)){
				cx=i;cy=j;
			}
		}
	}
	if(cx==-1&&cy==-1) return true;
	else return false;
}

主程序

然后就是主程序了!其实主程序十分简单,只是要注意:

  • 结束后不能走棋。
  • 不合法的棋要忽略。
  • 记得一方下完后一定是另一方下,否则不合法。

代码:

bool finished = 0;
int nowmove = 1;

signed main(){
	int q;
	cin>>q;
	while(q--){
		int sx,sy,dx,dy;
		cin>>sx>>sy>>dx>>dy;
		int moving = is_moving_legal(board[sx][sy],sx,sy,dx,dy);
		if(!moving||finished||board[sx][sy]/10 != nowmove){cout<<"Invalid command\n";continue;}
		string moved=get_name(board[sx][sy]),ate="NA",checking="no",finishing="no";
		if(moving == 2) ate=get_name(board[dx][dy]);
		board[dx][dy]=board[sx][sy];board[sx][sy]=0;
		if(is_checking(1) || is_checking(2)) checking="yes";
		if(is_finished(1) || is_finished(2)){finishing="yes";finished=1;}
		cout<<moved<<';'<<ate<<';'<<checking<<';'<<finishing<<'\n';
		if(nowmove == 1) nowmove = 2;
		else nowmove = 1;
	}
	return 0;
}

代码

显示代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

// 十位 0:空 1:红 2:蓝
// 个位 王:1 士:2 象:3 马:4 车:5 鸭:6 兵:7

int board[10][9] = {
	{15,14,13,12,11,12,13,14,15},
	{00,00,00,00,00,00,00,00,00},
	{16,00,00,00,00,00,00,00,16},
	{17,00,17,00,17,00,17,00,17},
	{00,00,00,00,00,00,00,00,00},
	{00,00,00,00,00,00,00,00,00},
	{27,00,27,00,27,00,27,00,27},
	{26,00,00,00,00,00,00,00,26},
	{00,00,00,00,00,00,00,00,00},
	{25,24,23,22,21,22,23,24,25}
};// 棋盘

typedef vector<pair<int,int> > moves;

moves captain_delta = {{1,0},{0,1},{-1,0},{0,-1}};// 王的偏移值
moves guard_delta = {{1,1},{1,-1},{-1,1},{-1,-1}};// 士的偏移值
moves soldier_delta = {{1,0},{-1,0},{0,1},{0,-1},{1,1},{-1,-1},{1,-1},{-1,1}};// 兵的偏移值

int is_landing_legal(int chs,int dx,int dy){
	// 棋子是否可以落到 (dx,dy) 返回 0:不可以 1:可以 2:可以且吃了子
	if(dx<0||dx>=10||dy<0||dy>=9) return 0;// 出界
	else if(board[dx][dy]==0) return 1;// 没有棋子自然可以放上去
	else if(board[dx][dy]/10 != chs/10) return 2;// 有棋子但不同方可以吃
	else return 0;// 有己方棋子不能走
}

int is_moving_legal(int chs,int sx,int sy,int dx,int dy){
	// 棋子 chs 从 (sx,sy) 走到 (dx,dy) 是否合法 0:不可以 1:可以 2:可以且吃了子
	if(sx==dx && sy==dy) return 0;
	if(chs==0) return 0;
	int landing = is_landing_legal(chs,dx,dy);
	if(!landing) return 0;
	if(chs%10 == 1){// 王
		for(pair<int,int> delta : captain_delta){
			if(sx+delta.first==dx && sy+delta.second==dy){
				return landing;
			}
		}
		return 0;
	}
	if(chs%10 == 2){// 士
		for(pair<int,int> delta : guard_delta){
			if(sx+delta.first==dx && sy+delta.second==dy){
				return landing;
			}
		}
		return 0;
	}
	if(chs%10 == 3){// 象
		for(int _sx : {1,-1}){
			for(int _sy : {1,-1}){
				if(board[sx+_sx][sy+_sy]!=0) continue;
				if((sx+(_sx<<1))==dx && (sy+(_sy<<1))==dy){
					return landing;
				}
			}
		}
		return 0;
	}
	if(chs%10 == 4){// 马
		for(int _sx : {1,-1}){
			for(int _sy : {1,-1}){
				if(board[sx+_sx][sy]==0){
					if((sx+(_sx<<1))==dx && (sy+_sy)==dy){
						return landing;
					}
				}
				if(board[sx][sy+_sy]==0){
					if((sx+_sx)==dx && (sy+(_sy<<1))==dy){
						return landing;
					}
				}
			}
		}
		return 0;
	}
	if(chs%10 == 5){// 车
		bool flag=0;
		if(sx==dx){
			for(int i=min(sy,dy)+1;i<max(sy,dy);i++){
				if(board[sx][i]!=0){
					flag=1;
					break;
				}
			}
			if(!flag) return landing;
			else return 0;
		}
		else if(sy==dy){
			for(int i=min(sx,dx)+1;i<max(sx,dx);i++){
				if(board[i][sy]!=0) {
					flag=1;
					break;
				}
			}
			if(!flag) return landing;
			else return 0;
		}
		else return 0;
	}
	if(chs%10 == 6){// 鸭
		for(int _sx : {1,-1}){
			for(int _sy : {1,-1}){
				if(board[sx+_sx*2][sy+_sy*1]==0 && board[sx+_sx][sy] == 0){
					if((sx+(_sx*3))==dx && (sy+_sy*2)==dy){
						return landing;
					}
				}
				if(board[sx][sy+_sy]==0 && board[sx+_sx*1][sy+_sy*2] == 0){
					if((sx+_sx*2)==dx && (sy+(_sy*3))==dy){
						return landing;
					}
				}
			}
		}
		return 0;
	}
	if(chs%10 == 7){// 兵
		for(pair<int,int> delta : soldier_delta){
			if(sx+delta.first==dx && sy+delta.second==dy){
				return landing;
			}
		}
		return 0;
	}
	return 0;
}

bool is_checking(int player){
	// 是否将军 player=1 红方被将 player=2 蓝方被将
	int cx=-1,cy=-1;
	for(int i=0;i<10&&(cx==-1);i++){
		for(int j=0;j<9&&(cy==-1);j++){
			if(board[i][j]==(player*10+1)){
				cx=i;cy=j;
			}
		}
	}
	if(cx==-1&&cy==-1) return false;
	for(int i=0;i<10;i++){
		for(int j=0;j<9;j++){
			if(i==cx&&j==cy) continue;
			if(board[i][j]==0) continue;
			if(is_moving_legal(board[i][j],i,j,cx,cy)){
				return true;
			}
		}
	}
	return false;
}

bool is_finished(int player){
	// 是否结束 player=1 蓝方胜利 player=2 红方胜利
	int cx=-1,cy=-1;
	for(int i=0;i<10&&(cx==-1);i++){
		for(int j=0;j<9&&(cy==-1);j++){
			if(board[i][j]==(player*10+1)){
				cx=i;cy=j;
			}
		}
	}
	if(cx==-1&&cy==-1) return true;
	else return false;
}

string get_name(int chs){
	// 获得棋子英文名
	string player,type;
	if(chs/10==1) player="red";
	if(chs/10==2) player="blue";
	if(chs%10 == 1) type="captain";
	if(chs%10 == 2) type="guard";
	if(chs%10 == 3) type="elephant";
	if(chs%10 == 4) type="horse";
	if(chs%10 == 5) type="car";
	if(chs%10 == 6) type="duck";
	if(chs%10 == 7) type="soldier";
	return player + " " + type;
}

bool finished = 0;
int nowmove = 1;

signed main(){
	int q;
	cin>>q;
	while(q--){
		int sx,sy,dx,dy;
		cin>>sx>>sy>>dx>>dy;
		int moving = is_moving_legal(board[sx][sy],sx,sy,dx,dy);
		if(!moving||finished||board[sx][sy]/10 != nowmove){cout<<"Invalid command\n";continue;}
		string moved=get_name(board[sx][sy]),ate="NA",checking="no",finishing="no";
		if(moving == 2) ate=get_name(board[dx][dy]);
		board[dx][dy]=board[sx][sy];board[sx][sy]=0;
		if(is_checking(1) || is_checking(2)) checking="yes";
		if(is_finished(1) || is_finished(2)){finishing="yes";finished=1;}
		cout<<moved<<';'<<ate<<';'<<checking<<';'<<finishing<<'\n';
		if(nowmove == 1) nowmove = 2;
		else nowmove = 1;
	}
	return 0;
}