[python+opencv]从0开始的ChineseChessOL项目
背景
暑假某日,家父突然提出想我做一个象棋的程序。由于在上个学期,我学过一点java的网络编程,也搭建了一台自己的服务器(腾讯云,后面考),同时考虑到下象棋没有什么复杂的算法,于是欣然答应。
项目地址
纯代码在github,完整客户端(包含所需要的图片和一个python安装包)在个人网页
目前的版本仍无法发行,有一些细节需要处理,同时需要使用者自行安装python+numpy+opencv的环境,但已经实现了相关的功能。
经验分享
第一步:选择编程语言和GUI工具。
C++和java的网络编程我猜不会比python更简单,rust没有考虑过网络编程,所以选择了python。图形化界面本来想选用PySimpleGUI,但是发现不太方便获取鼠标点击的坐标,我没想出合适的方法控制圆形的棋子,最后还是选择了opencv。
第二步:初始棋盘棋子界面显示。
由于opencv使用imread得到的其实是<class 'numpy.ndarray'>,所以可以采用如下的方式在一个棋盘的左上角显示一个棋子
import cv2
img = cv2.imread("pad.jpg") # pad.jpg为棋盘资源。
h,w,b = img.shape
print(h,w)#实际上是ndarray的index range,也是图片的分辨率
pieces = cv2.imread("pieces.png") # 实际项目中没有这个文件,这是举个例子
h2,w2,b2 = pieces.shape
img[0:h2,0:w2] = pieces
cv2.imshow("img",img)
cv2.waitkey(0)# 后面考
如法炮制,显示所有棋子即可(具体的像素坐标要找一下,可以用画图,也可以见第三步)
tip:这里有个小问题,就是我在网上下载的素材,棋子虽然是png格式,周围也确实是透明的,但是opencv如果直接用imread读取图片,得到的数据只有3通道,没有透明度通道,导致棋子显示时周围会有白边,也意味着我以后显示所有图片都是矩形。通过添加参数可以读到透明度,但我尝试了把棋盘转成png格式后没办法读出4通道,导致无法将棋子叠加到棋盘上,最后只能退而求其次保留白色边框。
第三步:鼠标反馈
opencv中有一个setMouseCallback函数,不知道为什么pycharm显示没找到reference,但是可以正常使用的。它有两个参数,第一个参数是窗口的名称,即想要在哪个窗口获得反馈,第二个参数是反馈的方式,参数类型是函数指针(函数名)
这个第二个参数所需的函数大概长这样,并且这样就可以得到鼠标左击的坐标
def on(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
print(x,y)
有了鼠标的像素坐标,也有了棋子的像素坐标(如果你能正常显示棋子,那你一定有办法知道它所在的像素坐标),下面你应该知道该干什么了吧
第四步:选中效果
在一个象棋程序里,点击一个棋子的选中效果大概有2,一是棋子要有标识,二是提示棋子可以往哪走。
一:根据第二步的tip,我选择通过画图把每个棋子的透明白边染成绿色作为选中效果,一旦哪个棋子被选中,则使用选中的图片覆盖先前的图片
二:最难,最容易出bug也是最核心的地方。对于每一种棋子,需要有不同的提示方式,象棋的规则也在这里体现出来。
tip:要考虑选中一个棋子后再选另一个棋子的时候如何复原。我采用的方式是每次选取前都重新加载棋盘。虽然浪费资源,但对象棋来说也足够了。
第五步:棋子移动
既然第四步已经在选中棋子后已经在棋盘上显示了往哪走的提示,那么相信你也有办法获得这些提示的像素坐标。则你可以判断是否点击了提示的坐标。则可以判断棋子是否移动。移动则改变初始坐标即可。
第六步:细节完善
至此,你已经可以做出一方棋子无限下棋的样子了。以下是一些规则方面的细节,不做赘述。
- 吃子
- 胜利条件
如果你在程序开始加上随机数使得你可以随机执黑棋或者红棋,那么你可以同时把另一方下棋的样子也实现了。
更多的细节,在开始网络编程之后才会出现。
第七步:服务端,客户端通信
本来以为按照教程创建socket就行了,但是实际操作之后才发现有2个问题。
一是腾讯云服务器bind绑定公网IP失败
二是客户端在连接服务端时无法访问端口
问题一与云服务器的部署有关。具体参见NAT协议。简单的说解决方法就是客户端绑定公网IP,服务端绑定私网IP,然后套接字才能连接
问题二与云服务器的防火墙有关。以下是我的入门级服务器默认打开的端口。
可以看到默认打开的只有web服务,ssh服务,windows远程桌面(还没尝试过,懒得用了~咕)和ping服务
所以需要手动打开你想要使用的那个端口。(这边顺便吐槽以下,腾讯云服务器不允许用户拿自己的服务器当邮箱。除非连接第三方平台,否则邮件的端口是禁止打开的。所以不要想用自己帅气的域名给自己搞个帅气的邮箱了。其实也很合理,毕竟假如服务器用一年就不用了,到时候这个邮箱就找不到人或者只能找到它的下一个主人了)
anyway,然后就可以通信了
第八步:轮流对弈
我一开始的想法就是,后下的人等待服务器的消息,等到了之后再下棋。发送的消息只要同步了坐标,一切都可以正常运行。(没有把运算工作放到服务端。服务器只起一个连接的发送消息的作用)
但是发现这样opencv显示的图片无响应
在查询了资料后发现,显示图片之后主程序必须停留在waitkey上给图片显示的时间,否则就无响应。setMouseCallback参数里的函数有点类似于在另一个线程中运行,也是现在唯一能运行代码的地方。但是这个函数在鼠标在图像上移动的时候就会执行一遍,导致如果在这个函数里接收服务器端的消息非常不现实。看似我们这里已经走到死局了,这个项目并不像我们想的那么好实现。
但是柳暗花明又一村。我们可以加入多线程。单独开一个线程来接收信息并控制鼠标反馈。
为了防止两方一起下棋。我本来想找setMouseCallback的退出方式,发现没有,但可以改变它调用的函数。于是我在一方下过棋之后改成do_nothing(见代码),do_nothing根据监听的线程的消息把函数再调回去。这样这个项目就算完成了。
总结
1:学到了不少东西,动手做的东西很多。主要是第一次网络编程和从0开始学习了python-opencv。
2:目前还有一些不足,如对于结尾没有处理好,草草收尾了,以及等待的一方没有例如对方思考中这类提示,必须鼠标移动之后才能看到棋子的变化。同时没有加背景音乐,数据存储等待功能。