#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>

using namespace std;

const int N=14;
int n,m,num[6];
int  a[N][N];
char c;
int flag;
int dirx[4]={0,1,0,-1},
    diry[4]={1,0,-1,0};
int vis[N][N];

struct Node{
    int x,y,dir,trun;
    Node(int x,int y,int dir,int trun):x(x),y(y),dir(dir),trun(trun){
    };
};

bool in(int x,int y){
    return x>=0&&x<n&&y>=0&&y<m;
}


bool cant(){
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(!in(i+1,j+1)) continue;
            if(!a[i][j]||!a[i][j+1]) continue;
            if(a[i][j]&&a[i+1][j]&&a[i][j]==a[i+1][j+1]&&a[i+1][j]==a[i][j+1]&&num[a[i][j]]==2&&num[a[i+1][j]]==2) return true;
        // 正方形对角线同&&相关的两个颜色块数目就剩正方形里的两个 
        }    
    }

    return false;
}

void bfs(int x,int y,int color,int sam[42][2],int &samn){
    queue<Node> q;
    memset(vis,0,sizeof(vis));

    Node f(x,y,-1,0);
    q.push(f);
    vis[x][y]=1;

    while(!q.empty()){
        f=q.front();
        q.pop();
        for(int i=0;i<4;i++){
            int nx=f.x+dirx[i],
                ny=f.y+diry[i],
                ntrun=f.trun;
            if(!in(nx,ny)||vis[nx][ny]||(a[nx][ny]!=color&&a[nx][ny]!=0)) continue;
            //next不在范围内|| 已经访问过||(next颜色与现循环颜色块不符&&next不是空块) 
            if(f.dir!=i&&f.dir!=-1)  ntrun+=+1;
            //方向不同&&不是第一次 
            if(ntrun>2) continue;
            //转弯超 
            vis[nx][ny]=1;
            if(a[nx][ny]==color){
                sam[samn][0]=nx;
                sam[samn][1]=ny;
                samn++;
                continue;
            }
            //sam记录相同块坐标 
            q.push(Node(nx,ny,i,ntrun));
        }
    }
}


void dfs(int cnt){
    if(flag) return;

    if(cnt==0){
        flag=1;return;
    }

    if(cant()) return;//cant必须放这里 原来放在main里这个是不对的可能出现:
//A***                 
//*AB*
//*BA*
//***A 
//但这里要排除:只剩下:
//***
//AB*
//BA*这个发生在过程中    
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(flag) return;

            if(!a[i][j]) continue;//空块下一个

            int sam[42][2];//记录相同块的坐标,转三个弯最多30 
            int samn=0,nowcolor=a[i][j];
            bfs(i,j,nowcolor,sam,samn);//第ij个,颜色,现在同色的个数 

            a[i][j]=0; 
            num[nowcolor]-=2;
            //与下方配合假设消掉 
            for(int k=0;k<samn;k++){
                int x=sam[k][0],
                    y=sam[k][1];

                a[x][y]=0;//假设消掉 

                dfs(cnt-2);//假设消掉 
                a[x][y]=nowcolor;//还原现场 
            }
            a[i][j]=nowcolor;//还原现场 
            num[nowcolor]+=2;

        }    
    }
}


int main(int argc, char** argv) {
    int cnt;
    while(scanf("%d%d",&n,&m)==2&&n+m){
        flag=0;//是否可以消光 
        cnt=0;//目前剩下的还要消的颜色块 
        memset(num,0,sizeof(num));//num记录每种块的数量包括空块 
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                cin>>c;
                if(c=='*') num[0]++,a[i][j]=0;//空块不要消 
                else if(c=='A') num[1]++,a[i][j]=1,cnt++;
                else if(c=='B') num[2]++,a[i][j]=2,cnt++;
                else if(c=='C') num[3]++,a[i][j]=3,cnt++;
                else if(c=='D') num[4]++,a[i][j]=4,cnt++;
            }    
        }


        if(num[1]&1||num[2]&1||num[3]&1||num[4]&1){//颜色块否单一定消不掉 
            printf("no\n"); continue; 
        }

        dfs(cnt);
        if(flag) printf("yes\n");
        else  printf("no\n");
    }
    return 0;
}