#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <cfloat>
#include <stack>
#include <queue>
#include <vector>
#include <string>
#include <iostream>
#include <set>
#include <map>
#include <time.h>
typedef long long int ll;
typedef unsigned long long int ull;
#define BIG_NUM 2000000000
#define MOD 1000000007
#define EPS 0.000000001
using namespace std;

#define NUM 20000
#define HEAD MOD
#define FOOT MOD+1

typedef pair<int,int> Point;

enum Type{
	TATE,
	YOKO,
};

struct Info{
	int row,col;
	Type dir;
};

struct Data{
	Data(int arg_row,int arg_col,int arg_index,int arg_which){
		row = arg_row;
		col = arg_col;
		index = arg_index;
		which = arg_which;
	}

	int row,col,index,which;
};


map<Point,int> MAP; //<座標、布団インデックス>
Info info[NUM];
vector<int> Group[NUM]; //隣接している布団のグループ

int near_row[4] = {1,0,0,-1},near_col[4] = {0,-1,1,0};
int diff_row[2][6] = {{2,1,1,0,0,-1},{-1,-1,0,0,1,1}},diff_col[2][6] = {{0,-1,1,-1,1,0},{0,1,-1,2,0,1}};

int N;
int boss[NUM],height[NUM];

//自分のボスのindexを取得しつつ、経路圧縮を行う関数
int get_boss(int id){
	if(boss[id] == id)return id; //自分が代表なら、自分の値を返す
	else{
		return boss[id] = get_boss(boss[id]); //代表でないなら、自分が所属する組織の代表を返しつつ、経路圧縮
	}
}

void unite(int x,int y){
	int boss_x = get_boss(x);
	int boss_y = get_boss(y);

	//既に同じグループなら何もしない
	if(boss_x == boss_y)return;

	//高さが高い方に吸収する
	if(height[x] > height[y]){

		boss[boss_y] = boss_x;

	}else if(height[x] < height[y]){

		boss[boss_x] = boss_y;

	}else{ //height[x] == height[y]

		boss[boss_y] = boss_x;
		height[x]++;
	}
}

void init(){
	for(int i = 0; i < N; i++){
		boss[i] = i;
		height[i] = 0;
	}
}

bool check(int group_id){

	map<Point,int> ROOM;
	queue<Data> Q;

	//ボスの、左下側を頭にする(ボスのid == group_id)
	ROOM[Point(info[group_id].row,info[group_id].col)] = HEAD;
	//printf("(%d,%d)に%dの頭を配置\n",info[group_id].row,info[group_id].col,group_id);
	Q.push(Data(info[group_id].row,info[group_id].col,group_id,HEAD));

	bool FLG = true;

	int adj_row,adj_col,adj_index;

	while(!Q.empty()){

		//printf("index:%d row:%d col:%d type:%d\n",Q.front().index,Q.front().row,Q.front().col,Q.front().which);

		//隣接4近傍を検索
		for(int i = 0; i < 4; i++){
			adj_row = Q.front().row+near_row[i];
			adj_col = Q.front().col+near_col[i];

			auto at = MAP.find(Point(adj_row,adj_col));
			if(at == MAP.end())continue; //隣接する布団がなければSKIP

			adj_index = MAP[Point(adj_row,adj_col)];
			//printf("adj_row:%d,adj_col:%d adj_index:%d\n",adj_row,adj_col,adj_index);

			if(adj_index != Q.front().index){ //自分以外の布団が隣接

				at = ROOM.find(Point(adj_row,adj_col));

				if(at == ROOM.end()){ //まだ割り当てがされていない
					if(Q.front().which == HEAD){
						ROOM[Point(adj_row,adj_col)] = HEAD; //頭と足は隣接してはいけない
						Q.push(Data(adj_row,adj_col,adj_index,HEAD));
						//printf("(%d,%d)に%dの頭を配置\n",adj_row,adj_col,adj_index);
					}else{
						ROOM[Point(adj_row,adj_col)] = FOOT;
						Q.push(Data(adj_row,adj_col,adj_index,FOOT));
						//printf("(%d,%d)に%dの足を配置\n",adj_row,adj_col,adj_index);
					}
				}else{ //割り当て済
					if((ROOM[Point(adj_row,adj_col)] == FOOT && Q.front().which == HEAD) ||
							(ROOM[Point(adj_row,adj_col)] == HEAD && Q.front().which == FOOT)){ //矛盾発生
						//printf("異種隣接\n");
						FLG = false;
						break;
					}
				}

			}else{ //自分の布団が隣接
				//自分のもう一方に、不適な割り当てがされていないか調べる
				at = ROOM.find(Point(adj_row,adj_col));

				if(at == ROOM.end()){ //まだ割り当てがされていない
					if(Q.front().which == HEAD){
						ROOM[Point(adj_row,adj_col)] = FOOT;
						Q.push(Data(adj_row,adj_col,Q.front().index,FOOT));
						//printf("(%d,%d)に%dの頭を配置\n",adj_row,adj_col,Q.front().index);
					}else{
						ROOM[Point(adj_row,adj_col)] = HEAD;
						Q.push(Data(adj_row,adj_col,Q.front().index,HEAD));
						//printf("(%d,%d)に%dの足を配置\n",adj_row,adj_col,Q.front().index);
					}
				}else{ //割り当て済
					if(ROOM[Point(adj_row,adj_col)] == Q.front().which){ //矛盾発生
						//printf("同じパーツが重複\n");
						FLG = false;
						break;
					}
				}
			}
		}

		if(!FLG)break;

		Q.pop();
	}

	if(FLG)return true;


	FLG = true;
	ROOM.clear();

	//ボスの、左下側を足にする
	ROOM[Point(info[group_id].row,info[group_id].col)] = FOOT;
	Q.push(Data(info[group_id].row,info[group_id].col,group_id,FOOT));
	//printf("(%d,%d)に%dの足を配置\n",info[group_id].row,info[group_id].col,group_id);

	while(!Q.empty()){

		//printf("index:%d row:%d col:%d type:%d\n",Q.front().index,Q.front().row,Q.front().col,Q.front().which);

		//隣接4近傍を検索
		for(int i = 0; i < 4; i++){
			adj_row = Q.front().row+near_row[i];
			adj_col = Q.front().col+near_col[i];

			auto at = MAP.find(Point(adj_row,adj_col));
			if(at == MAP.end())continue; //隣接する布団がなければSKIP

			adj_index = MAP[Point(adj_row,adj_col)];
			//printf("adj_row:%d,adj_col:%d adj_index:%d\n",adj_row,adj_col,adj_index);

			if(adj_index != Q.front().index){ //自分以外の布団が隣接

				at = ROOM.find(Point(adj_row,adj_col));

				if(at == ROOM.end()){ //まだ割り当てがされていない
					if(Q.front().which == HEAD){
						ROOM[Point(adj_row,adj_col)] = HEAD; //頭と足は隣接してはいけない
						Q.push(Data(adj_row,adj_col,adj_index,HEAD));
						//printf("(%d,%d)に%dの頭を配置\n",adj_row,adj_col,adj_index);
					}else{
						ROOM[Point(adj_row,adj_col)] = FOOT;
						Q.push(Data(adj_row,adj_col,adj_index,FOOT));
						//printf("(%d,%d)に%dの足を配置\n",adj_row,adj_col,adj_index);
					}
				}else{ //割り当て済
					if((ROOM[Point(adj_row,adj_col)] == FOOT && Q.front().which == HEAD) ||
							(ROOM[Point(adj_row,adj_col)] == HEAD && Q.front().which == FOOT)){ //矛盾発生
						//printf("異種隣接\n");
						FLG = false;
						break;
					}
				}

			}else{ //自分の布団が隣接
				//自分のもう一方に、不適な割り当てがされていないか調べる
				at = ROOM.find(Point(adj_row,adj_col));

				if(at == ROOM.end()){ //まだ割り当てがされていない
					if(Q.front().which == HEAD){
						ROOM[Point(adj_row,adj_col)] = FOOT;
						Q.push(Data(adj_row,adj_col,Q.front().index,FOOT));
						//printf("(%d,%d)に%dの頭を配置\n",adj_row,adj_col,Q.front().index);
					}else{
						ROOM[Point(adj_row,adj_col)] = HEAD;
						Q.push(Data(adj_row,adj_col,Q.front().index,HEAD));
						//printf("(%d,%d)に%dの足を配置\n",adj_row,adj_col,Q.front().index);
					}
				}else{ //割り当て済
					if(ROOM[Point(adj_row,adj_col)] == Q.front().which){ //矛盾発生
						//printf("同じパーツが重複\n");
						FLG = false;
						break;
					}
				}
			}
		}

		if(!FLG)break;

		Q.pop();
	}


	return FLG;
}

void func(){

	MAP.clear();
	for(int i = 0; i < N; i++)Group[i].clear();

	init();

	char buf[2];

	for(int i = 0; i < N; i++){
		scanf("%d %d %s",&info[i].col,&info[i].row,buf);
		if(buf[0] == 'x'){
			info[i].dir = YOKO;
		}else{
			info[i].dir = TATE;
		}
		MAP[Point(info[i].row,info[i].col)] = i; //マップに登録(2マス分)
		if(info[i].dir == YOKO){
			MAP[Point(info[i].row,info[i].col+1)] = i;
		}else{
			MAP[Point(info[i].row+1,info[i].col)] = i;
		}
	}

	//隣接している布団のグループを作る
	int adj_row,adj_col;
	for(int i = 0; i < N; i++){
		for(int k = 0; k < 6; k++){
			adj_row = info[i].row+diff_row[info[i].dir][k];
			adj_col = info[i].col+diff_col[info[i].dir][k];

			auto at = MAP.find(Point(adj_row,adj_col));
			if(at == MAP.end())continue;

			unite(i,MAP[Point(adj_row,adj_col)]); //隣接している布団を同じグループにする
		}
	}

	vector<int> BOSS;
	for(int i = 0; i < N; i++){ //布団をグループに振り分ける
		Group[get_boss(i)].push_back(i);
		if(boss[i] == i){
			BOSS.push_back(i); //ボスを収集する
		}
	}

	for(int i = 0; i < BOSS.size(); i++){
		if(check(BOSS[i]) == false){
			printf("No\n");
			return;
		}
	}

	printf("Yes\n");
}

int main(){

	while(true){
		scanf("%d",&N);
		if(N == 0)break;

		func();
	}

	return 0;
}



Measured memory_bytes: 9012