• 基于html+js编写的生命游戏


    前言

    本文将介绍一个基于html+js的生命游戏,该项目只有一个html代码,无任何其他以来,UI方面采用了vue+element-plus进行渲染,游戏的界面基于canvas进行渲染,先来看一下成果。

    我不知道游戏规则有没有写错,感觉经常会陷入循环中。

    游戏规则

    这边给出文心一言给出的游戏规则

    根据以上规则写的代码如下

    1. function calIter() {
    2. var tmp = new Array(cellHeight).fill(0).map(() => new Array(cellWidth).fill(0));
    3. for(let i=0; i
    4. for(let j=0; j
    5. // 计算周围的细胞数
    6. let num = 0;
    7. if (i-1>=0 && j-1>=0 && cells[i-1][j-1]==1) num++;
    8. if (i-1>=0 && cells[i-1][j]==1) num++;
    9. if (i-1>=0 && cells[i-1][j+1]==1) num++;
    10. if (i+11][j]==1) num++;
    11. if (i+11>=0 && cells[i+1][j-1]==1) num++;
    12. if (i+111][j+1]==1) num++;
    13. if (j-1>=0 && cells[i][j-1]==1) num++;
    14. if (j+11]==1) num++;
    15. if (cells[i][j] == 0 && num >= 3) {
    16. tmp[i][j] = 1;
    17. } else if (num<=1 || num>4){
    18. tmp[i][j] = 0;
    19. }
    20. }
    21. }
    22. for(let i=0; i
    23. for(let j=0; j
    24. cells[i][j] = tmp[i][j];
    25. }
    26. }
    27. rounds++;
    28. }

    代码

    所有的代码都写在了一个html里面,没有任何其他依赖,复制后就能运行,不过需要联网,因为通过cdn的方式引入了vue+element-plus。

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="utf-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <meta name="viewport" content="width=device-width,initial-scale=1.0">
    7. <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/element-plus/2.3.3/index.css" rel="stylesheet">
    8. <script src="https://unpkg.com/vue@3">script>
    9. <script src="https://cdn.bootcdn.net/ajax/libs/element-plus/2.3.3/index.full.js">script>
    10. <title>hello worldtitle>
    11. <style lang="scss">
    12. #app {
    13. font-family: Avenir, Helvetica, Arial, sans-serif;
    14. -webkit-font-smoothing: antialiased;
    15. -moz-osx-font-smoothing: grayscale;
    16. text-align: center;
    17. color: #2c3e50;
    18. margin: 0;
    19. padding: 0;
    20. }
    21. .gameCanvas {
    22. /* background-color: burlywood; */
    23. /* width: 60%; */
    24. height: 650px;
    25. }
    26. .infoDiv {
    27. /* background-color: darkcyan; */
    28. /* width: 40%; */
    29. height: 650px;
    30. }
    31. .el-text {
    32. font-size: middle;
    33. align: left;
    34. }
    35. style>
    36. head>
    37. <body>
    38. <div id="app">
    39. <el-container>
    40. <el-header>
    41. <el-text style="text-align: center;" :size="large">
    42. <h1>生命游戏h1>
    43. el-text>
    44. el-header>
    45. <el-row>
    46. <el-col :span="14" class="gameCanvas">
    47. <canvas id="gameCanvas" width="600" height="600" style="border: gray solid 5px">canvas>
    48. el-col>
    49. <el-col :span="10" class="infoDiv">
    50. <el-descriptions title="数据面板"
    51. :column="1"
    52. :size="large"
    53. :border="true">
    54. <el-descriptions-item label="存活的细胞数:">
    55. <el-text>
    56. <span id="span1">{{numSurvivors}} / {{numAll}}span>
    57. el-text>
    58. el-descriptions-item><br>
    59. <el-descriptions-item label="迭代轮次:">
    60. <el-text>
    61. <span id="span2">{{rounds}}span>
    62. el-text>
    63. el-descriptions-item>
    64. <el-descriptions-item label="当前状态:">
    65. <span id="span3">
    66. <el-text v-if="state==0">未开始迭代el-text>
    67. <el-text v-else-if="state==1">迭代进行中el-text>
    68. <el-text v-else>未知状态el-text>
    69. span>
    70. el-descriptions-item>
    71. <el-descriptions-item label="操作">
    72. <el-button type="primary" onclick="clickInitBtn()">初始化el-button>
    73. <el-button type="info" onclick="clickClearBtn()">清空面板el-button>
    74. <el-button type="success" onclick="clickStartBtn()">开始迭代el-button>
    75. <el-button type="danger" onclick="clickEndBtn()">结束迭代el-button>
    76. el-descriptions-item>
    77. el-descriptions>
    78. el-col>
    79. el-row>
    80. el-container>
    81. div>
    82. body>
    83. html>
    84. <script type="text/javascript">
    85. // 定义格子数以及每个格子的大小
    86. var cellWidth = 30;
    87. var cellHeight = 30;
    88. var cellSize = 20;
    89. // 定义一个二维数组存储每个格子的值
    90. var cells = new Array(cellHeight).fill(0).map(() => new Array(cellWidth).fill(0));
    91. var canvas;
    92. var ctx;
    93. var widht;
    94. var height;
    95. var numSurvivors = 0;
    96. var numAll = cellWidth*cellHeight;
    97. var state = 0;
    98. var rounds = 0;
    99. // 用于启动和停止定时任务
    100. var myInterval;
    101. // 迭代的间隔时间,单位是毫秒
    102. var time = 500;
    103. // 计算存活细胞数
    104. function calNumSurvivors() {
    105. numSurvivors = 0;
    106. for(let i=0; i
    107. for(let j=0; j
    108. if (cells[i][j] == 1) {
    109. numSurvivors++;
    110. }
    111. }
    112. }
    113. }
    114. // 随机初始化细胞
    115. function randomInitLife() {
    116. for(let i=0; i
    117. for(let j=0; j
    118. if (Math.random() < 0.2) {
    119. cells[i][j] = 1;
    120. }
    121. }
    122. }
    123. }
    124. // 清空面板中所有的细胞
    125. function clearAllCells() {
    126. cells = new Array(cellHeight).fill(0).map(() => new Array(cellWidth).fill(0));
    127. }
    128. // 绘制一帧图像
    129. function draw() {
    130. ctx.lineWidth = 2;
    131. ctx.strokeStyle = "gray";
    132. ctx.lineJoin = 'round';
    133. for(let i=0; i
    134. for(let j=0; j
    135. if (cells[i][j] === 1) {
    136. ctx.fillStyle = "LightSalmon";
    137. } else {
    138. ctx.fillStyle = "AliceBlue";
    139. }
    140. ctx.fillRect(i*cellSize, j*cellSize, cellSize, cellSize);
    141. ctx.strokeRect(i*cellSize, j*cellSize, cellSize, cellSize);
    142. }
    143. }
    144. }
    145. // 更新数据面板
    146. function updateInfo() {
    147. var span1 = document.getElementById("span1");
    148. var span2 = document.getElementById("span2");
    149. var span3 = document.getElementById("span3");
    150. span1.innerText = numSurvivors + " / " + numAll;
    151. span2.innerText = rounds;
    152. if (state == 0) {
    153. span3.innerText = "未开始迭代";
    154. } else if (state == 1) {
    155. span3.innerText = "迭代进行中";
    156. } else {
    157. span3.innerText = "未知状态";
    158. }
    159. }
    160. /**
    161. * 每个细胞在每一轮的状态都依赖于其邻居的数量。
    162. * 如果细胞的邻居数量少于一个,那么该细胞在下一次状态将死亡。
    163. * 如果细胞的邻居数量超过四个,那么该细胞在下一次状态将死亡。
    164. * 如果细胞的邻居数量为二或三个,那么该细胞下一次状态将稳定存活。
    165. * 如果某位置原无细胞存活,但该位置的邻居数量为三个,那么该位置将复活一细胞。
    166. *
    167. */
    168. function calIter() {
    169. var tmp = new Array(cellHeight).fill(0).map(() => new Array(cellWidth).fill(0));
    170. for(let i=0; i
    171. for(let j=0; j
    172. // 计算周围的细胞数
    173. let num = 0;
    174. if (i-1>=0 && j-1>=0 && cells[i-1][j-1]==1) num++;
    175. if (i-1>=0 && cells[i-1][j]==1) num++;
    176. if (i-1>=0 && cells[i-1][j+1]==1) num++;
    177. if (i+11][j]==1) num++;
    178. if (i+11>=0 && cells[i+1][j-1]==1) num++;
    179. if (i+111][j+1]==1) num++;
    180. if (j-1>=0 && cells[i][j-1]==1) num++;
    181. if (j+11]==1) num++;
    182. if (cells[i][j] == 0 && num >= 3) {
    183. tmp[i][j] = 1;
    184. } else if (num<=1 || num>4){
    185. tmp[i][j] = 0;
    186. }
    187. }
    188. }
    189. for(let i=0; i
    190. for(let j=0; j
    191. cells[i][j] = tmp[i][j];
    192. }
    193. }
    194. rounds++;
    195. }
    196. function run() {
    197. calIter();
    198. calNumSurvivors();
    199. draw();
    200. updateInfo();
    201. }
    202. function clickInitBtn(event) {
    203. console.log("点击了 [初始化] 按钮");
    204. if (state == 1) {
    205. alert("还在迭代呢!");
    206. }
    207. clearAllCells();
    208. randomInitLife();
    209. calNumSurvivors();
    210. draw();
    211. updateInfo();
    212. }
    213. function clickClearBtn(event) {
    214. console.log("点击了 [清空面板] 按钮");
    215. rounds = 0;
    216. clearAllCells();
    217. calNumSurvivors();
    218. draw();
    219. updateInfo();
    220. }
    221. function clickStartBtn(event) {
    222. console.log("点击了 [开始迭代] 按钮");
    223. if (state == 0) {
    224. state = 1;
    225. }
    226. myInterval = window.setInterval("run()", time);
    227. updateInfo();
    228. }
    229. function clickEndBtn(event) {
    230. console.log("点击了 [结束迭代] 按钮");
    231. if (state == 1) {
    232. state = 0;
    233. }
    234. clearInterval(myInterval);
    235. }
    236. const app = Vue.createApp({
    237. mounted() {
    238. canvas = document.getElementById("gameCanvas");
    239. ctx = canvas.getContext('2d');
    240. width = canvas.width;
    241. height = canvas.height;
    242. draw();
    243. },
    244. data() {
    245. return {
    246. numSurvivors : 0,
    247. numAll : cellWidth*cellHeight,
    248. state : 0,
    249. rounds : 0,
    250. }
    251. },
    252. methods() {
    253. }
    254. }).use(ElementPlus).mount('#app');
    255. console.log("初始化结束")
    256. script>

    思路非常简单,就是首先定义一个棋盘,然后每次迭代都计算一下结果,再将结果绘制在画布中,其中灰色表示死细胞,橙色表示活细胞。

    在写代码的过程中遇到了两个坑:

    1. canvas不能使用css定义大小,否则画出来的图会扭曲

    2. 这段代码中的按钮的点击事件如果写在 Vue.createApp 中的 methods 中的话会调用不到,有知道为什么的小伙伴可以给我留言

    3. 在 Vue.createApp 中的 data 所返回的四个变量都是外部定义的全局变量(用var修饰的),我们在外部更新变量值的时候,页面不会自动渲染,所以我写了一个函数手动进行数据更新,这个原因我也不太懂,有知道的小伙伴可以给我留言,更新数据的代码如下

    1. function updateInfo() {
    2. var span1 = document.getElementById("span1");
    3. var span2 = document.getElementById("span2");
    4. var span3 = document.getElementById("span3");
    5. span1.innerText = numSurvivors + " / " + numAll;
    6. span2.innerText = rounds;
    7. if (state == 0) {
    8. span3.innerText = "未开始迭代";
    9. } else if (state == 1) {
    10. span3.innerText = "迭代进行中";
    11. } else {
    12. span3.innerText = "未知状态";
    13. }
    14. }

    不过我觉得,像这种简单页面的数据渲染,也用不到vue,我们自己写几个dom操作就行,不过为了使用element-plus还是需要引入vue,毕竟我不太会布局。 

    总结

    本次项目可以算是对canvas的简单应用吧,我发现其实可以用canvas做很多东西,甚至可以用来制作一些简单的2D游戏,不过如果要做游戏的话,可能需要自己实现一下逻辑,还是挺复杂的。

  • 相关阅读:
    让org mode导出结果更好看
    AK F.*ing leetcode 流浪计划之半平面求交
    47-用户和权限管理
    数据湖还没玩明白,就别想着湖仓一体了! by 傅一平
    ConsulManager开源项目推荐
    如何限制内网网速
    【C++ techniques】限制某个class所能产生的对象数量
    python异常处理
    圆弧插补【C#】
    es6 正则表达式
  • 原文地址:https://blog.csdn.net/haohulala/article/details/133845041