#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <malloc.h> struct rcd;//声明节点结构
typedef struct rcd* Record;//节点指针别名
typedef struct rcd record;//节点别名
#define MAXIMUS 15 //定义棋盘大小 int p[MAXIMUS][MAXIMUS];//存储对局信息
char buff[MAXIMUS*+][MAXIMUS*+];//输出缓冲器
int Cx,Cy;//当前光标位置
int Now;//当前走子的玩家,1代表黑,2代表白
int wl,wp;//当前写入缓冲器的列数和行数位置
char* showText;//在棋盘中央显示的文字信息
int count;//回合数
int Putable;//指示当前是否可以走棋
int Exiting;//1为当场上无子并按ESC时询问是否退出程序的状态,2为非此状态
int ExiRep;//1为当回放到最后一回合并按向后时询问是否退出回放的状态,2为非此状态
Record RecBeg,RecNow;//记录的开始节点和当前节点 struct rcd//记录节点结构,双链表形式
{
int X;//此记录走棋的X坐标
int Y;//此记录走棋的Y坐标
Record Next;//前一个记录
Record Back;//后一个记录
}; Record newRecord()//记录节点构造函数
{
Record r=(Record)malloc(sizeof(record));//申请一个节点对象
r->Next=NULL;//给予前后节点初值NULL
r->Back=NULL;
return r;
} void Exit()//检查退出程序
{
int input;
if(Exiting)//如果是第二次按下ESC
{
exit();
}
else//如果是第一次按下ESC则询问是否退出程序
{
showText="是否退出?再次按下ESC退出,其他键返回";
Exiting=;//指示已经按下过ESC
}
} void ExitRep()//检查退出回放
{
int input;
if(ExiRep)//如果是第二次后移
{
ExiRep=;
}
else//如果是第一次后移则询问是否退出回放
{
showText="是否退出?再次后移退出回放,其他键返回";
ExiRep=;//指示已经按下过后移
}
} void AddRecord()//添加记录
{
RecNow->X=Cx;//记录坐标
RecNow->Y=Cy;
RecNow->Next=newRecord();//创建下一个记录节点
RecNow->Next->Back=RecNow;//完成双链表
RecNow=RecNow->Next;//当前记录推至下一个记录节点
} int DelRecord()//删除当前记录节点,1为删除成功,0为删除失败
{
Record b;//上一个节点
if(RecNow->Back!=NULL)//越界检查
{
b=RecNow->Back;//缓存上一个节点
free(RecNow);//释放当前节点
RecNow=b;//当前记录回至上一个记录节点
return ;
}
else
{
return ;//没有节点可删除时
}
} void CleanRecord()//清理所有记录
{
Record n;//下一个节点
while(RecBeg->Next!=NULL)//删除所有记录,直到越界前为止
{
n=RecBeg->Next;//记下下一个节点
free(RecBeg);//释放当前节点
RecBeg=n;//当前记录推至下一个记录节点
}
} char* Copy(char* strDest,const char* strSrc)//修改过的字符串复制函数,会忽略末端的\0
{
char* strDestCopy = strDest;
while (*strSrc!='\0')
{
*strDest++=*strSrc++;
}
return strDestCopy;
} void Initialize()//初始化一个对局函数
{
int i,j;//循环变量
system("title 对局中(按方向键控制光标,空格走子),Esc撤销");
showText="";//重置显示信息
count=;//回合数归零
RecNow=RecBeg=newRecord();
Exiting=;
for(i=;i<MAXIMUS;i++)//重置对局数据
{
for(j=;j<MAXIMUS;j++)
{
p[i][j]=;
}
}
Cx=Cy=MAXIMUS/;//重置光标到中央
Now=;//重置当前为黑方
} char* getStyle(int i,int j)//获得棋盘中指定坐标交点位置的字符,通过制表符拼成棋盘
{
if(p[i][j]==)//1为黑子
return "●";
else if(p[i][j]==)//2为白子
return "○";
else if(i==&&j==)//以下为边缘棋盘样式
return "┏";
else if(i==MAXIMUS-&&j==)
return "┓";
else if(i==MAXIMUS-&&j==MAXIMUS-)
return "┛";
else if(i==&&j==MAXIMUS-)
return "┗";
else if(i==)
return "┠";
else if(i==MAXIMUS-)
return "┨";
else if(j==)
return "┯";
else if(j==MAXIMUS-)
return "┷";
return "┼";//中间的空位
} char* getCurse(int i,int j){//获得指定坐标交点位置左上格的样式,通过制表符来模拟光标的显示
if(Putable)//可走棋时光标为粗线
{
if(i==Cx){
if(j==Cy)
return "┏";
else if (j==Cy+)
return "┗";
}
else if(i==Cx+)
{
if(j==Cy)
return "┓";
else if (j==Cy+)
return "┛";
}
}
else//不可走棋时光标为虚线
{
if(i==Cx){
if(j==Cy)
return "┌";
else if (j==Cy+)
return "└";
}
else if(i==Cx+)
{
if(j==Cy)
return "┐";
else if (j==Cy+)
return "┘";
}
}
return " ";//如果不在光标附近则为空
} void write(char* c)//向缓冲器写入字符串
{
Copy(buff[wl]+wp,c);
wp+=strlen(c);
} void ln()//缓冲器写入位置提行
{
wl+=;
wp=;
} void Display()//将缓冲器内容输出到屏幕
{
int i,l=strlen(showText);//循环变量,中间文字信息的长度
int Offset=MAXIMUS*+-l/;//算出中间文字信息居中显示所在的横坐标位置
if(Offset%==)//如果位置为奇数,则移动到偶数,避免混乱
{
Offset--;
}
Copy(buff[MAXIMUS]+Offset,showText);//讲中间文字信息复制到缓冲器
if(l%==)//如果中间文字长度为半角奇数,则补上空格,避免混乱
{
*(buff[MAXIMUS]+Offset+l)=0x20;
}
system("cls");//清理屏幕,准备写入
for(i=;i<MAXIMUS*+;i++){//循环写入每一行
printf("%s",buff[i]);
if(i<MAXIMUS*)//写入完每一行需要换行
printf("\n");
}
} void Print()//将整个棋盘算出并储存到缓冲器,然后调用Display函数显示出来
{
int i,j;//循环变量
wl=;
wp=;
for(j=;j<=MAXIMUS;j++)//写入出交点左上角的字符,因为需要打印棋盘右下角,所以很以横纵各多一次循环
{
for(i=;i<=MAXIMUS;i++)
{
write(getCurse(i,j));//写入左上角字符
if(j==||j==MAXIMUS)//如果是棋上下盘边缘则没有连接的竖线,用空格填充位置
{
if(i!=MAXIMUS)
write(" ");
}
else//如果在棋盘中间则用竖线承接上下
{
if(i==||i==MAXIMUS-)//左右边缘的竖线更粗
write("┃");
else if(i!=MAXIMUS)//中间的竖线
write("│");
}
}
if(j==MAXIMUS)//如果是最后一次循环,则只需要处理边侧字符,交点要少一排
{
break;
}
ln();//提行开始打印交点内容
write(" ");//用空位补齐位置
for(i=;i<MAXIMUS;i++)//按横坐标循环正常的次数
{
write(getStyle(i,j));//写入交点字符
if(i!=MAXIMUS-)//如果不在最右侧则补充一个横线承接左右
{
if(j==||j==MAXIMUS-)
{
write("━");//上下边缘的横线更粗
}
else
{
write("─");//中间的横线
}
}
}
ln();//写完一行后提行
}
Display();//将缓冲器内容输出到屏幕
} int Put(){//在当前光标位置走子,如果非空,则返回0表示失败
if(Putable)
{
p[Cx][Cy]=Now;//改变该位置数据
AddRecord();
return ;//返回1表示成功
}
else
{
return ;
}
} int Check()//胜负检查,即判断当前走子位置有没有造成五连珠的情况
{
int w=,x=,y=,z=,i;//累计横竖正斜反邪四个方向的连续相同棋子数目
for(i=;i<;i++)if(Cy+i<MAXIMUS&&p[Cx][Cy+i]==Now)w++;else break;//向下检查
for(i=;i<;i++)if(Cy-i>&&p[Cx][Cy-i]==Now)w++;else break;//向上检查
if(w>=)return Now;//若果达到5个则判断当前走子玩家为赢家
for(i=;i<;i++)if(Cx+i<MAXIMUS&&p[Cx+i][Cy]==Now)x++;else break;//向右检查
for(i=;i<;i++)if(Cx-i>&&p[Cx-i][Cy]==Now)x++;else break;//向左检查
if(x>=)return Now;//若果达到5个则判断当前走子玩家为赢家
for(i=;i<;i++)if(Cx+i<MAXIMUS&&Cy+i<MAXIMUS&&p[Cx+i][Cy+i]==Now)y++;else break;//向右下检查
for(i=;i<;i++)if(Cx-i>&&Cy-i>&&p[Cx-i][Cy-i]==Now)y++;else break;//向左上检查
if(y>=)return Now;//若果达到5个则判断当前走子玩家为赢家
for(i=;i<;i++)if(Cx+i<MAXIMUS&&Cy-i>&&p[Cx+i][Cy-i]==Now)z++;else break;//向右上检查
for(i=;i<;i++)if(Cx-i>&&Cy+i<MAXIMUS&&p[Cx-i][Cy+i]==Now)z++;else break;//向左下检查
if(z>=)return Now;//若果达到5个则判断当前走子玩家为赢家
return ;//若没有检查到五连珠,则返回0表示还没有玩家达成胜利
}
void ReplayMode(){
int i,j;//循环变量
system("title 回放中(按左键后退,右键或空格前进),Esc退出");
showText="";//重置显示信息
count=;//回合数归零
Putable=;//不可走棋状态
RecBeg->Back=newRecord();
RecBeg->Back->Next=RecBeg;
RecBeg=RecBeg->Back;
for(i=;i<MAXIMUS;i++)//重置对局数据
{
for(j=;j<MAXIMUS;j++)
{
p[i][j]=;
}
}
Now=;//重置当前为黑方
} void RepForward()//回放模式前进
{
if(RecNow->Next->Next!=NULL)//越界检查
{
RecNow=RecNow->Next;//当前节点推至下一个记录节点
p[RecNow->X][RecNow->Y]=Now;//按照记录还原一个回合
Cx=RecNow->X;//设置光标位置
Cy=RecNow->Y;
Now=-Now;//转换当前的黑白方
}
else//若已达到最后则询问退出
{
ExitRep();
}
} void RepBackward()//回放模式后退
{
if(RecNow->Back!=NULL)//越界检查
{
p[RecNow->X][RecNow->Y]=;//按照记录撤销一个回合
if(RecNow->Back->Back==NULL)//在整个棋盘没有棋子时隐藏光标
{
Cx=-;
Cy=-;
}
else if(RecNow->Back==NULL)//在只有一个棋子时移动光标到这个棋子的位置
{
Cx=RecNow->X;
Cy=RecNow->Y;
}
else//正常情况下移动光标到上一回合的位置
{
Cx=RecNow->Back->X;
Cy=RecNow->Back->Y;
}
RecNow=RecNow->Back;//当前节点后退至上一个记录节点
Now=-Now;//转换当前的黑白方 }
} void ShowReplay()
{
int input;//输入变量
ReplayMode();//初始化回放模式
RecNow=RecBeg;//当前观察从头开始
RepForward();//显示第一次走棋
while()//开始无限回合的死循环,直到Esc退出
{
if(ExiRep==)
{
ExiRep=;
break;
}
Print();//打印棋盘
input=getch();//等待键盘按下一个字符
if(input==)//如果是ESC则退出回放
{
return;
}
else if(input==0x20)//如果是空格则前进
{
RepForward();
continue;
}
else if(input==0xE0)//如果按下的是方向键,会填充两次输入,第一次为0xE0表示按下的是控制键
{
input=getch();//获得第二次输入信息
switch(input)//判断方向键方向并移动光标位置
{
case 0x4B:
RepBackward();//向左后退
break;
case 0x4D:
RepForward();//向右前进
continue;
}
}
ExiRep=;//未再次按后移则不准备退出
showText="";
}
} void Regret()//悔棋撤销,如果棋盘上没有子即为退出
{
if(DelRecord()){//尝试删除当前节点,如果有节点可以删除则
p[RecNow->X][RecNow->Y]=;//撤除当前回合
if(RecNow->Back==NULL)//如果删除的是第一颗子则将光标移动到第一颗子原来的位置上
{
Cx=RecNow->X;
Cy=RecNow->Y;
}
else//否则将光标移动到上一颗子上
{
Cx=RecNow->Back->X;
Cy=RecNow->Back->Y;
}
Now=-Now;//反转当前黑白方
}
else
{
Exit();//如果没有棋子可以撤销,则询问退出
}
} int RunGame()//进行整个对局,返回赢家信息(虽然有用上)
{
int input;//输入变量
int victor;//赢家信息
Initialize();//初始化对局
while(){//开始无限回合的死循环,直到出现胜利跳出
Putable=p[Cx][Cy]==;
Print();//打印棋盘
input=getch();//等待键盘按下一个字符
if(input==)//如果是ESC则悔棋或退出
{
Regret();
Print();
continue;
}
else if(input==0x20)//如果是空格则开始走子
{
if(Put())//如果走子成功则判断胜负
{
victor=Check();
Now=-Now;//轮换当前走子玩家
count++;
if(victor==)//如果黑方达到胜利,显示提示文字并等待一次按键,返回胜利信息
{
showText="黑方胜利!按R查看回放,按其他键重新开局";
Print();
input=getch();
if(input==0xE0)
{
getch();
}
else if(input=='R'||input=='r')
{
ShowReplay();
}
return Now;
}
else if(victor==)//如果白方达到胜利,显示提示文字并等待一次按键,返回胜利信息
{
showText="白方胜利!按R查看回放,按其他键重新开局";
Print();
input=getch();
if(input==0xE0)
{
getch();
}
else if(input=='R'||input=='r')
{
ShowReplay();
}
return Now;
}else if(count==MAXIMUS*MAXIMUS)//如果回合数达到了棋盘总量,即棋盘充满,即为平局
{
showText="平局!按R查看回放,按其他键重新开局";
Print();
input=getch();
if(input==0xE0)
{
getch();
}
else if(input=='R'||input=='r')
{
ShowReplay();
}
CleanRecord();
return ;
}
}
}
else if(input==0xE0)//如果按下的是方向键,会填充两次输入,第一次为0xE0表示按下的是控制键
{
input=getch();//获得第二次输入信息
switch(input)//判断方向键方向并移动光标位置
{
case 0x4B://
Cx--;
break;
case 0x48:
Cy--;
break;
case 0x4D:
Cx++;
break;
case 0x50:
Cy++;
break;
}
if(Cx<)Cx=MAXIMUS-;//如果光标位置越界则移动到对侧
if(Cy<)Cy=MAXIMUS-;
if(Cx>MAXIMUS-)Cx=;
if(Cy>MAXIMUS-)Cy=;
}
Exiting=;//未再次按下ESC则不准备退出
showText="";
}
} int main()//主函数
{
system("mode con cols=63 lines=32");//设置窗口大小
system("color E0");//设置颜色
while(){//循环执行游戏
RunGame();
}
}

最新文章

  1. [IOS] &#39;Double&#39; is not convertible to &#39;CGFloat&#39;
  2. IOS RunLoop面试题
  3. Android开发中遇到的小问题 一
  4. 《oracle每天一练》触发器不能调用或间接调用COMMIT,ROLLBACK等DCL语句
  5. 【分享】深入浅出WPF全系列教程及源代码
  6. javascript 传递引用类型参数
  7. Hibernate中Entity实体类的写法
  8. R语言学习笔记:因子(Factors)
  9. Linux下对字符串进行MD5加密
  10. JS复习:第二十章
  11. Win10各版本区别
  12. linux内核体系结构
  13. 实现自己的HashMap
  14. JQuery官方学习资料(译):避免与其他库的冲突
  15. UINavigationController 、navigationBar 、 UINavigationItem、UIBarButtonItem之间的关系
  16. iOS 开发笔记-报错处理
  17. ThinkCMF X2.2.2多处SQL注入漏洞分析
  18. Wtrofms
  19. 【随机化】Petrozavodsk Summer Training Camp 2016 Day 5: Petr Mitrichev Contest 14, Saturday, August 27, 2016 Problem I. Vier
  20. Windows的静态库使用步骤

热门文章

  1. BZOJ3524 &amp; LOJ2432:[POI2014]代理商Couriers——题解
  2. [HNOI2002]跳蚤 【容斥】
  3. 小Q与内存
  4. 折腾到死:matlab7.0 安装
  5. apk文件签名绕过
  6. rar 解压
  7. OpenCV---Canny边缘提取
  8. java绝对路径和相对路径的理解
  9. windows安装zookeeper和kafka,flume
  10. 常用Path路径