Python开发的四国军棋AI智能裁判

目录

1,背景和软件的用途

2,环境介绍

2.1 硬件环境

2.2 软件环境

2.3 素材准备

3,软件功能介绍

4,代码

5,源程序打包下载

1,背景和软件的用途

在玩四国军棋时,除了要四个玩家之外,还需要第五个人做裁判。往往很难凑齐。又想玩又确认的情况下萌生了做一个人工智能裁判的想法。最初在淘宝搜了一下,只有能识别棋子内置特殊标记的裁判器,价格不菲。于是想到用python自制一个。

本人由于不会python,本程序是第一个python程序。第一步自然是搜索,找到了本站内littlezhuhui发表的《python开发的军棋自动裁判软件》参考。该文采用cv2库对图像进行处理,采用pytesseract识别文字。验证后发现图像处理步骤繁杂,文字识别效果不好。

本文采用easyocr库,对摄像头拍摄的照片直接进行文字识别。光线充足的情况下识别效果良好。

2,环境介绍

2.1 硬件环境

1,USB摄像头。用来采集图像。因为棋子面要朝下,光照不好,最好带补光效果的摄像头。

Python开发的四国军棋AI智能裁判

这个没有补光效果,识别笔画多的字时,需要用手机手电筒补光,比如”地雷”。

2,支架

其实只有摄像头是必须的硬件,支架可有可无。比如我在隔离宾馆没有条件,用下图所示的简答搭建也可以用。

Python开发的四国军棋AI智能裁判

有条件可以按图示做一个漂亮一点的支架

2.2 软件环境

1,Pycharm, 程序编写软件;网上下载即可。

2,python安装,参考网络教程安装即可

3,其他支持包通过pip安装

pip install pillow            # 图片处理
pip install opencv_python        #图片处理
pip install easyocr            #OCR文字识别
pip install pygame            #界面
pip install playsound        #播放音效

2.3 素材准备

1,准备了一些图片和一些音效素材。

Python开发的四国军棋AI智能裁判

录制声音的网站有很多,但大多不方便下载,这里推荐httpshiwp.comtext-to-sound.html,简洁方便。声音素材的文件名必须为英文。

Python开发的四国军棋AI智能裁判

分别对应以下音效:
地雷和军棋不能移动
红方不能识别,请放正重试
红方胜

红方胜,蓝方亮旗
红方胜,蓝方游戏结束
蓝方不能识别,请放正重试’
蓝方胜
蓝方胜,红方亮旗’
蓝方胜,红方游戏结束
同归于尽
同归于尽,红方亮旗
同归于尽,红方游戏结束
同归于尽,蓝方亮旗
同归于尽,蓝方游戏结束
同归于尽,同时亮旗
只能比较两个棋子哦

3,软件功能介绍

1,调试和裁判模式切换。 调试模式显示棋子图像,裁判模式隐藏棋子图像,避免泄露。

Python开发的四国军棋AI智能裁判

Python开发的四国军棋AI智能裁判

2,裁判记录自动保存。裁判结果记录为TXT文档,保存在根目录下的cache文件夹,文件按时间命名,程序启动一次的裁判记录存档在一个文件。

Python开发的四国军棋AI智能裁判

3,裁判结果语音播报。萝莉音嗲嗲的,增加游戏乐趣。

4,裁判结果显示。裁判结果会文字显示在软件上方,方便未听清声音时查看。 调试模式会显示棋子名字,裁判模式不显示棋子名字。

演示视频:

四国军棋AI智能裁判

4,代码

1,Config.py:用来方便管理一些参数。

配置数据

class Config:
    def __init__(self):
        pass

    src = "pic/3.jpg"
    checkname = []      # ORC识别的棋子名字
    result = ''         # 裁判结果
    screen = None
    frame = None
    judgemode = False
    filename= ''        # 文本文件名,存放对战记录的文本文件

2,bf_button.py 按钮对象

-*- coding=utf-8 -*-
import threading
import pygame
from pygame.locals import MOUSEBUTTONDOWN

class BFControlId(object):
    _instance_lock = threading.Lock()
    def __init__(self):
        self.id = 1

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(BFControlId, "_instance"):
            BFControlId._instance = BFControlId(*args, **kwargs)
        return BFControlId._instance

    def get_new_id(self):
        self.id += 1
        return self.id

CLICK_EFFECT_TIME = 100
class BFButton(object):
    def __init__(self, parent, rect, text='Button', click=None):
        self.x,self.y,self.width,self.height = rect
        self.bg_color = (225,225,225)
        self.parent = parent
        self.surface = parent.subsurface(rect)
        self.is_hover = False
        self.in_click = False
        self.click_loss_time = 0
        self.click_event_id = -1
        self.ctl_id = BFControlId().instance().get_new_id()
        self._text = text
        self._click = click
        self._visible = True
        self.init_font()

    def init_font(self):
        #font = pygame.font.Font(None, 28)
        font = pygame.font.Font("C:\Windows\Fonts\STSONG.TTF", 20)
        white = 100, 100, 100
        self.textImage = font.render(self._text, True, white)
        w, h = self.textImage.get_size()
        self._tx = (self.width - w) / 2
        self._ty = (self.height - h) / 2

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        self._text = value
        self.init_font()

    @property
    def click(self):
        return self._click

    @click.setter
    def click(self, value):
        self._click = value

    @property
    def visible(self):
        return self._visible

    @visible.setter
    def visible(self, value):
        self._visible = value

    def update(self, event):
        if self.in_click and event.type == self.click_event_id:
            if self._click: self._click(self)
            self.click_event_id = -1
            return

        x, y = pygame.mouse.get_pos()
        if x > self.x and x < self.x + self.width and y > self.y and y < self.y + self.height:
            self.is_hover = True
            if event.type == MOUSEBUTTONDOWN:
                pressed_array = pygame.mouse.get_pressed()
                if pressed_array[0]:
                    self.in_click = True
                    self.click_loss_time = pygame.time.get_ticks() + CLICK_EFFECT_TIME
                    self.click_event_id = pygame.USEREVENT+self.ctl_id
                    pygame.time.set_timer(self.click_event_id,CLICK_EFFECT_TIME-10)
        else:
            self.is_hover = False

    def draw(self):
        if self.in_click:
            if self.click_loss_time < pygame.time.get_ticks():
                self.in_click = False
        if not self._visible:
            return
        if self.in_click:
            r,g,b = self.bg_color
            k = 0.95
            self.surface.fill((r*k, g*k, b*k))
        else:
            self.surface.fill(self.bg_color)
        if self.is_hover:
            pygame.draw.rect(self.surface, (0,0,0), (0,0,self.width,self.height), 1)
            pygame.draw.rect(self.surface, (100,100,100), (0,0,self.width-1,self.height-1), 1)
            layers = 5
            r_step = (210-170)/layers
            g_step = (225-205)/layers
            for i in range(layers):
                pygame.draw.rect(self.surface, (170+r_step*i, 205+g_step*i, 255), (i, i, self.width - 2 - i*2, self.height - 2 - i*2), 1)
        else:
            self.surface.fill(self.bg_color)
            pygame.draw.rect(self.surface, (0,0,0), (0,0,self.width,self.height), 1)
            pygame.draw.rect(self.surface, (100,100,100), (0,0,self.width-1,self.height-1), 1)
            pygame.draw.rect(self.surface, self.bg_color, (0,0,self.width-2,self.height-2), 1)

        self.surface.blit(self.textImage, (self._tx, self._ty))

3,Judge.py 比较棋子大小

#coding:utf-8
#军棋自动裁判 判断类

from config import *

piecePower={
    '工兵':1,
    '排长':2,
    '连长':3,
    '营长':4,
    '团长':5,
    '旅长':6,
    '师长':7,
    '军长':8,
    '司令':9,
    '地雷':-1,
    '炸弹':-2,
    '军旗':200,
}

#裁判类
class Judger:
    def __init__(self):
        pass

    def judge(self):
        if type(Config.checkname)==type('正在处理'):
            return Config.checkname
        return self.judgeOcrResut()

    #识别ocr识别的结果, ocr识别的结果是一个列表,存放于 Config.checkname中
    def judgeOcrResut(self):
        red=''
        black=''
        if len(Config.checkname)!= 2 :
            return "只能比较两个棋子哦"
        else :
            red = Config.checkname[0]
            black = Config.checkname[1]

        #print(red,black)

        if red not in piecePower.keys():
            return '红方不能识别,请放正重试'
        if black not in piecePower.keys():
            return '蓝方不能识别,请放正重试'
        return self.compare(red,black)

    #比较两个棋子棋力的大小
    def compare(self,red,black):
        if piecePower[red] ==piecePower[black] :            #如果相同
            tmp="同归于尽"
            if piecePower[red] == 9 :
                tmp=tmp+",同时亮旗"
            elif piecePower[red]==200 :
                tmp="地雷和军棋不能移动"
        else:
            if piecePower[red] ==-2 or piecePower[black] ==-2 :  # 有炸弹
                tmp='同归于尽'
                if piecePower[red] == 9 or piecePower[black] == 9:  # 炸到司令
                    if piecePower[red]>piecePower[black] :
                        tmp=tmp + ",红方亮旗"
                    else :
                        tmp=tmp + ",蓝方亮旗"
                if piecePower[red] == 200 or piecePower[black] == 200:  # 炸到军旗
                    if piecePower[red]>piecePower[black] :
                        tmp=tmp + ",红方游戏结束"
                    else :
                        tmp=tmp + ",蓝方游戏结束"
            elif piecePower[red] * piecePower[black]==-200 :  # 地雷遇到军旗
                tmp = "地雷和军棋不能移动"
            elif piecePower[red] ==200 or piecePower[black] ==200 :  # 有军旗
                if piecePower[red]>piecePower[black] :
                    tmp= "蓝方胜,红方"
                else :
                    tmp= "红方胜,蓝方"
                tmp=tmp + '游戏结束'
            elif piecePower[red] ==-1 or piecePower[black] ==-1 :  # 有地雷
                if piecePower[red]>piecePower[black] :
                    tmp="蓝方胜"
                else :
                    tmp="红方胜"

                if piecePower[red] == 1 :  # 遇到工兵要反转
                    tmp='红方胜'
                if piecePower[black] == 1 :  # 遇到工兵要反转
                    tmp='蓝方胜'
                if piecePower[red] == 9 :  # 司令撞雷
                    tmp=tmp + ',红方亮旗'
                if piecePower[black] == 9:  # 司令撞雷
                    tmp=tmp + ',蓝方亮旗'
            else :
                if piecePower[red]>piecePower[black] :
                    tmp="红方胜"
                else :
                    tmp="蓝方胜"

        return tmp

4,main主函数

coding:utf-8
将两个棋子的内容从棋子图像采集器中提取出来,调用easyocr识别出文字后判断两个棋子的棋力大小

图像预处理所需的模块
from config import *
from Judge import Judger
import cv2
import time
import numpy as np
import easyocr
import pygame
from playsound import playsound
from bf_button import BFButton

def soundfile(str):
    if str== '红方胜':
        return 'hfs'
    if str== '红方胜,蓝方亮旗':
        return 'hfslflq'
    if str== '红方胜,蓝方游戏结束':
        return 'hfslfyxjs'
    if str== '红方游戏结束':
        return 'hfyxjs'
    if str== '蓝方胜':
        return 'lfs'
    if str== '蓝方胜,红方亮旗':
        return 'lfshflq'
    if str== '蓝方胜,红方游戏结束':
        return 'lfshfyxjs'
    if str== '蓝方游戏结束':
        return  'lfyxjs'
    if str== '同归于尽':
        return  'tgyj'
    if str== '同归于尽,红方亮旗':
        return  'tgyjhflq'
    if str== '同归于尽,蓝方亮旗':
        return  'tgyjlflq'
    if str== '同归于尽,红方游戏结束':
        return 'tgyjhfyxjs'
    if str== '同归于尽,蓝方游戏结束':
        return  'tgyjlfyxjs'
    if str== '同归于尽,同时亮旗':
        return 'tslq'
    if str== '只能比较两个棋子哦':
        return 'znbj2g'
    if str== '红方不能识别,请放正重试':
        return 'hfbnsb'
    if str== '蓝方不能识别,请放正重试':
        return 'lfbnsb'
    if str== '地雷和军棋不能移动':
        return 'bnyd'
    else:
        return '无'

提取目标区域,并对提取的图像进行文字识别
def pickOut(srcImg=None):
    Config.checkname = []
    if srcImg is None:
        # 开始图像处理,读取图片文件
        image = cv2.imread(Config.src)
    else:
        image = srcImg
    # 设置识别中英文两种语言  更换OCR源
    # reader = easyocr.Reader(['ch_sim', 'en'], gpu=False)  # need to run only once to load model into memory  #放在主程序允许一次就可以了
    Config.checkname = reader.readtext(image, detail=0)
    # 判断结果
    tmp = theJudger.judge()
    # 播放音效
    # tmp ='sound/' + soundfile(tmp) + '.mp3'  # 对应成声音文件名,只能英文
    if soundfile(tmp) != '无':
        playsound('sound/' + soundfile(tmp) + '.mp3')
    # 输出文字结论
    Config.result = ""
    if  not Config.judgemode :
        for code in Config.checkname:
            Config.result += code + ","
    Config.result = Config.result + tmp
    print(Config.result)
    with open(Config.filename, "a") as f:  # 结果存入文本文档,追加型式
        f.write(Config.result + '\n')  # 自带文件关闭功能,不需要再写f.close()

在pygame上显示图像
def showImgOnScreen(img, pos, isBGR=True):
    imgFrame = np.rot90(img, k=1, axes=(1, 0)) # 根据摄像头,图片的方向可以通过数组(1,0)来控制
    imgFrame = cv2.flip(imgFrame, 1, dst=None)  # 水平镜像
    if isBGR:
        # cv2用的是BGR颜色空间,pygame用的是RGB颜色空间,需要做一个转换
        imgFrame = cv2.cvtColor(imgFrame, cv2.COLOR_BGR2RGB)
    # pygame不能直接显示numpy二进制数组数据,需要转换成surface才能正常显示
    imgSurf = pygame.surfarray.make_surface(imgFrame)
    Config.screen.blit(imgSurf, pos)

按扭事件处理
def do_click1(btn):
    if Config.frame is None:
        print('Config.frame is None')
    else:
        pickOut(Config.frame)

按扭事件处理
def do_click2(btn):
    if Config.frame is None:
        print('Config.frame is None')
    else:
        Config.judgemode = not Config.judgemode
        if Config.judgemode :
            button2.text = '■裁判模式'
        else:
            button2.text = '□调试模式'

-----------------------------------------------------------------------------------------------

pygame.init()

screen = pygame.display.set_mode([600, 600])  # 设置图形窗口大小为600*600
Config.screen = screen
pygame.display.set_caption("军棋智能裁判")  # 设置图形窗口标题

BLACK = (0, 0, 0)  # 用RGB值定义黑色
WHITE = (255, 255, 255)  # 用RGB值定义白色
BROWN = (166, 134, 95)  # 用RGB值定义棕色

格式化成20220507 114539形式 初始化文件名 用文本文件来保存对战记录
Config.filename = 'Cache/' + time.strftime("%Y%m%d %H%M%S", time.localtime()) + '对战记录.txt'

生产勾选按钮
button2 = BFButton(screen, (0, 550, 110, 30))
button2.text = '□调试模式'
button2.bg_color = BLACK
button2.click = do_click2

生成按钮对象
button1 = BFButton(screen, (260, 545, 200, 40))
button1.text = '判断(快捷键J或回车)'
button1.click = do_click1

准备捕捉摄像头内容
camera = cv2.VideoCapture(0)
设置显示中文所用的字体
font = pygame.font.Font("C:\Windows\Fonts\STSONG.TTF", 36)
窗口背景
screen.fill(BLACK)

生成一个定时器对象
timer = pygame.time.Clock()

#生成一个裁判员对象
theJudger = Judger()

设置easyocr,设置识别中英文两种语言
reader = easyocr.Reader(['ch_sim', 'en'], gpu=False)  # need to run only once to load model into memory
reader = easyocr.Reader(['ch_sim'], gpu=False)  # need to run only once to load model into memory #只要中文即可

keepGoing = True
while keepGoing:  # 事件处理循环
    # 显示摄像头内容
    success, frame = camera.read()
    Config.frame = frame
    # cv2直接显示捕获的图像没有问题,但要在pygame中正常显示,还要做一些处理
    # cv2.imshow('MyCamera',frame)
    if not Config.judgemode :
        showImgOnScreen(frame, (0, 50), True)
    else:
        showImgOnScreen(cv2.imread(Config.src), (0, 50), False)
        # screen.fill(BROWN, rect=(0, 50, 600, 480)) # 不显示图片  用背景色代替

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            keepGoing = False
        if event.type == pygame.KEYDOWN:  # 如果按下了键盘上的键
            if event.key == pygame.K_j:  # 如果按下'j'
                pickOut(frame)
            elif event.key == pygame.K_RETURN:  # 如果按下了回车键
                pickOut(frame)
            elif event.key == pygame.K_ESCAPE:  # 如果按下了ESC键则退出
                keepGoing = False
        button1.update(event)
        button2.update(event)
    # pickOut(frame)  # 不停检测  比较耗资源  且无法记录历史
    # 输出提示信息

    text = font.render(Config.result, True, WHITE)
    text_rect = text.get_rect()
    # text_rect.centerx = screen.get_rect().centerx
    text_rect.x = 10
    text_rect.y = 0
    screen.fill(BROWN, rect=(0, 0, 600, 50))  # 写过字的区域要刷一遍背景色才能重新写字
    screen.blit(text, text_rect)

    pygame.display.update()  # 刷新窗口
    button1.draw()
    button2.draw()

    timer.tick(30)  # 设置帧率不超过30

pygame.quit()  # 退出

5,源程序打包下载

下载地址:基于Python的四国军棋AI智能裁判-Python文档类资源-CSDN下载

Original: https://blog.csdn.net/lietuzhou/article/details/124642954
Author: lietuzhou
Title: Python开发的四国军棋AI智能裁判

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/704496/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球