【项目源起】
军棋的玩法通常有三种:明棋、暗棋和翻棋,其中暗棋是一种知己不知彼的玩法,每人使用一种颜色的棋子,立起来,正面朝向自己摆放,布置自己喜欢的阵法。然后按照规定的行棋方式走棋。想要吃棋的时候,并不知道对面是什么棋,也不能看。这时候,需要第三个人来担任裁判。裁判查看双方的棋子,比大小,小的一方被吃,拿出棋盘(拿出棋盘后也不能暴露是什么棋),大的一方留在棋盘上,直到分出胜负。于是我们决定设计制作一套智能军棋,只需两个玩家对战,不仅解脱裁判的尴尬与无聊,更能让玩家专注局势的博弈,不受裁判影响,极大提升玩家体验,如图1-3所示。
【项目展示】
【功能简介】
(1)智能识别:智能军棋通过Huskylens视觉识别传感器扫描放置于擂台上军棋标签,对双方的棋子进行准确识别。
(2)智能评判:智能军棋通过内置算法判断双方出棋等级高低,从而给出胜负的判定。
(3)声光效果:智能军棋搭配音效搭配LED光效,紧张刺激又烧脑,增加玩家的代入感。
(4)磁力吸附:棋子的底部设计有三枚强磁铁,与擂台上的磁铁相互吸附,避免错误摆放。
步骤1 1.系统设计
在进行系统设计前,我们先来了解下传统军棋的玩法。
(1)吃子规则
一副军旗双方共含有50个棋子,两方各有25个棋子,分别是司令、军长、师长、旅长、团长、营长、连长、排长、工兵、地雷、炸弹、军旗。
司令→军长→师长→旅长→团长→营长→连长→排长→工兵,小棋遇大棋被吃,相同棋子相遇,则同归于尽;工兵能排除地雷,其它棋子不能排雷;炸弹与任何棋子相遇时同归于尽,也就是说,在军棋中,司令是最大的,就是司令可以吃一切的棋子。
(2)棋子识别
本作品最初设计时,是想在每枚棋子中放置不同阻值的电阻来予以区分,但通过串口读取数据发现数据误差较大。因此放弃这种方案,采用AprilTag标签进行标识,Huskylens视觉识别传感器进行识别的方案。同时为了防止对方记棋,我们对每一颗棋子都标记了AprilTag,这样一套棋子就需要50个AprilTag标签。这样问题就转换成Huskylens识别标签了问题了。
(3)棋子PK算法
依据军棋的吃子规则,我们对于两个上擂台棋子的PK,只要以下几类问题:
▲军衔PK:这类棋子属于进攻型棋子,只需按照上述军衔等级高低判断胜负,等级相同同归于尽。
▲工兵拆雷:工兵遇到地雷,则工兵拆除地雷。
▲工兵扛旗:军棋必须在棋盘中两个大本营中的其中之一,且不可移动。工兵可扛旗,结束棋局。
▲炸弹处理:防御型棋子,此棋子在棋盘上不可动,只能被动接受PK,任何可动棋子与之相遇两者同归于尽。
▲地雷处理:防御型棋子,此棋子在棋盘上不可动,只能被动接受PK,除工兵以外的棋子与之相遇两者同归于尽。
针对上述问题进行算法设计,作为双方PK的核心算法。通过Huskylens识别出棋子的ID来判断棋子PK的胜负。
(4)声光效果
为了增加棋局的娱乐性,我们在系统中增加的了声光效果,用绿色光代表胜利,红色光代表失败,并配以适当的音效。
步骤2 结构设计
本次作品结构设计涉及两部分:一部分是棋盘结构,使用LaserMaker绘制结构图。另一部分是3D打印棋子结构件。
(1)棋盘结构件设计(松木板)
首先以3mm厚的松木板为原料,使用“快速造盒工具”,生成一个盒体。然后删除盒体左右两侧,并在盒体顶部,添加“棋盘”图片,并绘制蜂鸣器、Huskylens安装孔位和擂台孔位。具体设计如下图8-9所示。
其次依据上步生成盒体宽(内部)的尺寸绘制棋子收纳盒,具体设计图如下图10所示。
最后绘制出25个棋子的面板,如下图11所示
(1)棋子结构件设计(3D打印)
我们在Fusion 360中,设计绘制棋子外壳结构和擂台结构如下图12-13所示。
步骤3 电路连线
智能军棋系统电路连接如图14所示。两条灯带分别接D7,D8口,蜂鸣器模块接D4口,Huskylens视觉识别传感器通过I2C口与主控Arduino连接。
步骤4 代码编写
主程序如下图15所示,包含了两个函数“定位识别”、“判断结果”,其中“定位识别”是通过识别到的两个方框中X的坐标值进行比较从而判断,从而判断所对应ID的两颗棋子左右位置;“判断结果”则依据前面定义的算法对两颗棋子进行胜负判定。
/*!
* MindPlus
* uno
*
*/
#include <DFRobot_NeoPixel.h>
#include <DFRobot_HuskyLens.h>
#include <DFRobot_Libraries.h>
// 动态变量
String mind_s_M1, mind_s_M2;
volatile float mind_n_N1, mind_n_N2;
// 函数声明
void DF_DingWeiShiBie();
void DF_CeLiangZuoBiao();
void DF_N1DaYuN2();
void DF_N1XiaoYuN2();
void DF_PanDuanJieGuo();
void DF_MingChen();
void DF_DiLeiChuLi();
void DF_TongGuiYuJin();
void DF_ZuoBianSheng();
void DF_ZhaDanChuLi();
void DF_YouBianSheng();
void DF_GongBingChaiLei();
void DF_KangQi();
void DF_XiangDeng();
void DF_LXiaoYuR();
void DF_LDaYuR();
// 创建对象
DFRobot_HuskyLens huskylens;
DFRobot_NeoPixel neoPixel_7;
DFRobot_NeoPixel neoPixel_8;
DFRobot_Tone DFTone;
// 主程序开始
void setup() {
huskylens.beginI2CUntilSuccess();
huskylens.writeAlgorithm(ALGORITHM_TAG_RECOGNITION);
neoPixel_7.begin(7, 6);
neoPixel_8.begin(8, 6);
neoPixel_7.setBrightness(255);
neoPixel_8.setBrightness(255);
mind_n_N1 = 0;
mind_n_N2 = 0;
huskylens.clearOSD();
delay(1000);
huskylens.writeOSD(String("system is ok"), 84, 35);
delay(1000);
}
void loop() {
huskylens.request();
DF_DingWeiShiBie();
DF_PanDuanJieGuo();
}
// 自定义函数
void DF_DingWeiShiBie() {
mind_n_N1 = (String(huskylens.readBlockParameterDirect(1).xCenter).toInt());
mind_n_N2 = (String(huskylens.readBlockParameterDirect(2).xCenter).toInt());
DF_N1XiaoYuN2();
DF_N1DaYuN2();
}
void DF_CeLiangZuoBiao() {
huskylens.writeOSD(String((String((String(mind_n_N1) + String("===="))) + String(mind_n_N2))), 103, 21);
delay(1000);
}
void DF_N1DaYuN2() {
if (((mind_n_N2 - mind_n_N1)<0)) {
huskylens.clearOSD();
mind_n_N1 = (String(huskylens.readBlockParameterDirect(2).ID).toInt());
mind_n_N2 = (String(huskylens.readBlockParameterDirect(1).ID).toInt());
huskylens.writeOSD(String((String((String(mind_n_N1) + String("-------"))) + String(mind_n_N2))), 20, 130);
delay(500);
}
}
void DF_N1XiaoYuN2() {
if (((mind_n_N1 - mind_n_N2)<0)) {
huskylens.clearOSD();
mind_n_N1 = (String(huskylens.readBlockParameterDirect(1).ID).toInt());
mind_n_N2 = (String(huskylens.readBlockParameterDirect(2).ID).toInt());
huskylens.writeOSD(String((String((String(mind_n_N1) + String("-------"))) + String(mind_n_N2))), 20, 130);
delay(500);
}
}
void DF_PanDuanJieGuo() {
if ((((2<mind_n_N1) && (mind_n_N1<41)) && ((5<mind_n_N2) && (mind_n_N2<41)))) {
DF_XiangDeng();
DF_LXiaoYuR();
DF_LDaYuR();
}
DF_GongBingChaiLei();
DF_DiLeiChuLi();
DF_ZhaDanChuLi();
DF_KangQi();
}
void DF_MingChen() {
if ((mind_n_N1==1)) {
mind_s_M1 = "chess:";
}
if ((mind_n_N2==2)) {
mind_s_M1 = "chess:";
}
if (((3<=mind_n_N1) && (mind_n_N1<=5))) {
mind_s_M1 = "engineer:";
}
if (((6<=mind_n_N2) && (mind_n_N2<=8))) {
mind_s_M2 = "engineer:";
}
if (((9<=mind_n_N1) && (mind_n_N1<=11))) {
mind_s_M1 = "platoon:";
}
if (((12<=mind_n_N2) && (mind_n_N2<=14))) {
mind_s_M2 = "platoon:";
}
if (((15<=mind_n_N1) && (mind_n_N1<=17))) {
mind_s_M1 = "Company:";
}
if (((18<=mind_n_N2) && (mind_n_N2<=20))) {
mind_s_M2 = "Company:";
}
if (((21<=mind_n_N1) && (mind_n_N1<=22))) {
mind_s_M1 = "battalion :";
}
if (((23<=mind_n_N2) && (mind_n_N2<=24))) {
mind_s_M2 = "battalion:";
}
if (((25<=mind_n_N1) && (mind_n_N1<=26))) {
mind_s_M1 = "regimental ";
}
if (((27<=mind_n_N2) && (mind_n_N2<=28))) {
mind_s_M2 = "regimental: ";
}
if (((29<=mind_n_N1) && (mind_n_N1<=30))) {
mind_s_M1 = "brigade: ";
}
if (((31<=mind_n_N2) && (mind_n_N2<=32))) {
mind_s_M2 = "brigade: ";
}
if (((33<=mind_n_N1) && (mind_n_N1<=34))) {
mind_s_M1 = "teacher:";
}
if (((35<=mind_n_N2) && (mind_n_N2<=36))) {
mind_s_M2 = "teacher:";
}
if ((mind_n_N1==37)) {
mind_s_M1 = "army:";
}
if ((mind_n_N2==38)) {
mind_s_M2 = "army:";
}
if ((mind_n_N1==39)) {
mind_s_M1 = "commander:";
}
if ((mind_n_N2==40)) {
mind_s_M2 = "commander:";
}
if (((41<=mind_n_N1) && (mind_n_N1<=43))) {
mind_s_M1 = "mine:";
}
if (((44<=mind_n_N2) && (mind_n_N2<=46))) {
mind_s_M2 = "mine:";
}
if (((47<=mind_n_N1) && (mind_n_N1<=48))) {
mind_s_M1 = "bomb:";
}
if (((49<=mind_n_N2) && (mind_n_N2<=50))) {
mind_s_M2 = "bomb:";
}
}
void DF_DiLeiChuLi() {
if ((((9<=mind_n_N1) && (mind_n_N1<=39)) && ((44<=mind_n_N2) && (mind_n_N2<=46)))) {
huskylens.writeOSD(String("boom_RD"), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if ((((41<=mind_n_N1) && (mind_n_N1<=43)) && ((12<=mind_n_N2) && (mind_n_N2<=40)))) {
huskylens.writeOSD(String("boom_LD"), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if ((((41<=mind_n_N1) && (mind_n_N1<=43)) && ((44<=mind_n_N2) && (mind_n_N2<=46)))) {
huskylens.writeOSD(String("boom_LRD"), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
}
void DF_TongGuiYuJin() {
neoPixel_7.setRangeColor(0, 6, 0xFF0000);
neoPixel_7.setRangeColor(0, 6, 0xFF0000);
DFTone.play(4, 131, 500);
delay(1000);
neoPixel_7.clear();
neoPixel_8.clear();
}
void DF_ZuoBianSheng() {
neoPixel_7.setRangeColor(0, 6, 0x00FF00);
neoPixel_7.setRangeColor(0, 6, 0xFF0000);
DFTone.play(4, 196, 500);
delay(1000);
neoPixel_7.clear();
neoPixel_8.clear();
}
void DF_ZhaDanChuLi() {
if ((((47<=mind_n_N1) && (mind_n_N1<=48)) && ((6<=mind_n_N2) && (mind_n_N2<=46)))) {
huskylens.writeOSD(String("boom-LZ"), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if ((((3<=mind_n_N1) && (mind_n_N1<=46)) && ((49<=mind_n_N2) && (mind_n_N2<=50)))) {
huskylens.writeOSD(String("boom_RZ"), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if ((((47<=mind_n_N1) && (mind_n_N1<=48)) && ((49<=mind_n_N2) && (mind_n_N2<=50)))) {
huskylens.writeOSD(String("boom_LRZ"), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
}
void DF_YouBianSheng() {
neoPixel_7.setRangeColor(0, 6, 0xFF0000);
neoPixel_7.setRangeColor(0, 6, 0x00FF00);
DFTone.play(4, 262, 500);
delay(1000);
neoPixel_7.clear();
neoPixel_8.clear();
}
void DF_GongBingChaiLei() {
if ((((3<=mind_n_N1) && (mind_n_N1<=5)) && ((44<=mind_n_N2) && (mind_n_N2<=46)))) {
huskylens.writeOSD(String("L-CHAI"), 5, 10);
delay(1000);
DF_ZuoBianSheng();
}
if ((((41<=mind_n_N1) && (mind_n_N1<=43)) && ((6<=mind_n_N2) && (mind_n_N2<=8)))) {
huskylens.writeOSD(String("R_CHAI"), 5, 10);
delay(1000);
DF_YouBianSheng();
}
}
void DF_KangQi() {
if (((1==mind_n_N1) && ((6<=mind_n_N2) && (mind_n_N2<=40)))) {
huskylens.writeOSD(String("R-winner"), 5, 10);
delay(1000);
DF_YouBianSheng();
}
if (((2==mind_n_N2) && ((3<=mind_n_N1) && (mind_n_N2<=40)))) {
huskylens.writeOSD(String("L-winner"), 5, 10);
delay(1000);
DF_ZuoBianSheng();
}
}
void DF_XiangDeng() {
if ((((3<=mind_n_N1) && (mind_n_N1<=5)) && ((6<=mind_n_N2) && (mind_n_N2<=8)))) {
huskylens.writeOSD(String("====="), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if ((((9<=mind_n_N1) && (mind_n_N1<=11)) && ((12<=mind_n_N2) && (mind_n_N2<=14)))) {
huskylens.writeOSD(String("====="), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if ((((15<=mind_n_N1) && (mind_n_N1<=17)) && ((18<=mind_n_N2) && (mind_n_N2<=20)))) {
huskylens.writeOSD(String("====="), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if ((((mind_n_N1==21) || (mind_n_N1==22)) && ((mind_n_N2==23) || (mind_n_N2==24)))) {
huskylens.writeOSD(String("====="), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if ((((mind_n_N1==25) || (mind_n_N1==26)) && ((mind_n_N2==27) || (mind_n_N2==28)))) {
huskylens.writeOSD(String("====="), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if ((((mind_n_N1==29) || (mind_n_N1==30)) && ((mind_n_N2==31) || (mind_n_N2==32)))) {
huskylens.writeOSD(String("====="), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if ((((mind_n_N1==33) || (mind_n_N1==34)) && ((mind_n_N2==35) || (mind_n_N2==36)))) {
huskylens.writeOSD(String("====="), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if (((mind_n_N1==37) && (mind_n_N2==38))) {
huskylens.writeOSD(String("====="), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
if (((mind_n_N1==39) && (mind_n_N2==40))) {
huskylens.writeOSD(String("====="), 5, 10);
delay(1000);
DF_TongGuiYuJin();
}
}
void DF_LXiaoYuR() {
if ((((3<=mind_n_N1) && (mind_n_N1<=5)) && ((12<=mind_n_N2) && (mind_n_N2<=40)))) {
huskylens.writeOSD(String("R"), 5, 10);
delay(1000);
DF_YouBianSheng();
}
if ((((9<=mind_n_N1) && (mind_n_N1<=11)) && ((18<=mind_n_N2) && (mind_n_N2<=40)))) {
huskylens.writeOSD(String("R"), 5, 10);
delay(1000);
DF_ZuoBianSheng();
}
if ((((15<=mind_n_N1) && (mind_n_N1<=17)) && ((23<=mind_n_N2) && (mind_n_N2<=40)))) {
huskylens.writeOSD(String("R"), 5, 10);
delay(1000);
DF_YouBianSheng();
}
if ((((21<=mind_n_N1) && (mind_n_N1<=22)) && ((27<=mind_n_N2) && (mind_n_N2<=40)))) {
huskylens.writeOSD(String("R"), 5, 10);
delay(1000);
DF_YouBianSheng();
}
if ((((25<=mind_n_N1) && (mind_n_N1<=26)) && ((31<=mind_n_N2) && (mind_n_N2<=40)))) {
huskylens.writeOSD(String("R"), 5, 10);
delay(1000);
DF_YouBianSheng();
}
if ((((29<=mind_n_N1) && (mind_n_N1<=30)) && ((35<=mind_n_N2) && (mind_n_N2<=40)))) {
huskylens.writeOSD(String("R"), 5, 10);
delay(1000);
DF_YouBianSheng();
}
if ((((33<=mind_n_N1) && (mind_n_N1<=34)) && ((38<=mind_n_N2) && (mind_n_N2<=40)))) {
huskylens.writeOSD(String("R"), 5, 10);
delay(1000);
DF_YouBianSheng();
}
if (((mind_n_N1==37) && (mind_n_N2==40))) {
huskylens.writeOSD(String("R"), 5, 10);
delay(1000);
DF_YouBianSheng();
}
}
void DF_LDaYuR() {
if ((((9<=mind_n_N1) && (mind_n_N1<=11)) && ((6<=mind_n_N2) && (mind_n_N2<=8)))) {
huskylens.writeOSD(String("L"), 5, 10);
delay(1000);
DF_ZuoBianSheng();
}
if ((((15<=mind_n_N1) && (mind_n_N1<=17)) && ((6<=mind_n_N2) && (mind_n_N2<=14)))) {
huskylens.writeOSD(String("L"), 5, 10);
delay(1000);
DF_ZuoBianSheng();
}
if ((((21<=mind_n_N1) && (mind_n_N1<=22)) && ((6<=mind_n_N2) && (mind_n_N2<=20)))) {
huskylens.writeOSD(String("L"), 5, 10);
delay(1000);
DF_ZuoBianSheng();
}
if ((((25<=mind_n_N1) && (mind_n_N1<=26)) && ((6<=mind_n_N2) && (mind_n_N2<=24)))) {
huskylens.writeOSD(String("L"), 5, 10);
delay(1000);
DF_ZuoBianSheng();
}
if ((((29<=mind_n_N1) && (mind_n_N1<=30)) && ((6<=mind_n_N2) && (mind_n_N2<=28)))) {
huskylens.writeOSD(String("L"), 5, 10);
delay(1000);
DF_ZuoBianSheng();
}
if ((((33<=mind_n_N1) && (mind_n_N1<=34)) && ((6<=mind_n_N2) && (mind_n_N2<=32)))) {
huskylens.writeOSD(String("L"), 5, 10);
delay(1000);
DF_ZuoBianSheng();
}
if (((mind_n_N1==37) && ((6<=mind_n_N2) && (mind_n_N2<=36)))) {
huskylens.writeOSD(String("L"), 5, 10);
delay(1000);
DF_ZuoBianSheng();
}
if (((mind_n_N1==39) && ((6<=mind_n_N2) && (mind_n_N2<=38)))) {
huskylens.writeOSD(String("L"), 5, 10);
delay(1000);
DF_ZuoBianSheng();
}
}
步骤5 设备组装
3D打印棋子外壳结构件如下图25-27所示,取出 3颗强磁铁,将其安装在3个孔位。
取出切割好的棋子表面,将其安装在棋子外壳上,如下图28所示
3D打印出擂台结构件,并拿出6颗强磁铁安装在对应的孔位上,如图30所示。具体安装磁铁的级性要与蓝橙色棋子匹配。例如,一侧擂台外露为N极,则对应棋子外露为S极;另一侧擂台外露为S极,则对应棋子外露为N极。安装后以后,使用棋子测试。
为Huskylens加装支架和铜柱,如下图32所示。
将Huskylens、蜂鸣器、擂台固定在棋盘上,如下图33所示。
用热熔胶将LED灯带安装固定到指示灯区域,如下图34所示。
将上步中的棋盘于底板侧板组合安装,主控固定在底板对应孔位,从而完成安装如图35所示。另外使用热熔胶将棋子收纳盒,粘贴完成如下图36所示。
启动MaixPy.ide编程软件,选择“工具”——“机器视觉”——“AprilTag生成器——TAG36H11”生成相应的标签如图37所示。
从生成标签中选取其中的50个标签,用热敏打印机打印出来,如下图38所示。
第十步:分配标签,以下表为依据将生成的标签依次分配给蓝方和橙方对应棋子。并将打印出的标签粘贴在棋子的顶部,如下图39所示。
步骤6 系统测试
在测试前,先要完成对50个棋子标签的学习,嗯,这是个力气活,而且需要有耐心!具体操作这里不再赘述。系统加电对其进行测试,分别军衔PK(大于、等于、小于)、工兵拆雷、炸弹处理、地雷处理、工兵扛旗的所有情况进行测试,验证其结果的有效性。
步骤7 改进与提升
本次制作过程中,是使用二维码进行识别,前期二维码制作和学习比较繁琐,但识别效果还是比较理想。另外蜂鸣器的位置挡住了线路,后续可以修改。
TomcatZH2021.05.22
一个小建议,标签在棋的底部是不是更合理些
没有毕业的菜狗2021.04.18
棒