当前位置:   article > 正文

React - 连连看小游戏

React - 连连看小游戏

简介

        小时候经常玩连连看小游戏。在游戏中,当找到2个相同的元素就可以消除元素。

        本文会借助react实现连连看小游戏。

实现效果

实现难点

 1.item 生成

   1. 每一个图片都是一个item,items数组的大小为size*size。

       item对象包括grid布局的位置,key。

       key是标识符,可以标识图片, 相等判断等。

  2. items 可以先顺序生成,最后再调用shuffle算法随机排序。

  1. const size = 8; // 大小为 8 * 8
  2. const itemImgSize = 20; // 圖片素材大小
  3. const [items, setItems] = useState([]);
  4. useEffect(() => { // 初始化元素
  5. const initItems = [];
  6. let idx = 0;
  7. while (initItems.length < size * size) {
  8. // 一次插入2個
  9. initItems.push({
  10. key: (idx % itemImgSize) + 1,
  11. x: parseInt(idx / size),
  12. y: parseInt(idx % size)
  13. });
  14. initItems.push({
  15. key: (( idx)% itemImgSize) + 1,
  16. x: parseInt(( idx + 1) / size),
  17. y: parseInt((idx + 1)% size)
  18. });
  19. idx = idx + 1;
  20. }
  21. const nArr = [...shuffleArray(initItems)];
  22. setItems(nArr);
  23. }, [])
  24. function shuffleArray(arr) {
  25. for (let i = arr.length - 1; i >= arr.length / 2; i--) {
  26. const j = Math.floor(Math.random() * (size - 1));
  27. [arr[i], arr[j]] = [arr[j], arr[i]];
  28. if (arr[i] instanceof Array) {
  29. shuffleArray(arr[i]);
  30. shuffleArray(arr[j])
  31. } else {
  32. // 交换key
  33. let key = arr[i].key;
  34. arr[i].key = arr[j].key;
  35. arr[j].key = key;
  36. }
  37. }
  38. return arr;
  39. }

1. 判断选择的2个item可以消除

      基于dfs算法实现,以其中一方为原点,另一方为终点。找到一条成功的路径。

tips: dfs 可以改成bfs, dfs  当item 消除大半后,会变慢。

  1. /**
  2. * 判断 (i,j) 与(x,y)是否可达
  3. */
  4. function dfs(i, j, visited, x, y) {
  5. if (res.current === true) {
  6. return;
  7. }
  8. if (i < 0 || i >= size + 2 || j < 0 || j >= size + 2) { // 边界
  9. return;
  10. }
  11. if (i === x && j === y) {
  12. res.current = true;
  13. return;
  14. }
  15. if (visited[i][j] === 1) {
  16. return;
  17. }
  18. if (board.current[i][j] === 1) { // 只能走空白
  19. return;
  20. }
  21. visited[i][j] = 1;
  22. dfs(i - 1, j, visited, x, y);
  23. dfs(i + 1, j, visited, x, y);
  24. dfs(i, j + 1, visited, x, y);
  25. dfs(i, j - 1, visited, x, y);
  26. visited[i][j] = 0;
  27. }

  boards标记数组是根据items数组生成,若item存在,则boards对应标记为1,反之为null。

  item 对应位置为(item.x+1,item.y+1)

 boards 的大小为(size + 2) * (size + 2) ,  + 2是为了解决边界上的2点相连处理。

  1. // 二维int数组,标记是否存在元素 (size + 2) * (size + 2), +1是为了边界可以连接
  2. const board = useRef([]);
  3. useEffect(() => {
  4. const nBoard = new Array(size + 2);
  5. // init
  6. for (let i = 0; i < size + 2; i++) {
  7. nBoard[i] = new Array(size + 2);
  8. for (let j = 0; j< size + 2;j++){
  9. nBoard[i][j] = 0;
  10. }
  11. }
  12. //根据items设置boards
  13. items.map((item) => {
  14. nBoard[item.x + 1][item.y + 1] = 1;
  15. })
  16. board.current = (nBoard)
  17. }, [items]);
整体代码
  1. import bgImg from './imgs/bg.png'
  2. import {useEffect, useRef, useState} from "react";
  3. export const LinkGame = () => {
  4. const size = 8; // 大小为 8 * 8
  5. const itemImgSize = 20; // 圖片素材大小
  6. const [items, setItems] = useState([]);
  7. useEffect(() => { // 初始化元素
  8. const initItems = [];
  9. let idx = 0;
  10. while (initItems.length < size * size) {
  11. // 一次插入2個
  12. initItems.push({
  13. key: (idx % itemImgSize) + 1,
  14. x: parseInt(idx / size),
  15. y: parseInt(idx % size)
  16. });
  17. initItems.push({
  18. key: (idx % itemImgSize) + 1,
  19. x: parseInt((idx + 1)/ size),
  20. y: parseInt((idx + 1) % size)
  21. });
  22. idx = idx + 2;
  23. }
  24. const nArr = [...shuffleArray(initItems)];
  25. setItems(nArr);
  26. }, [])
  27. function shuffleArray(arr) {
  28. for (let i = arr.length - 1; i >= arr.length / 2; i--) {
  29. const j = Math.floor(Math.random() * (size - 1));
  30. [arr[i], arr[j]] = [arr[j], arr[i]];
  31. if (arr[i] instanceof Array) {
  32. shuffleArray(arr[i]);
  33. shuffleArray(arr[j])
  34. } else {
  35. // 交换key
  36. let key = arr[i].key;
  37. arr[i].key = arr[j].key;
  38. arr[j].key = key;
  39. }
  40. }
  41. return arr;
  42. }
  43. // 二维int数组,标记是否存在元素 (size + 2) * (size + 2), +1是为了边界可以连接
  44. const board = useRef([]);
  45. useEffect(() => {
  46. const nBoard = new Array(size + 2);
  47. // init
  48. for (let i = 0; i < size + 2; i++) {
  49. nBoard[i] = new Array(size + 2);
  50. for (let j = 0; j< size + 2;j++){
  51. nBoard[i][j] = 0;
  52. }
  53. }
  54. //根据items设置boards
  55. items.map((item) => {
  56. nBoard[item.x + 1][item.y + 1] = 1;
  57. })
  58. board.current = (nBoard)
  59. }, [items]);
  60. // 当选择2个时候,判断是否能消除,如果能消除,则消除,不能则复原。
  61. const res = useRef(false);
  62. useEffect(() => {
  63. const checkedList = [];
  64. items.map(item => {
  65. if (item.checked) {
  66. checkedList.push(item);
  67. }
  68. if (checkedList.length === 2) {
  69. const a = checkedList[0];
  70. const b = checkedList[1];
  71. if (a.key !== b.key) {
  72. a.checked = false;
  73. b.checked = false;
  74. setItems([...items])
  75. } else {
  76. // 判断 a 和 b 直接是否能连接
  77. const visited = new Array(size + 2);
  78. for (let i = 0; i < size + 2; i++) {
  79. visited[i] = new Array(size + 2);
  80. }
  81. const i = a.x + 1;
  82. const j = a.y + 1;
  83. const x = b.x + 1;
  84. const y = b.y + 1;
  85. dfs(i + 1, j, visited, x, y)
  86. dfs(i - 1, j, visited, x, y)
  87. dfs(i, j + 1, visited, x, y)
  88. dfs(i, j - 1, visited, x, y)
  89. if (res.current === true) { // 存在线路相连
  90. // 移除 a 和 b
  91. const nItems = [];
  92. items.map((item) => {
  93. if (item !== a && item !== b) {
  94. nItems.push(item);
  95. }
  96. })
  97. setItems(nItems)
  98. res.current = false; //init
  99. } else {
  100. a.checked = false;
  101. b.checked = false;
  102. setItems([...items])
  103. }
  104. }
  105. }
  106. })
  107. }, [items]);
  108. /**
  109. * 判断 (i,j) 与(x,y)是否可达
  110. */
  111. function dfs(i, j, visited, x, y) {
  112. if (res.current === true) {
  113. return;
  114. }
  115. if (i < 0 || i >= size + 2 || j < 0 || j >= size + 2) { // 边界
  116. return;
  117. }
  118. if (i === x && j === y) {
  119. res.current = true;
  120. return;
  121. }
  122. if (visited[i][j] === 1) {
  123. return;
  124. }
  125. if (board.current[i][j] === 1) { // 只能走空白
  126. return;
  127. }
  128. visited[i][j] = 1;
  129. dfs(i - 1, j, visited, x, y);
  130. dfs(i + 1, j, visited, x, y);
  131. dfs(i, j + 1, visited, x, y);
  132. dfs(i, j - 1, visited, x, y);
  133. visited[i][j] = 0;
  134. }
  135. function onItemClick(item) {
  136. item.checked = !item.checked;
  137. setItems([...items]);
  138. }
  139. const gameBoardStyle = { // 游戏区域样式
  140. display: 'grid',
  141. gridTemplateColumns: `repeat(${size}, 1fr)`,
  142. gridTemplateRows: `repeat(${size}, 1fr)`,
  143. width: '60vw',
  144. height: '80vh',
  145. backgroundImage: 'url(' + bgImg + ')',
  146. backgroundSize: 'cover'
  147. };
  148. const gameBoardItemStyle = (item) => {
  149. if (item.checked) {
  150. return ({
  151. gridRowStart: item.x + 1,
  152. gridColumnStart: item.y + 1,
  153. opacity: 0.4
  154. })
  155. }
  156. return ({
  157. gridRowStart: item.x + 1,
  158. gridColumnStart: item.y + 1,
  159. });
  160. }
  161. return <>
  162. <div id={'link-game'}>
  163. <div style={gameBoardStyle}>
  164. {
  165. items.map((item, idx) => (
  166. <div style={gameBoardItemStyle(item)}
  167. onClick={() => onItemClick(item)} key={'item-' + idx}>
  168. <img src={require(`./imgs/${item.key}.png`)}/>
  169. </div>
  170. ))
  171. }
  172. </div>
  173. </div>
  174. </>
  175. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/369856
推荐阅读
相关标签
  

闽ICP备14008679号