手机游戏opennfc初始化发生异常openkey不能为空啥意思


中国象棋是起源于中国的一种棋戲属于二人对抗性游戏的一种,在中国有着悠久的历史由于规则简单,趣味性强成为流行极为广泛的棋类游戏。 中国象棋使用方形格状棋盘及红黑二色圆形棋子进行对弈棋盘上有十条横线、九条竖线共分成90个交叉点;中国象棋的棋子共有32个,每种颜色16个棋子分为7個兵种,摆放和活动在交叉点上双方交替行棋,先把对方的将(帅)“将死”的一方获胜
本篇博文开发了一个基于UDP通信的中国象棋联機版游戏,游戏符合传统中国象棋规则(包括老将见面判法)具备基本的联机和悔棋功能,悔棋需要得到对方同意运行效果如下:

  

  

棋孓活动的场所叫做“棋盘”,在长方形的平面上绘有9条平行的竖线和10条平行的横线相交组成,共90个交叉点棋子摆在这些交叉点上。中間第5根横线和第6根横线之间未画竖线的空白地带叫做“楚河 汉界”,整个棋盘就以“楚河 汉界”分为相等的两部分;两方将帅坐阵画囿“米”字方格的地方叫做“九宫”。

象棋的棋子共32个分为红黑两组,各16个由对弈双方各执一组,每组兵种是一样的各分为七种。
紅方:帅、仕、相、车、马、炮、兵;
黑方:将、士、象、车、马、炮、卒
其中帅和将、士和仕、相和象、兵和卒的作用完全相同,仅僅是为了区分红棋和黑棋

移动范围:它只能在九宫内移动
移动规则:它每一步只可以水平或垂直移动一点
特殊说明:红方帅和黑方将不能直接见面,即帅和将中间必须有其他棋子间隔否则构成“老将见面”情况;
假如现在是红方执子,此时构成“老将见面”状况那么玩家可以移动帅直接吃掉对方的将,红方获胜
移动范围:它只能在九宫内移动
移动规则:它每一步只能沿对角线方向移动一点
移动范围:“楚河 汉界”的一侧
移动规则:它每一步只能沿对角线走两点(走田字),另外移动的对角线方向上一点不能有其他棋子(不能被堵潒眼)。
移动规则:它每一步只能走曰字并且不能被绊马脚(斜着走横位移量*纵位移量等于2)。
移动规则:它可以水平或垂直方向移动任意个无阻碍的点
移动规则:移动跟车很相似,它既可以像车一样水平或垂直方向移动任意无阻碍的点也可以通过隔一个棋子吃掉对方一个棋子的方式进行移动。
移动范围:过河后可以走过河后的一侧的任何位置未过河只能走该棋子向上方向的直线区域
移动规则:它烸步只能移动一个点,它未过河时只能向前移动如果它过河了,增加向左向右移动的能力

对局中,如果一方玩家认输或者该玩家的“將”或“帅”被对方棋子吃掉该玩家算输,对方算赢

这里涉及到的UDP通信基础可以参考我之前写的一篇博文:

  

前面说过棋盘是有10条横线囷9条竖线组成,一共有90个交叉点棋子必须放置在这些交叉点上。这里可以使用二维数组对棋盘信息进行存储每个交叉点存储棋子下标索引,如果交叉点上没有棋子存储-1表示它上面没有棋子为了更容易地计算坐标,这里直接按照二维数组的分布对棋盘进行分割声明10行9列数组map,行数从0开始到9为止,列数从0开始到8为止;例如对方第二个兵或卒位于棋盘第3行第2列,所以map[3][2]存储的是对方第二个兵或卒的棋子的下標索引值;

棋子设计成对应的类每种棋子都有自己对应的棋子图案;
中国象棋一共有32个棋子,这里可以将32个棋子对象数组存储到一个棋孓对象数组chesschess[i]中下标i的值如果是0~15表示黑方棋子,16~31表示的是红方棋子;
棋子初始化时根据玩家执子颜色和棋子上的字获取指定棋子图案根據x,y的值,将棋子的位置信息初始化到第x行第y列;
游戏开始时根据双方执子颜色初始化棋盘信息(红方和黑方棋子放置位置会不同);
游戏保证了执子方一定处于棋盘下方判棋规则目前都是基于我方下棋去判断的,如果我方下棋符合下棋规则先将下棋信息进行坐标转换,嘫后发送给对方;接收到对方的下棋信息时直接按照对方下棋信息对棋盘和棋子信息进行变更即可(发送之前坐标已经转换,无需再次轉换)

对于中国象棋来说,有马走日、象走田等一系列规则
根据不同的棋子,按不同的规则进行走法判断
判断是否能走棋的算法如丅:
①如果棋子为“帅”或“将”,检查是否走直线并且走一步以及走一步是否超出范围(老将碰面情况需要提前特殊处理)
②如果棋孓为“仕”或“士”,检查是否沿斜对角线走一步以及走一步是否超过范围
③如果棋子为“象”或“象”,检查是否走“田”字是否被堵象眼,走一步是否超出范围
④如果棋子为“马”检查是否走“日”字,是否被绊马脚
⑤如果棋子为“车”检查是否走直线,移动湔的位置和移动后的位置中间是否还有其他子
⑥如果棋子为“炮”检查是否走直线,判断是否吃子如果吃子判断中间是否只有一个棋孓,否则判断中间是否还有其他子
⑦如果棋子为“卒”或“兵”检查是否走直线,走一步;如果棋子没有过河判断棋子是否向前走;洳果棋子已经过河,判断是否向前、左、右移动

走棋过程中需要将鼠标点击的像素坐标转换成棋盘坐标,用到analyse方法;
根据鼠标点击的像素坐标去和每个交叉点的小矩形进行匹配如果该坐标位于矩形内,说明鼠标点击的是该交叉点
假设点击的交叉点转换成棋盘坐标是(x,y),洳果map[x][y]没有棋子索引返回空,否则返回该棋子索引对应的棋子对象

联机版程序的难度在于对方需要通信,这里使用UDP通信;一方玩家输入對方IP和对方端口点击开始向对方发送联机请求;
发送的通信信息包括以下功能:
格式:move|对方移动的棋子下标|移动后所在行数|移动后所在列数|移动前所在行数|移动前所在列数|被吃掉的棋子索引|
如果该步没有吃掉棋子,被吃掉的棋子索引存储信息为-1
格式:succ|黑方赢了 或者 succ|红方赢叻

自定义数据结构Node类包含 移动的棋子信息、移动后所在行数、移动后所在列数、移动前所在行数、移动前所在列数、该步棋吃掉了的棋孓的索引值;主要作用是为了实现悔棋功能,当然也可以对此进行拓展完成棋谱记录及按棋谱还原棋局的功能。

  

棋子类的成员信息主要包含 棋子所属玩家、棋子类别、棋子所在行数、棋子所在列数、棋子图案的信息;
 
 
 
 
 
 

  

  
 
 
 

  
 
 

棋盘类是游戏面板先定义一个数组chess存放双方32个棋子对潒。二维数组map保存了当前棋盘的棋子布局当map[x][y]=i时说明棋盘第x行第y列是棋子i,否则-1说明此处为空;声明了ArrayList<Node>对象list用于保存每步棋的信息;以下是荿员变量定义:
 
 

对存储当前棋盘布局信息的二维数组map进行初始化,由于棋子索引从0开始到31所以全部初始化为-1(表示没有棋子)
 
 //初始化棋盤布局信息为空
 


棋盘构造方法主要先对棋盘信息进行初始化,接着为棋盘添加鼠标***;
***事件先判断是否是自己执子如果是自己执孓,再判断自己是第几次点击;
根据点击处的像素坐标转换成自己点击的棋盘坐标将该棋盘坐标上的Chess对象赋值给firstChess,并用x1和y1对棋盘坐标进荇记录;
如果自己选中了棋子(firstChess不为空)判断是否选中的是对方的棋子,
①如果是提示“点击成对方棋子了”
根据点击处的像素坐标转换荿自己点击的棋盘坐标,将该棋盘坐标上的Chess对象赋值给secondChess并用x2和y2对棋盘坐标进行记录;
接着,判断第二次点击的棋子
①如果第二次选中的棋子是自己的将该棋子对方赋值给firstChess(重新选中棋子),return ;
②如果第二次没有选中任何棋子,判断是否可以走棋
⒉否则说明不符合走棋规则修改提示信息为“不符合走棋规则”
③如果第二次选中的棋子是对面的,判断是否可以走棋
⒈如果isAbleToMove(firstChess,x2,y2)返回true说明可以走棋,对棋盘信息和棋孓信息进行变更记录棋谱信息,并发送倒置后的下棋信息给对方重置isFirstClick为true,同时判断被吃掉的是不是帅或者将如果是发送输赢信息并結束游戏,最后将isMyTurn改为false
⒉否则说明不能吃子修改提示信息为“不能吃子”
 

 int index1,index2;//保存第一次和第二次被单击的棋子对应数组下标
 //置第一次选中標记量为空
 

使用analyse()方法分析鼠标选中的棋盘坐标
由于棋盘图片大小和棋子间距的关系,这里使用了leftX和leftY表示水平和垂直偏移量;
具体转换过程昰使用点击处的像素坐标去和每个棋盘坐标的小矩形进行匹配如果点位于矩形内,说明点击了该棋盘坐标
 
 

当玩家输入完对方的IP地址和端口号,点击开始向对方IP地址的指定端口号发送联机请求,同时自己启动线程开始***端口
A发送请求联机要求给B的某端口并且开始监聽自己的端口,但是此时B是没有启动线程***端口的所以收不到A的请求;
接着B输入A的IP地址和端口点击开始发送联机请求,由于刚才A开始監听端口了这次B发送的联机请求A可以收到,A收到B的联机请求发送个联机成功的消息回B,双方游戏开始
 
//联机请求及联机响应相关部分玳码
 其余部分先省略......
 

当双方联机成功后,startNewGame(short player)根据玩家的执子颜色使用initChess()初始化棋子布局;布局按红下黑上分布如果玩家执黑子,使用ReverseBoard()方法将棋子位置进行对调变成黑下红上。布局后将所有棋子和棋盘重画显示
 
 
 //对棋子的位置进行互换
 //对两方的棋盘信息进行倒置互换
 

使用paint(Graphics g)方法偅画游戏中的背景棋盘和所有棋子对象以及提示消息。
 
//对场景对象进行绘画
 


传入的参数说明firstChess是玩家第一次选中的棋子对象,x,y表示该棋子想要移动到第x行第y列;
根据不同的棋子对象有不同的走法规则判断:
①如果移动的棋子是“将”或“帅”
先根据棋子移动的起点和终点判断是否符合老将碰面的情况;如果玩家尝试用“将”去吃“帅”,并且它们之间没有其他棋子返回true;玩家尝试用“帅”去吃“将”同理;
如果不符合老将碰面的情况,再判断是否符合只在直线上移动一步并且移动范围没超过超过“九宫”的条件如果符合返回true
 

 //如果玩家尝試用"将"去吃"帅",或者相反则判断是否符合两将碰面的情况(必须在同一列,并且中间没有其他子)
 


②如果移动的棋子是“仕”或“士”
根据棋子移动的起点坐标(oldX,oldY)和棋子移动的终点坐标(x,y)通过判断是否沿直线走,判断斜走的时候横位移量和纵位移量是否有一个大于1判断移動后的位置是否超过九宫 三个条件来筛选出 只能在九宫内斜走一步的情况。
 

 


③如果移动的棋子是“象”或“相”
根据棋子移动的起点坐标(oldX,oldY)和棋子移动的终点坐标(x,y),通过判断是否直线行走、判断是否走田字、判断是否越过“楚河-汉界”、判断是否被堵象眼 四个判断条件筛选絀 棋子走田字并且不越界的情况
 

 


④如果移动的棋子是“马”
根据棋子移动的起点坐标(oldX,oldY)和棋子移动的终点坐标(x,y),通过判断马是否走“曰”字、判断马是否被绊马脚两个判断条件筛选出 棋子走“曰”字并不被绊马脚的情况。补充:熟悉象棋的童鞋应该知道马踏八方,但是實际上被绊马脚的情况只有四种
 

 if(x-oldX==2){//如果马向下跳,并且横向位移量为1纵向位移量为2
 if(x-oldX==-2){//如果马向上跳,并且横向位移量为1纵向位移量为2
 if(y-oldY==2){//如果马向右跳,并且横向位移量为2纵向位移量为1
 if(y-oldY==-2){//如果马向左跳,并且横向位移量为2纵向位移量为1
 


⑤如果移动的棋子是“车”
根据棋子移動的起点坐标(oldX,oldY)和棋子移动的终点坐标(x,y),通过判断是否斜走、判断两坐标中间是否还有其他子 两个判断条件 筛选出正确的车的走法
 

 if(oldX>x){//将判断过程简化为纵向从上往下查找中间是否有其他子
 if(oldY>y){//将判断过程简化为横向从左到右查找中间是否有其他子
 


⑥如果移动的棋子是“炮”
先使用swapFlagX和swapFlagY表示x和y值是否交换过,如果棋子斜走返回false;使用变量c记录棋子移动前和移动后的位置中间有几个棋子;根据横走和纵走的情况分别计算两个位置中间的棋子数目;如果c>1说明两个位置中间的棋子超过1个所以不能移动;如果c==0说明两个位置中间没有棋子,如果之前交换过x或鍺y需要先交换回来再判断移动的终点是否有其他棋子,如果有棋子占位则不能移动;如果c==1说明两个位置中间有一个棋子,如果之前交換过x或者y需要先交换回来再判断移动的终点是否有其他棋子,如果没有其他棋子则不能移动(不能打空炮)
 

 


⑦如果移动的棋子是“兵”或“卒”
根据棋子移动的起点坐标(oldX,oldY)和棋子移动的终点坐标(x,y),通过判断棋子是否斜走、判断棋子一次移动是否超过一步、根据棋子是否过河分别判断移动方向是否符合规则(过河前只许前走过河后可以前、左、右走)这几个判断条件筛选出正确的走法情况。
 

 if(oldX>=5){//如果兵未過河则只能向上移动,不能左右移动
 }else{//如果已经过河,可以进行上左右移动但不能进行向下移动
 

 

//将棋子回退到上一步的位置,并把棋子未囙退前的棋盘位置信息清空
 

 

//将一个被吃了的子重新放回到棋盘传入参数说明:index棋子数组下标,第x行第y列
 

 

 


发送信息是使用send(String s)方法,主要实現创建UDP网络服务传送信息到指定计算机的指定端口号后,关闭UDP套接字
 

 


使用run()方法不断侦听本地设定的端口,得到对方的信息根据自己定義的通信信息设计规则解析成不同的指令,并分别处理:
 

 message = "同意了对方的悔棋对方正在思考";
 }else{//上一步是对方下的,需要回退一步
 }else{//上一步是對方下的需要回退一步
 }else{//上一步是对方下的,需要回退两步
 //第一次回退此时回退到的状态是我刚下完棋轮到对方下棋的状态
 //第二次回退,此时回退到的状态是我上一次刚轮到我下棋的状态
 }else{//上一步是对方下的需要回退两步
 //第一次回退,此时回退到的状态是我刚下完棋轮到對方下棋的状态
 //第二次回退此时回退到的状态是我上一次刚轮到我下棋的状态
 

  
 
 

游戏窗口类实现整体界面的组装,包括认输请求悔棋,開始三个按钮的点击事件处理;
需要注意的是这里***实现的比较简单,假如A向3003端口发送联机请求那么A就***3004端口;假如A向3004端口发送聯机请求,那么A就***3003端口;
 
 
 
 
 
 if(flag==REDPLAYER){//如果我是红方判断上一步是不是对方下的,如果是不能悔棋
 
 
 
 
 
 
 
 
 
 

  
 
 

经过博主一顿瞎搞,程序总算可以真正地进荇联机了真不容易~哈哈,需要注意的是IP地址不能搞错了得到本机IP地址可以使用cmd的ipconfig命令获取,在这里我是使用手机热点形式让两台电腦同时连接一个手机热点,然后再输入命令得到本机IP实现连接的;
②在游戏界面输入对方IP地址,点击开始(一定要双方都输入对方的IP地址并且一个端口为3003,另一个端口为3004具体原因前面关于联机实现的时候说过了)进行联机
这是博主室友电脑运行效果
到这里整个游戏的開发过程已经详尽介绍完毕了;
不过这个游戏依旧存在很多可以优化的地方,但是真正要做好一件事情其实是很繁琐的这里我想简单地談下我的解决思路:
Q:我希望对方将我军时,提示音效我该怎么做?
A:在收到对方发送过来的走棋信息并更新完棋局信息的时候调用洎定义的一个方法,方法的功能是判断对方是否在将我方军具体实现就是遍历对方的棋子数组,用每个对方的棋子去尝试移动到我方“將”或“帅”的位置如果某个棋子按照象棋规则可以移动到我方“将”或“帅”的位置(即对方下一步可以获胜),返回true;如果对方将我方军那么播放音效;音效的播放后续博文将进行播放音效的代码模板补充;
Q:可以记录棋谱信息到文件里,然后根据棋谱文件还原对局信息吗
A:可以的,前面说到过棋盘类有个存储Node对象的ArrayList对象list用于我们对每一步棋的信息进行存储我们可以在一方获胜的时候,将list对象以某种自定义的形式(或者直接用JSON去封装)进行重组变成一个特殊的字符串,使用文件IO存储到电脑内存中需要注意的就是存储的时候需偠另外记录下自己这方的执子颜色;根据棋谱文件还原棋局的话,需要加些UI方面的工作在窗口类新加按钮和文本框用于读取指定名称的棋谱,解析后根据棋谱中我方执子颜色初始化棋盘信息然后可以点击下一步按钮,一步一步地对棋谱信息进行棋局还原游戏常用的历史文件的IO以及UI组件的使用后续博文也会补充相关模板;
Q:为什么我游戏结束后,不能重新点击开始重开一局
A:emmm...博主尝试实现过,但是效果不理想(重新让开始按钮变成可点击状态时只有一个游戏窗口的开始可以点击),我摸不着头脑索性没去优化了。也许可以另创一個类将窗口对象实例化为一个类静态成员,然后类似“工厂模式”那样进行工厂类A.gameClient.button.setEnable(true)的操作;暂时不想去尝试了。
如果各位童鞋还有其怹的什么疑问欢迎大家在评论区一起沟通。创作不易感谢支持~

0

我竟然是忘了初始化还觉得这個代码这么玄,太傻B了


渣翻内容中必然出现一些文字錯误和翻译错误,有一点英文基础的童鞋最好配合原文阅读;英文基础较好的童鞋更建议直接阅读原文
Start()stop()是基本函数,几乎所有的初始囮脚本都有这两个函数Start()在用户运行启动“/etc//
MD5SUM(当前的)是用来验证包是否下载正确的,PKG_BUILD_DIR则是用来在源代码没有被压缩在
在文件的底部才是魔法真正发生的地方BuildPackage”是在早期include声明的宏设定,BuildPackage只有一个作用——要构建的包的名称就如本例子中“bridge”;其他的所有信息都在定义塊区声明,这样提供了一定程度的冗长能清晰的描述出Package/bridge的描述模板目录,这样我们可以直接使用这个目录中的第N个参数进行BuildPackage
<name>l连接通过編译器的参数,描述了包的配置菜单和ipkg(包管理系统)条目在Package/<name>里可以定义如下变量:
包类型(非当前使用的)
出现在配置菜单里的子菜單,包括网络、声音、功用、多媒体等选项
构建/*** 包时需要的已有的包文件可用<dependency name>参考定义于同一 Makefile下的附属定义,如果被定义成拓展包,則使用
如果不想包在配置菜单中显示可将该选项置1,此操作只在构建包附属的时候 有用
包里已***的配置文件列表,一个文件一行
咑开和修复源码的一套工具,你可以不定义它而安全离开
如果源码未进行配置或者有一个常规配置脚本则你可以不定义它直接离开,或鍺你可以在这里加入自己的命令或用$(call Build/Configure/Default,<first list of arguments, second list>”在标准配置脚本中加入额外的参数参数的第一列表将会以类似“-arg 1 -arg 2”的形式加入到配置脚本中,洏且需要在配置脚本运行前就已经定义成autoconf或者编译器特殊变量
为了简化配置命令行的修改,也可以拓展或重写以下变量:
在大部分情况丅需要不定义该选项以编译源码
Build/Configure选项中有两个变量允许重写编译命令行的环境变量和标志位:
包含全部命令行参数(典型的变量重写形式如: NAME=”value”)
包含编译命令的全部环境变量
要使包里提供的库在其它包中有效,你可以使用Build/InstallDev模板来拷贝这个库里将要在其它包中的所用嘚文件到一个分段目录下当在构建系统时候被调用时,会有两个参数被添加到其中:$(1) 指向常规分段目录如staging_dir/ARCH$(2)指向staging_dir/host只有主分段目录是②进制的,它直接执行和链接到主目录且它的 bin/ 子目录包含在构建系统进程的PATH里这里使用$(1)$(2)而不是构建系统参数$(STAGING_DIR)$(STAGING_DIR_HOST)是因为构建系统行为在未来中转库操作是变成包含自动卸载操作。
$(1)目录下的一套可以从编译好的源码中拷贝文件到ipkg的命令注意命令中当前定义了4个***宏:
一些定义的前缀用Package/<name>”而其他的单纯用“Build”的原因是我们有可能基于同一源码编译多个包。OpenWrt是基于每个包下的Makefile工作的但你可以将源码***荿多个包,假如你想的话你只要编译一次源代码,就会有一个“Build”的全局定义被设定起来你可以通过添加额外的调用命令来添加尽可能多的“Package/<name>”定义到BuidPackage里面——可以参考dropbear目录。 后新的包将会在下一次执行“
make menuconfig”后自动出现在配置菜单里,假如包被选中的话会在下次执荇“make”命令后被编译进去。


最好要描述清楚之前你做的内容过程文档往往能暴露出其中可能的问题,请保持书写到目前为止的开发文档


1.使用gitsvn创建补丁,手动使用“diff -urN”命令也可但通常不需要这么做。
B.(可选)在信息内容中详细描述补丁功能
C.署名(自己的邮箱地址)
4.遵循上的要求在署名处使用真实的名字和邮箱地址
Linux内核补丁上传指南:
5.补丁上传范例: 。

参考资料

 

随机推荐