回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页
best-icon

用Arduino制作智能军棋 简单

头像 hnyzcj 2021.02.19 3120 2

【项目源起】

军棋的玩法通常有三种:明棋、暗棋和翻棋,其中暗棋是一种知己不知彼的玩法,每人使用一种颜色的棋子,立起来,正面朝向自己摆放,布置自己喜欢的阵法。然后按照规定的行棋方式走棋。想要吃棋的时候,并不知道对面是什么棋,也不能看。这时候,需要第三个人来担任裁判。裁判查看双方的棋子,比大小,小的一方被吃,拿出棋盘(拿出棋盘后也不能暴露是什么棋),大的一方留在棋盘上,直到分出胜负。于是我们决定设计制作一套智能军棋,只需两个玩家对战,不仅解脱裁判的尴尬与无聊,更能让玩家专注局势的博弈,不受裁判影响,极大提升玩家体验,如图1-3所示。

project-image
project-image
project-image

【项目展示】

project-image
project-image
project-image
project-image

【功能简介】

(1)智能识别:智能军棋通过Huskylens视觉识别传感器扫描放置于擂台上军棋标签,对双方的棋子进行准确识别。

(2)智能评判:智能军棋通过内置算法判断双方出棋等级高低,从而给出胜负的判定。

(3)声光效果:智能军棋搭配音效搭配LED光效,紧张刺激又烧脑,增加玩家的代入感。

(4)磁力吸附:棋子的底部设计有三枚强磁铁,与擂台上的磁铁相互吸附,避免错误摆放。

材料清单

  • DFRduino UNO R3(含USB线|传感器扩展板) X1 链接
  • HuskyLens AI 视觉传感器 X1 链接
  • 炫彩 WS2812 RGB LED可剪裁黑色灯带 X1 链接
  • 蜂鸣器 X1 链接
  • 5*5mm强磁铁 X200 链接
  • 600*400松木板 X3 链接

步骤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所示。

project-image
project-image

其次依据上步生成盒体宽(内部)的尺寸绘制棋子收纳盒,具体设计图如下图10所示。

project-image

最后绘制出25个棋子的面板,如下图11所示

project-image

(1)棋子结构件设计(3D打印)

我们在Fusion 360中,设计绘制棋子外壳结构和擂台结构如下图12-13所示。

project-image
project-image

步骤3 电路连线

智能军棋系统电路连接如图14所示。两条灯带分别接D7,D8口,蜂鸣器模块接D4口,Huskylens视觉识别传感器通过I2C口与主控Arduino连接。

project-image

步骤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个孔位。

project-image
project-image
project-image

取出切割好的棋子表面,将其安装在棋子外壳上,如下图28所示

project-image
project-image

3D打印出擂台结构件,并拿出6颗强磁铁安装在对应的孔位上,如图30所示。具体安装磁铁的级性要与蓝橙色棋子匹配。例如,一侧擂台外露为N极,则对应棋子外露为S极;另一侧擂台外露为S极,则对应棋子外露为N极。安装后以后,使用棋子测试。

project-image
project-image

为Huskylens加装支架和铜柱,如下图32所示。

project-image

将Huskylens、蜂鸣器、擂台固定在棋盘上,如下图33所示。

project-image

用热熔胶将LED灯带安装固定到指示灯区域,如下图34所示。

project-image

将上步中的棋盘于底板侧板组合安装,主控固定在底板对应孔位,从而完成安装如图35所示。另外使用热熔胶将棋子收纳盒,粘贴完成如下图36所示。

project-image
project-image

启动MaixPy.ide编程软件,选择“工具”——“机器视觉”——“AprilTag生成器——TAG36H11”生成相应的标签如图37所示。

project-image

从生成标签中选取其中的50个标签,用热敏打印机打印出来,如下图38所示。

project-image

第十步:分配标签,以下表为依据将生成的标签依次分配给蓝方和橙方对应棋子。并将打印出的标签粘贴在棋子的顶部,如下图39所示。

project-image

步骤6 系统测试

在测试前,先要完成对50个棋子标签的学习,嗯,这是个力气活,而且需要有耐心!具体操作这里不再赘述。系统加电对其进行测试,分别军衔PK(大于、等于、小于)、工兵拆雷、炸弹处理、地雷处理、工兵扛旗的所有情况进行测试,验证其结果的有效性。

project-image
project-image
project-image
project-image
project-image

步骤7 改进与提升

本次制作过程中,是使用二维码进行识别,前期二维码制作和学习比较繁琐,但识别效果还是比较理想。另外蜂鸣器的位置挡住了线路,后续可以修改。

评论

user-avatar
  • TomcatZH

    TomcatZH2021.05.22

    一个小建议,标签在棋的底部是不是更合理些

    0
    • 没有毕业的菜狗

      没有毕业的菜狗2021.04.18

      0