一、背景

近一个月没写 Javascript 代码,有点生疏。正好浏览网页时弹出五子棋的游戏广告,于是想通过编写这个小游戏练练手。

二、简单介绍

# 2.1 效果展示

# 2.2 实现思路

  1. 棋盘:通过图片(chessboard.png)和 div 标签渲染出棋盘。

  2. 棋子:通过图片(black_flag.png、white_flag.png等)渲染出黑白棋子。落子前,鼠标出会出现一个可以随鼠标移动的棋子。我们创建一个浮动的 div,动态设置其 top 和 left 。

  3. 落子:给容器(class="container")添加 click 事件,给其添加对应的 classname。即被点击的单元格设置棋子背景图片。此外,需要判断落子点是否存在棋子。

  4. 输赢:使用二维数组保存棋盘(棋子)状态,通过横向、纵向、左上到右下和右上到左下四个方向进行判断是否有 5 个以上连续同颜色(样式)的棋子。

# 2.3 涉及技术

DOM操作、面向对象、事件操作和间隔函数 setInterval

# 2.4 项目结构

三、实现步骤

# 3.1 绘制棋盘

style.css 内容:

  1. html,body {
  2. padding: 0;
  3. margin: 0;
  4. }
  5. .container {
  6. position: relative;
  7. width: 540px;
  8. height: 540px;
  9. margin: 10px auto;
  10. padding-top: 7px;
  11. padding-left: 7px;
  12. background: url("../images/chessboard.png") no-repeat;
  13. cursor: pointer;
  14. }
  15. .none {
  16. position: absolute;
  17. width: 36px;
  18. height: 36px;
  19. box-sizing: border-box;
  20. /*border: 1px solid #fff;*/
  21. }
  22. .black_flag {
  23. position: absolute;
  24. width: 36px;
  25. height: 36px;
  26. background: url("../images/black_flag.png") no-repeat;
  27. }
  28. .black_flag_cur {
  29. position: absolute;
  30. background: url("../images/black_flag_cur.png") no-repeat;
  31. /*设置点击无效*/
  32. pointer-events: none;
  33. }
  34. .white_flag {
  35. position: absolute;
  36. width: 36px;
  37. height: 36px;
  38. background: url("../images/white_flag.png") no-repeat;
  39. }
  40. .white_flag_cur {
  41. position: absolute;
  42. background: url("../images/white_flag_cur.png") no-repeat;
  43. /*设置点击无效*/
  44. pointer-events: none;
  45. }

chessboard.js 代码:

  1. var Chessboard = function() {
  2. // 保存棋盘棋子状态
  3. this.flagArr = [];
  4. this.size = 36;
  5. }
  6. // 初始化棋盘
  7. Chessboard.prototype.init = function() {
  8. var container = document.getElementById("container");
  9. for (var i = 0; i < 15; i++) {
  10. var arr = [];
  11. for (var j = 0; j < 15; j++) {
  12. var div = document.createElement("div");
  13. div.className = "none";
  14. div.style.top = (i * this.size) + "px";
  15. div.style.left = (j * this.size) + "px";
  16. container.appendChild(div);
  17. arr.push(div);
  18. }
  19. this.flagArr.push(arr);
  20. }
  21. }

game.js 代码:

  1. var Game = function() {
  2. }
  3. Game.prototype.start = function() {
  4. var chessboard = new Chessboard();
  5. chessboard.init();
  6. }

最终效果如下:

为了方便查看 div 与棋盘图片中格子之间的对应关系,笔者将 div 边框设置成白色。

从图中我们可以看到,div 大小正好对应棋盘的落子点。我们将 div 背景设置成棋子图片就实现了落子操作。

# 3.2 绘制棋子

chessboard.js 代码:

  1. var Chessboard = function() {
  2. // 保存棋盘棋子状态
  3. this.flagArr = [];
  4. this.size = 36;
  5. // 默认黑色为先手
  6. this.currentFlag = true;
  7. // 保存落子前的样式映射
  8. this.flagCurMap = [];
  9. // 黑子
  10. this.flagCurMap[true] = "black_flag_cur";
  11. // 白子
  12. this.flagCurMap[false] = "white_flag_cur";
  13. }
  14. // 初始化棋盘
  15. Chessboard.prototype.init = function() {
  16. var container = document.getElementById("container");
  17. for (var i = 0; i < 15; i++) {
  18. var arr = [];
  19. for (var j = 0; j < 15; j++) {
  20. var div = document.createElement("div");
  21. div.className = "none";
  22. div.style.top = (i * this.size) + "px";
  23. div.style.left = (j * this.size) + "px";
  24. container.appendChild(div);
  25. arr.push(div);
  26. }
  27. this.flagArr.push(arr);
  28. }
  29. // 添加事件监听器
  30. this.addListener(container);
  31. }
  32. // 落子事件监听器
  33. Chessboard.prototype.addListener = function() {
  34. var that = this;
  35. // 设置落子前的鼠标样式
  36. var mouse = document.createElement("div");
  37. mouse.id = "mouse";
  38. mouse.style.width = mouse.style.height = 36 + "px";
  39. document.body.appendChild(mouse);
  40. document.body.onmousemove = function(event) {
  41. mouse.className = that.flagCurMap[that.currentFlag];
  42. var x = event.clientX - 16;
  43. var y = event.clientY - 16;
  44. mouse.style.top = y + "px";
  45. mouse.style.left = x + "px";
  46. }
  47. }

结果如下图:

# 3.3 落子

在 chessboard.js 的监听器方法中添加落子的点击事件:

  1. var Chessboard = function() {
  2. // 保存棋盘棋子状态
  3. this.flagArr = [];
  4. this.size = 36;
  5. // 默认黑色为先手
  6. this.currentFlag = true;
  7. // 保存落子前的样式映射
  8. this.flagCurMap = [];
  9. // 黑子
  10. this.flagCurMap[true] = "black_flag_cur";
  11. // 白子
  12. this.flagCurMap[false] = "white_flag_cur";
  13. // 保存落子后的样式映射
  14. this.flagMap = [];
  15. // 黑子
  16. this.flagMap[true] = "black_flag";
  17. // 白子
  18. this.flagMap[false] = "white_flag";
  19. // 保存结果映射关系
  20. this.resultMap = [];
  21. this.resultMap[true] = "黑子胜利";
  22. this.resultMap[false] = "白子胜利";
  23. }
  24. // 初始化棋盘
  25. Chessboard.prototype.init = function() {
  26. var container = document.getElementById("container");
  27. for (var i = 0; i < 15; i++) {
  28. var arr = [];
  29. for (var j = 0; j < 15; j++) {
  30. var div = document.createElement("div");
  31. div.className = "none";
  32. div.style.top = (i * this.size) + "px";
  33. div.style.left = (j * this.size) + "px";
  34. container.appendChild(div);
  35. arr.push(div);
  36. }
  37. this.flagArr.push(arr);
  38. }
  39. // 添加事件监听器
  40. this.addListener(container);
  41. }
  42. // 落子事件监听器
  43. Chessboard.prototype.addListener = function(container) {
  44. var that = this;
  45. // 设置落子前的鼠标样式
  46. var mouse = document.createElement("div");
  47. mouse.id = "mouse";
  48. mouse.style.width = mouse.style.height = 36 + "px";
  49. document.body.appendChild(mouse);
  50. document.body.onmousemove = function(event) {
  51. mouse.className = that.flagCurMap[that.currentFlag];
  52. var x = event.clientX - 16;
  53. var y = event.clientY - 16;
  54. mouse.style.top = y + "px";
  55. mouse.style.left = x + "px";
  56. }
  57. // 落子监听
  58. container.onclick = function(event) {
  59. // 判断落子点是否存在棋子
  60. if (event.target.className != "none") {
  61. alert("此处不能落子!");
  62. return;
  63. }
  64. // 落子,设置棋子图片
  65. event.target.className = that.flagMap[that.currentFlag];
  66. // 换棋手
  67. that.currentFlag = !that.currentFlag;
  68. }
  69. }

运行结果如下:

# 3.4 判断输赢

在 chessboard.js 的落子监听实践代码中,判断是否五连子:

  1. // 落子事件监听器
  2. Chessboard.prototype.addListener = function(container) {
  3. var that = this;
  4. // 设置落子前的鼠标样式
  5. var mouse = document.createElement("div");
  6. mouse.id = "mouse";
  7. mouse.style.width = mouse.style.height = 36 + "px";
  8. document.body.appendChild(mouse);
  9. document.body.onmousemove = function(event) {
  10. mouse.className = that.flagCurMap[that.currentFlag];
  11. var x = event.clientX - 16;
  12. var y = event.clientY - 16;
  13. mouse.style.top = y + "px";
  14. mouse.style.left = x + "px";
  15. }
  16. // 落子监听
  17. container.onclick = function(event) {
  18. // 判断落子点是否存在棋子
  19. if (event.target.className != "none") {
  20. alert("此处不能落子!");
  21. return;
  22. }
  23. // 落子,设置棋子图片
  24. event.target.className = that.flagMap[that.currentFlag];
  25. // 当前落子坐标
  26. var x = Math.floor(event.target.offsetLeft / that.size);
  27. var y = Math.floor(event.target.offsetTop / that.size);
  28. // 判断是否胜利
  29. if (that._checkSuccess(x, y)) {
  30. document.getElementById("mouse").style.display = "none";
  31. container.onclick = null;
  32. document.body.onmousemove = null;
  33. alert(that.resultMap[that.currentFlag]);
  34. return;
  35. }
  36. // 换棋手
  37. that.currentFlag = !that.currentFlag;
  38. }
  39. }
  40. // 判断棋局
  41. Chessboard.prototype._checkSuccess = function(x, y) {
  42. var result = false;
  43. // 当前落子的样式/颜色
  44. var className = this.flagArr[y][x].className;
  45. // 横向判断
  46. var count = 0;
  47. for (var i = 0; i < 15; i++) {
  48. if (className == this.flagArr[y][i].className) {
  49. count++;
  50. if (count >= 5) {
  51. return true;
  52. }
  53. } else {
  54. count = 0;
  55. }
  56. }
  57. // 纵向判断
  58. for (var j = 0; j < 15; j++) {
  59. if (className == this.flagArr[j][x].className) {
  60. count++;
  61. if (count >= 5) {
  62. return true;
  63. }
  64. } else {
  65. count = 0;
  66. }
  67. }
  68. // 左上到右下判断
  69. var a = y - x;
  70. var index = 0;
  71. if (a > 0) {
  72. for (a; a < 15; a++) {
  73. if (className == this.flagArr[a][index++].className) {
  74. count++;
  75. if (count >= 5) {
  76. return true;
  77. }
  78. } else {
  79. count = 0;
  80. }
  81. }
  82. } else {
  83. a = Math.abs(a);
  84. for (a; a < 15; a++) {
  85. if (className == this.flagArr[index++][a].className) {
  86. count++;
  87. if (count >= 5) {
  88. return true;
  89. }
  90. } else {
  91. count = 0;
  92. }
  93. }
  94. }
  95. // 右上到左下判断
  96. var b = 14 - y -x;
  97. var index2 = 14;
  98. if (b > 0) {
  99. b = 14 - b;
  100. index2 = 0;
  101. for (b; b >= 0; b--) {
  102. if (className == this.flagArr[index2++][b].className) {
  103. count++;
  104. if (count >= 5) {
  105. return true;
  106. }
  107. } else {
  108. count = 0;
  109. }
  110. }
  111. } else {
  112. b = Math.abs(b);
  113. for (b; b < 15; b++) {
  114. if (className == this.flagArr[index2--][b].className) {
  115. count++;
  116. if (count >= 5) {
  117. return true;
  118. }
  119. } else {
  120. count = 0;
  121. }
  122. }
  123. }
  124. if (count >= 5) {
  125. result = true;
  126. }
  127. return result;
  128. }

演示结果:

剩余的一些文本提示,倒计时就不在此处介绍。具体代码可以在下边提供的链接中下载。

四、源码下载