#include<bits/stdc++.h>
using namespace std;
using ll=long long;
using PII=pair<ll,ll>;
// 定义8个方向的行/列偏移量(覆盖右、左、下、右下、左下、上、右上、左上)
// dx对应行偏移,dy对应列偏移,一一配对构成8个方向
ll dx[]={0,0,1,1,1,-1,-1,-1};
ll dy[]={1,-1,0,1,-1,0,1,-1};
char ans[4][4]; // 最终结果数组:存储4x4网格中每个格子的确定状态(数字/X/O/.)
// X=确定是雷,O=确定不是雷,.=无法确定,数字=原始数字格子
int main(){
// 关闭输入输出同步,加速cin/cout(优化效率,不影响核心逻辑)
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
vector<string>a(4); // 存储输入的4x4扫雷网格(0-based索引,行/列均为0-3)
// q:存储所有数字格子的约束信息
// 每个元素是unordered_map<ll, unordered_set<ll>>,格式为{数字值: 周围空白格的一维位置集合}
// 例如:数字3对应周围3个雷,则map中key=3,value是这3个雷所在的空白格位置集合
vector<unordered_map<ll,unordered_set<ll>>>q;
unordered_set<ll>s; // 存储所有需要判断的空白格的一维位置(范围0-15,对应4x4网格的16个格子)
// 输入4行扫雷网格数据
cin>>a[0]>>a[1]>>a[2]>>a[3];
// ====================== 步骤1:收集数字格子的约束 ======================
// 遍历4x4网格的每个格子(行i:0-3,列j:0-3,均为0-based)
for(ll i=0;i<4;i++){
for(ll j=0;j<4;j++){
if(a[i][j]!='.'){ // 找到非空白格(即数字格子)
// 存储当前数字格子的约束:key=数字值,value=周围空白格的一维位置集合
unordered_map<ll,unordered_set<ll>>m;
// 遍历8个方向,查找当前数字格子周围的空白格
for(ll k=0;k<8;k++){
// 计算偏移后的行xt、列yt(均为0-based)
ll xt=i+dx[k],yt=j+dy[k];
// 过滤无效位置:1.坐标超出4x4网格范围 2.偏移后位置不是空白格
if(xt<0||xt>3||yt<0||yt>3||a[xt][yt]!='.')continue;
// 计算空白格的一维位置(行号*4 + 列号,范围0-15)
ll pos = xt*4 + yt;
// 记录约束:当前数字值 对应 该空白格位置
m[a[i][j]-'0'].insert(pos);
// 标记该空白格为需要判断的格子
s.insert(pos);
}
// 将当前数字格子的约束加入总约束列表
q.emplace_back(m);
}
}
}
// ====================== 步骤2:枚举所有可能的雷分布 ======================
// 用16位二进制掩码表示雷分布:每一位对应一个格子(0-15),1=该格子是雷,0=不是雷
// 枚举范围0 ~ (1<<16)-1,覆盖16个格子的所有雷/非雷组合(共65536种可能)
for(ll i=0;i<=(1<<16)-1;i++){
ll flag=0; // 标记当前掩码是否合法(0=合法,1=不合法)
// ====================== 步骤3:验证掩码合法性 ======================
// 遍历所有数字格子的约束,检查当前掩码是否满足所有约束
for(auto j:q){ // j:单个数字格子的约束(map<数字值, 空白格集合>)
for(auto [k,v]:j){ // k=数字值(需要的雷数),v=周围空白格的一维位置集合
ll cnt=0; // 统计当前掩码下,该数字周围的雷的数量
// 遍历该数字周围的所有空白格,统计雷数
for(auto t:v){ // t:空白格的一维位置
if((i>>t)&1){ // 掩码的第t位为1 → 该位置是雷,计数+1
cnt++;
}
}
// 若统计的雷数≠数字值 → 掩码不合法,标记后跳出循环
if(cnt!=k){
flag=1;
break;
}
}
if(flag==1)break; // 只要有一个约束不满足,无需继续检查,跳过当前掩码
}
// ====================== 步骤4:更新合法掩码对应的结果 ======================
// 若当前掩码合法(满足所有数字约束),统计该掩码下空白格的状态
if(flag==0){
// 遍历16个格子(一维位置j:0-15)
for(ll j=0;j<16;j++){
// 将一维位置转换为二维坐标(行it:0-3,列jt:0-3)
ll it=j/4,jt=j%4;
// 情况1:原格子是数字 → 直接保留到结果数组
if(a[it][jt]!='.'){
ans[it][jt] = a[it][jt];
}
// 情况2:原格子是需要判断的空白格(在集合s中)
else if(s.find(j)!=s.end()){
if((i>>j)&1){ // 当前掩码下该位置是雷(X)
// 逻辑:若结果未标记为O/初始态 → 标记为X;否则标记为.(状态冲突,无法确定)
if(ans[it][jt]!='O' && ans[it][jt]!='.'){
ans[it][jt] = 'X';
} else {
ans[it][jt] = '.';
}
} else { // 当前掩码下该位置不是雷(O)
// 逻辑:若结果未标记为X/初始态 → 标记为O;否则标记为.(状态冲突,无法确定)
if(ans[it][jt]!='X' && ans[it][jt]!='.'){
ans[it][jt] = 'O';
} else {
ans[it][jt] = '.';
}
}
}
// 情况3:无需判断的空白格 → 直接标记为.
else {
ans[it][jt] = '.';
}
}
}
}
// ====================== 步骤5:输出最终结果 ======================
// 按4x4格式输出结果数组,每行4个字符
for(ll i=0;i<4;i++){
for(ll j=0;j<4;j++){
cout<<ans[i][j];
}
cout<<'\n'; // 每行结束换行
}
return 0;
}