title: "html+css+JavaScript实现爱恩斯坦棋游戏"
author: Sun-Wind
date: December 30, 2021
背景:本贴将基于前端的语言实现爱恩斯坦棋游戏的实现。
小声BB:查了一下,这应该是全网第一个基于前端语言实现爱恩斯坦棋的贴,应该是开端了。
效果图
考虑到大家可能还不了解爱恩斯坦棋(其实点进来或多或少应该已经有了解过了吧)
理论上还是应该要介绍一下才对
规则如下:
- 棋盘为5×5的方格形棋盘,方格为棋位,左上角为红方出发区;右下角为蓝方出发区;
- 红蓝方各有6枚方块形棋子,分别标有数字1—6。开局时双方棋子在出发区的棋位可以随意摆放;
- 双方轮流掷骰子,然后走动与骰子显示数字相对应的棋子。如果相对应的棋子已从棋盘上移出,便可走动大于或小于此数字的并与此数字最接近的棋子;
- 红方棋子走动方向为向右、向下、向右下,每次走动一格;蓝方棋子走动方向为向左、向上、向左上,每次走动一格;
- 如果在棋子走动的目标棋位上有棋子,则要将该棋子从棋盘上移出(吃掉)。有时吃掉本方棋子也是一种策略,因为可以增加其它棋子走动的机会与灵活性;
- 率先到达对方出发区角点或将对方棋子全部吃掉的一方获胜;
- 对弈结果只有胜负,没有和棋。
- 每盘每方用时3分钟,超时判负;每轮双方对阵最多7盘,轮流先手(甲方一四五盘先手,乙方二三六七盘先手),两盘中间不休息,先胜4盘为胜方。
需要提前掌握的知识
- css选择器
- 绝对位置
- css盒子模型
- 动画效果
- js基本语法
- js中事件
css选择器
一般用于选择元素定义其样式
绝对位置
position 的四个值:static、relative、absolute、fixed
由于只会用到绝对位置,所以我们主要介绍一下absolute属性
absolute:元素会脱离文档流,如果设置偏移量,会影响其他元素的位置定位
absolute定位原理剖析:
1.在父元素没有设置相对定位或绝对定位的情况下,元素相对于根元素定位(即html元素)(是父元素没有)。
2.父元素设置了相对定位或绝对定位,元素会相对于离自己最近的设置了相对或绝对定位的父元素进行定位(或者说离自己最近的不是static的父元素进行定位,因为元素默认是static)。
可以把整个屏幕看成一个坐标系,px是单位长度
比如下面这段代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
h2
{
position:absolute;
left:100px;
top:150px;
}
</style>
</head>
<body>
<h2>这是一个绝对定位了的标题</h2>
<p>用绝对定位,一个元素可以放在页面上的任何位置。标题下面放置距离左边的页面100 px和距离页面的顶部150 px的元素。.</p>
</body>
</html>
效果图如下所示
css盒子模型
所有HTML元素可以看作盒子,在CSS中,"box model"这一术语是用来设计和布局时使用。
CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际内容。
盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。
下面的图片说明了盒子模型(Box Model):
- Margin(外边距) - 清除边框外的区域,外边距是透明的。
- Border(边框) - 围绕在内边距和内容外的边框。
- Padding(内边距) - 清除内容周围的区域,内边距是透明的。
- Content(内容) - 盒子的内容,显示文本和图像。
总元素的宽度=宽度+左填充+右填充+左边框+右边框+左边距+右边距
总元素的高度=高度+顶部填充+底部填充+上边框+下边框+上边距+下边距
比如下面这个块
div {
width: 300px;
border: 25px solid green;
padding: 25px;
margin: 25px;
}
实际上300px (宽)+ 50px (左 + 右填充)+ 50px (左 + 右边框)+ 50px (左 + 右边距)= 450px
动画效果
要创建 CSS3 动画,你需要了解 @keyframes 规则。
@keyframes 规则是创建动画。
@keyframes 规则内指定一个 CSS 样式和动画将逐步从目前的样式更改为新的样式。
如
@keyframes myfirst
{
from {background: red;}
to {background: yellow;}
}
这个动画的意思显而易见是把背景色从红色变成黄色
现在可以给块加上动画了
div
{
animation: myfirst 5s;
}
必须定义动画的名称和动画的持续时间。如果省略的持续时间,动画将无法运行,因为默认值是0。
用百分比来规定变化发生的时间,或用关键词 "from" 和 "to",等同于 0% 和 100%。
0% 是动画的开始,100% 是动画的完成。
为了得到最佳的浏览器支持,我们应该始终定义 0% 和 100% 选择器。
js基本语法
这里只列举本帖子需要用到的一些语法
JavaScript 显示数据
JavaScript 可以通过不同的方式来输出数据:
- 使用 window.alert() 弹出警告框。
- 使用 document.write() 方法将内容写到 HTML 文档中。
- 使用 innerHTML 写入到 HTML 元素。
- 使用 console.log() 写入到浏览器的控制台。
js变量
在 JavaScript 中创建变量通常称为"声明"变量。
我们使用 var 关键词来声明变量:var carname;
变量声明之后,该变量是空的(它没有值)。
如果需要向变量赋值,可以使用等号,也可以在申明的时候给它赋值
JavaScript 拥有动态类型。这意味着相同的变量可用作不同的类型:
var x; // x 为 undefined
var x = 5; // 现在 x 为数字
var x = "John"; // 现在 x 为字符串
JavaScript 只有一种数字类型。数字可以带小数点,也可以不带:
var x1=34.00; //使用小数点来写
var x2=34; //不使用小数点来写
注意在js中的除法是按照小数除法,这一点和c++有区别
语句方面,c++和js几乎一样
js数组
数组下标是基于零的,所以第一个项目是 [0],第二个是 [1],以此类推。
创建数组
var cars=new Array();
cars[0]="Saab";
cars[1]="Volvo";
cars[2]="BMW";
js函数
函数就是包裹在花括号中的代码块,前面使用了关键词 function:
function functionname()
{
// 执行代码
}
当调用该函数时,会执行函数内的代码。
可以在某事件发生时直接调用函数(比如当用户点击按钮时),并且可由 JavaScript 在任何位置进行调用。
可以发送任意多的参数,由逗号 (,) 分隔:
js事件
HTML 事件是发生在 HTML 元素上的事情。
当在 HTML 页面中使用 JavaScript 时, JavaScript 可以触发这些事件。
以下是 HTML 事件的实例:
- HTML 页面完成加载
- HTML input 字段改变时
- HTML 按钮被点击
事件可以用于处理表单验证,用户输入,用户行为及浏览器动作:
- 页面加载时触发事件
- 页面关闭时触发事件
- 用户点击按钮执行动作
- 验证用户输入内容的合法性
等等 ...
设计思路
爱恩斯坦棋的界面设计相对比较简单,主要运用了JavaScript加入div块和规定绝对位置来进行实现,在初始化时对每个块加上id标记,指向这个块的绝对编号(即本身所具有的固有属性,不会随着后面玩家进行布局错乱棋子的位置影响。
同时也为后续进行编号和块号的映射进行了铺垫,在加入div块的同时对这个块设置它的样式(比如绝对位置等等),在外面格外设置一个ans全局变量,对块的编号进行计数。
由于玩家刚开始游戏的时候可以随意地布局,所以我们采用了用点击事件来对各个块编上玩家想要的号码。同样在最外层放置一个计数器cnt,在点击的同时进行计数,同时用querySelector选中这个块,对这个块的样式进行修改(如颜色等等)。
同理对玩家2也是同样的修改。
同样我们也需要让原来的加在每个块上的click事件移除,防止影响到对后续的棋子的移动。(这个移除操作封装到“开始游戏”按钮)。
设置随机数按钮,定义全局变量play,值为1时表示玩家1,值为2时表示玩家2。
分别对对应玩家1和玩家2。每次开始随机的时候,用Math库内置的函数产生一个随机数,然后加入一个div块,把随机数放到这个块上,更改块的样式,动画等。
用户开始游戏后,每次行棋前都要点击随机数,重新申明两个个Array数组player1和player2。
这个数组表示每个编号的棋子是否存活然后。
再利用一个Array数组,这个数组的效果是对应于每个编号所映射的块的绝对位置(块号)。
这样我们能知道所点击块的块号,利用点击来进行移动,对产生的随机数进行搜索,如果离它最近的棋子存活,可以通过mapp数组找到它映射的块号,进而对这个元素添加点击事件,同时搜素它的三种走法,如果这三种走法都是合法的,分别对对应的块添加点击事件。
方便用户的移动,同时可以改变play的值。
在第二次点击事件后,增加一个判断胜利的条件。如果对应块的位置是对面的颜色,或者player数组中没有棋子存活,那么把此作为胜利的条件。
行棋流程图
代码实现
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset="UTF-8">
<title>爱恩斯坦棋</title>
<style>
[class~=big] {
position: absolute;
left: 300px;
top: 200px;
border: 10px black solid;
width: 600px;
height: 600px;
}
[class~=an] {
position: absolute;
left: 1200px;
top: 600px;
width: 100px;
height: 100px;
border-radius: 50%;
}
[class~=bn]{
position: absolute;
left: 1200px;
top: 800px;
width: 200px;
height: 50px;
}
[class~=tip]{
position:absolute;
left:350px;
top:100px;
width:500px;
height:100px;
border:1px black solid;
box-shadow:0 9px 14px 0 rgb(240, 192, 35);
text-align: center;
font-size:50px;
font-weight: normal;
}
[class~=tips]{
position:absolute;
border:1px black solid;
left:1000px;
top:0px;
width:500px;
height:600px;
font-size: 20px;
}
[class~=saizi]{
position:absolute;
border:1px black solid;
width: 100px;
height:100px;
left:1100px;
top:600px;
text-align: center;
background:white;
font-size:60px;
border-radius: 20%;
}
@keyframes button1 {
0%{background-color:rgb(117, 114, 106);box-shadow: 0 0 9px rgb(6, 34, 33);}
100%{background-color: rgb(230, 167, 31);box-shadow: 0 0 9px rgb(6, 34, 33);}
}
@keyframes white_show{
0%{background-color:gray;box-shadow: 0 0 9px rgb(6, 34, 33);}
100%{background-color: white;box-shadow: 0 0 9px rgb(247, 198, 36);}
}
@keyframes blue_show {
0%{background-color:gray;box-shadow: 0 0 9px rgb(6, 34, 33);}
100%{background-color: skyblue;box-shadow: 0 0 9px rgb(235, 175, 48);}
}
/* @keyframes white_none {
0%{background-color:white;}
100%{background-color: gray;}
}
@keyframes blue_none {
0%{background-color: skyblue;}
100%{background-color: gray;}
} */
</style>
</head>
<body bgcolor = "gray">
<div class = "tips">
爱恩斯坦棋:
<p>1.棋盘为5×5的方格形棋盘,方格为棋位,左上角为白方出发区;右下角为蓝方出发区;</p>
<p>2.红蓝方各有6枚方块形棋子,分别标有数字1—6。开局时双方棋子在出发区的棋位可以随意摆放;</p>
<p>3.双方轮流掷骰子,然后走动与骰子显示数字相对应的棋子。如果相对应的棋子已从棋盘上移出,便可走动大于或小于此数字的并与此数字最接近的棋子;</p>
<p>4.白方方棋子走动方向为向右、向下、向右下,每次走动一格;蓝方棋子走动方向为向左、向上、向左上,每次走动一格;</p>
<p>5.如果在棋子走动的目标棋位上有棋子,则要将该棋子从棋盘上移出(吃掉)。</p>
<p>6.率先到达对方出发区角点或将对方棋子全部吃掉的一方获胜;</p>
</div>
<div class = "tip">
</div>
<div class="big">
</div>
<button class="an">随机数</button>
<div class = "saizi"></div>
<button class="bn">开始游戏</button>
<script>
var cnt = 1;//计数器
var ans = 1;//方格数量
var click2 = 0;//表示第一个还没有放完
var ram = 0;//储存随机数
var val;//保存权值
var play = 1;//表示该第几个玩家,
var mapp1 = new Array(7);//由数字映射到编号
var mapp2 = new Array(7);
var player1 = new Array(7);//场上的棋子是否存活
var player2 = new Array(7);
var flag = 0;//标识是否随机完成
var tc, tn; //保存上一个方块的颜色和数字
var sel;//保存上一个次选中的元素
var big = document.querySelector(".big");
var an = document.querySelector(".an");
var bn = document.querySelector(".bn");
var tip = document.querySelector(".tip");
var saizi = document.querySelector(".saizi");
bn.addEventListener("click", clear);
an.addEventListener("click", star);
for (var i = 1; i <= 6; ++i) {
player1[i] = 1;
player2[i] = 1;
}
for (var i = 0; i < 5; ++i)
for (var j = 0; j < 5; ++j) {
div = document.createElement("div");
div.setAttribute("class", "Block" + ans);
div.setAttribute("id", ans);
div.style.border = "2px black solid";
div.style.width = "98px";
div.style.height = "98px";
div.style.position = "absolute";
div.style.textAlign = "center";
div.style.left = 50 + j * 100 + "px";
div.style.top = 50 + i * 100 + "px";
div.style.fontSize = "50px";
big.appendChild(div);
ans++;
}
for (ans = 1; ans <= 11; ++ans) {
var temp = document.querySelector(".Block" + ans);
temp.addEventListener("click", clickblock1);
}
for (cnt = 15; cnt <= 25; ++cnt) {
var temp = document.querySelector(".Block" + cnt);
// console.log(temp);
temp.addEventListener("click", clickblock2);
}
ans = 1;
cnt = 1;//重置方便后续的计数
function clear(){
for (var i = 1; i <= 11; ++i) {
var temp = document.querySelector(".Block" + i);
temp.removeEventListener("click", clickblock1);
}
for (var j = 15; j <= 25; ++j) {
var temp = document.querySelector(".Block" + j);
// console.log(temp);
temp.removeEventListener("click", clickblock2);
}
}
function clickblock1(e) {
e.target.textContent = ans;
mapp1[ans] = e.target.id;
console.log(e.target.id);
ans++;
e.target.style.backgroundColor = "white";
}
function clickblock2(e) {
e.target.textContent = cnt;
mapp2[cnt] = e.target.id;
cnt++;
e.target.style.backgroundColor = "skyblue";
}
function star() {
ram = Math.floor(Math.random() * 6 + 1);
console.log(ram);
// console.log(mapp1);
// for(var i = 1; i <= 25; ++i){
// var id = document.getElementById(i);
// id.addEventListener("click", clickmove);
// }
saizi.textContent = ram;
saizi.style.animation = "button1 2s infinite";
var div = document.createElement("div");
div.style.backgroundColor = "gray";
div.style.width = "170px";
div.style.height = "100px";
div.style.borderRadius = "50px";
div.style.textAlign = "center";
div.textContent = "你只能移动和" + ram + "最接近的数";
div.style.color = "red";
div.style.position = "absolute";
div.style.top = "100px";
div.style.left = "-30px";
an.appendChild(div);
if(play == 1){
tip.textContent = "轮到白方走棋";
tip.style.animation = "white_show 5s infinite";
}
else{
tip.textContent = "轮到蓝方走棋";
tip.style.animation = "blue_show 5s infinite";
}
if (play == 1) {
for (var i = 0; i < 6; ++i) {
if (ram - i >= 1 && player1[ram - i] == 1) {
var id = document.getElementById(mapp1[ram - i]);
id.addEventListener("click", clickmove);
var exp1 = mapp1[ram - i] - 1;
var y = exp1 % 5;
var x = (exp1 - y)/ 5;
console.log("坐标" + x + "" + y);
if (y + 1 < 5) {
exp1 = x * 5 + y + 2;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
if (x + 1 < 5) {
exp1 = (x + 1) * 5 + y + 1;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
if (y + 1 < 5 && x + 1 < 5) {
exp1 = (x + 1) * 5 + y + 2;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
flag = 1;
}
if (ram + i <= 6 && player1[ram + i] == 1 && i != 0) {
var id = document.getElementById(mapp1[ram + i]);
id.addEventListener("click", clickmove);
var exp1 = mapp1[ram + i] - 1;
var y = exp1 % 5;
var x = (exp1 - y)/ 5;
console.log("坐标" + x + "" + y);
if (y + 1 < 5) {
exp1 = x * 5 + y + 2;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
if (x + 1 < 5) {
exp1 = (x + 1) * 5 + y + 1;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
if (y + 1 < 5 && x + 1 < 5) {
exp1 = (x + 1) * 5 + y + 2;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
flag = 1;
}
if (flag) {flag = 0;break;}
}
play = 2;
}
else {
for (var i = 0; i < 6; ++i) {
if (ram - i >= 1 && player2[ram - i] == 1) {
var id = document.getElementById(mapp2[ram - i]);
id.addEventListener("click", clickmove);
var exp1 = mapp2[ram - i] - 1;
var y = exp1 % 5;
var x = (exp1 - y)/ 5;
console.log("坐标" + x + "" + y);
console.log(exp1);
if (y - 1 >= 0) {
exp1 = x * 5 + y;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
if (x - 1 >= 0) {
exp1 = (x - 1) * 5 + y + 1;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
if (y - 1 >= 0 && x - 1 >= 0) {
exp1 = (x - 1) * 5 + y;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
flag = 1;
}
if (ram + i <= 6 && player2[ram + i] == 1 && i != 0) {
var id = document.getElementById(mapp2[ram + i]);
id.addEventListener("click", clickmove);
var exp1 = mapp2[ram + i] - 1;
var y = exp1 % 5;
var x = (exp1 - y)/ 5;
console.log("坐标" + x + "" + y);
if (y -1 >= 0) {
exp1 = x * 5 + y;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
if (x -1 >= 0) {
exp1 = (x - 1) * 5 + y + 1;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
if (y - 1 >= 0 && x - 1 >= 0) {
exp1 = (x - 1) * 5 + y;
console.log(exp1);
var temp = document.getElementById(exp1);
temp.addEventListener("click", clickmove);
}
flag = 1;
}
if (flag) {flag = 0;break;}
}
play = 1;
}
}
function clickmove(e) {
if (click2) {
console.log("第二次点击");
//不改变块的编号
if (e.target.textContent != "") {
if (e.target.style.backgroundColor == 'white') {
player1[e.target.textContent]--;
// mapp1[e.target.textContent] = e.target.id;
}
else {
player2[e.target.textContent]--;
// mapp2[e.target.textContent] = e.target.id;
}
}
e.target.textContent = tn;
e.target.style.backgroundColor = tc;
if(tc == "white"){
mapp1[tn] = e.target.id;
}
if(tc == "skyblue"){
mapp2[tn] = e.target.id;
}
click2 = 0;
console.log(mapp1);
console.log(mapp2);
console.log(player1);
console.log(player2);
//判断胜负
var win1 = document.getElementById("1");
var win2 = document.getElementById("25");
if(win1.style.backgroundColor == "skyblue"){
alert("蓝色方胜利");
location.reload();
}
if(win2.style.backgroundColor == "white"){
alert("白色方胜利");
location.reload();
}
var rw = 1,bw = 1;
for(var i = 1; i <= 6; ++i){
if(player1[i])
{bw = 0;break;}
}
for(var i = 1; i <= 6; ++i){
if(player2[i])
{rw = 0; break;}
}
if(rw) {
alert("白色方胜利");
location.reload();
}
if(bw){
alert("蓝色方胜利");
location.reload();
}
}
else {
console.log("第一次点击");
sel = e.target;
tc = e.target.style.backgroundColor;
tn = e.target.textContent;
e.target.style.backgroundColor = "gray";
e.target.textContent = "";
click2 = 1;
}
}
</script>
</body>
</html>