本文将介绍一个基于html+js的生命游戏,该项目只有一个html代码,无任何其他以来,UI方面采用了vue+element-plus进行渲染,游戏的界面基于canvas进行渲染,先来看一下成果。
我不知道游戏规则有没有写错,感觉经常会陷入循环中。
这边给出文心一言给出的游戏规则
根据以上规则写的代码如下
- function calIter() {
- var tmp = new Array(cellHeight).fill(0).map(() => new Array(cellWidth).fill(0));
- for(let i=0; i
- for(let j=0; j
- // 计算周围的细胞数
- let num = 0;
- if (i-1>=0 && j-1>=0 && cells[i-1][j-1]==1) num++;
- if (i-1>=0 && cells[i-1][j]==1) num++;
- if (i-1>=0 && cells[i-1][j+1]==1) num++;
- if (i+1
1][j]==1) num++; - if (i+1
1>=0 && cells[i+1][j-1]==1) num++; - if (i+1
11][j+1]==1) num++; - if (j-1>=0 && cells[i][j-1]==1) num++;
- if (j+1
1]==1) num++; - if (cells[i][j] == 0 && num >= 3) {
- tmp[i][j] = 1;
- } else if (num<=1 || num>4){
- tmp[i][j] = 0;
- }
- }
- }
- for(let i=0; i
- for(let j=0; j
- cells[i][j] = tmp[i][j];
- }
- }
- rounds++;
- }
代码
所有的代码都写在了一个html里面,没有任何其他依赖,复制后就能运行,不过需要联网,因为通过cdn的方式引入了vue+element-plus。
- html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width,initial-scale=1.0">
-
-
- <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/element-plus/2.3.3/index.css" rel="stylesheet">
-
- <script src="https://unpkg.com/vue@3">script>
-
- <script src="https://cdn.bootcdn.net/ajax/libs/element-plus/2.3.3/index.full.js">script>
-
- <title>hello worldtitle>
- <style lang="scss">
- #app {
- font-family: Avenir, Helvetica, Arial, sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- text-align: center;
- color: #2c3e50;
- margin: 0;
- padding: 0;
- }
- .gameCanvas {
- /* background-color: burlywood; */
- /* width: 60%; */
- height: 650px;
- }
- .infoDiv {
- /* background-color: darkcyan; */
- /* width: 40%; */
- height: 650px;
- }
- .el-text {
- font-size: middle;
- align: left;
- }
- style>
- head>
- <body>
- <div id="app">
- <el-container>
- <el-header>
- <el-text style="text-align: center;" :size="large">
- <h1>生命游戏h1>
- el-text>
- el-header>
- <el-row>
- <el-col :span="14" class="gameCanvas">
- <canvas id="gameCanvas" width="600" height="600" style="border: gray solid 5px">canvas>
- el-col>
- <el-col :span="10" class="infoDiv">
- <el-descriptions title="数据面板"
- :column="1"
- :size="large"
- :border="true">
- <el-descriptions-item label="存活的细胞数:">
- <el-text>
- <span id="span1">{{numSurvivors}} / {{numAll}}span>
- el-text>
- el-descriptions-item><br>
- <el-descriptions-item label="迭代轮次:">
- <el-text>
- <span id="span2">{{rounds}}span>
- el-text>
- el-descriptions-item>
- <el-descriptions-item label="当前状态:">
- <span id="span3">
- <el-text v-if="state==0">未开始迭代el-text>
- <el-text v-else-if="state==1">迭代进行中el-text>
- <el-text v-else>未知状态el-text>
- span>
- el-descriptions-item>
- <el-descriptions-item label="操作">
- <el-button type="primary" onclick="clickInitBtn()">初始化el-button>
- <el-button type="info" onclick="clickClearBtn()">清空面板el-button>
- <el-button type="success" onclick="clickStartBtn()">开始迭代el-button>
- <el-button type="danger" onclick="clickEndBtn()">结束迭代el-button>
- el-descriptions-item>
- el-descriptions>
- el-col>
- el-row>
- el-container>
-
- div>
-
-
-
- body>
-
- html>
-
-
- <script type="text/javascript">
- // 定义格子数以及每个格子的大小
- var cellWidth = 30;
- var cellHeight = 30;
- var cellSize = 20;
- // 定义一个二维数组存储每个格子的值
- var cells = new Array(cellHeight).fill(0).map(() => new Array(cellWidth).fill(0));
- var canvas;
- var ctx;
- var widht;
- var height;
-
- var numSurvivors = 0;
- var numAll = cellWidth*cellHeight;
- var state = 0;
- var rounds = 0;
-
- // 用于启动和停止定时任务
- var myInterval;
- // 迭代的间隔时间,单位是毫秒
- var time = 500;
-
- // 计算存活细胞数
- function calNumSurvivors() {
- numSurvivors = 0;
- for(let i=0; i
- for(let j=0; j
- if (cells[i][j] == 1) {
- numSurvivors++;
- }
- }
- }
- }
-
- // 随机初始化细胞
- function randomInitLife() {
- for(let i=0; i
- for(let j=0; j
- if (Math.random() < 0.2) {
- cells[i][j] = 1;
- }
- }
- }
- }
-
- // 清空面板中所有的细胞
- function clearAllCells() {
- cells = new Array(cellHeight).fill(0).map(() => new Array(cellWidth).fill(0));
- }
-
- // 绘制一帧图像
- function draw() {
- ctx.lineWidth = 2;
- ctx.strokeStyle = "gray";
- ctx.lineJoin = 'round';
- for(let i=0; i
- for(let j=0; j
- if (cells[i][j] === 1) {
- ctx.fillStyle = "LightSalmon";
- } else {
- ctx.fillStyle = "AliceBlue";
- }
- ctx.fillRect(i*cellSize, j*cellSize, cellSize, cellSize);
- ctx.strokeRect(i*cellSize, j*cellSize, cellSize, cellSize);
- }
- }
- }
-
- // 更新数据面板
- function updateInfo() {
- var span1 = document.getElementById("span1");
- var span2 = document.getElementById("span2");
- var span3 = document.getElementById("span3");
- span1.innerText = numSurvivors + " / " + numAll;
- span2.innerText = rounds;
- if (state == 0) {
- span3.innerText = "未开始迭代";
- } else if (state == 1) {
- span3.innerText = "迭代进行中";
- } else {
- span3.innerText = "未知状态";
- }
- }
-
- /**
- * 每个细胞在每一轮的状态都依赖于其邻居的数量。
- * 如果细胞的邻居数量少于一个,那么该细胞在下一次状态将死亡。
- * 如果细胞的邻居数量超过四个,那么该细胞在下一次状态将死亡。
- * 如果细胞的邻居数量为二或三个,那么该细胞下一次状态将稳定存活。
- * 如果某位置原无细胞存活,但该位置的邻居数量为三个,那么该位置将复活一细胞。
- *
- */
- function calIter() {
- var tmp = new Array(cellHeight).fill(0).map(() => new Array(cellWidth).fill(0));
- for(let i=0; i
- for(let j=0; j
- // 计算周围的细胞数
- let num = 0;
- if (i-1>=0 && j-1>=0 && cells[i-1][j-1]==1) num++;
- if (i-1>=0 && cells[i-1][j]==1) num++;
- if (i-1>=0 && cells[i-1][j+1]==1) num++;
- if (i+1
1][j]==1) num++; - if (i+1
1>=0 && cells[i+1][j-1]==1) num++; - if (i+1
11][j+1]==1) num++; - if (j-1>=0 && cells[i][j-1]==1) num++;
- if (j+1
1]==1) num++; - if (cells[i][j] == 0 && num >= 3) {
- tmp[i][j] = 1;
- } else if (num<=1 || num>4){
- tmp[i][j] = 0;
- }
- }
- }
- for(let i=0; i
- for(let j=0; j
- cells[i][j] = tmp[i][j];
- }
- }
- rounds++;
- }
-
- function run() {
- calIter();
- calNumSurvivors();
- draw();
- updateInfo();
- }
-
- function clickInitBtn(event) {
- console.log("点击了 [初始化] 按钮");
- if (state == 1) {
- alert("还在迭代呢!");
- }
- clearAllCells();
- randomInitLife();
- calNumSurvivors();
- draw();
- updateInfo();
- }
- function clickClearBtn(event) {
- console.log("点击了 [清空面板] 按钮");
- rounds = 0;
- clearAllCells();
- calNumSurvivors();
- draw();
- updateInfo();
- }
- function clickStartBtn(event) {
- console.log("点击了 [开始迭代] 按钮");
- if (state == 0) {
- state = 1;
- }
- myInterval = window.setInterval("run()", time);
- updateInfo();
- }
- function clickEndBtn(event) {
- console.log("点击了 [结束迭代] 按钮");
- if (state == 1) {
- state = 0;
- }
- clearInterval(myInterval);
- }
-
-
- const app = Vue.createApp({
- mounted() {
- canvas = document.getElementById("gameCanvas");
- ctx = canvas.getContext('2d');
- width = canvas.width;
- height = canvas.height;
- draw();
- },
- data() {
- return {
- numSurvivors : 0,
- numAll : cellWidth*cellHeight,
- state : 0,
- rounds : 0,
- }
- },
- methods() {
- }
- }).use(ElementPlus).mount('#app');
-
-
- console.log("初始化结束")
- script>
思路非常简单,就是首先定义一个棋盘,然后每次迭代都计算一下结果,再将结果绘制在画布中,其中灰色表示死细胞,橙色表示活细胞。
在写代码的过程中遇到了两个坑:
1. canvas不能使用css定义大小,否则画出来的图会扭曲
2. 这段代码中的按钮的点击事件如果写在 Vue.createApp 中的 methods 中的话会调用不到,有知道为什么的小伙伴可以给我留言
3. 在 Vue.createApp 中的 data 所返回的四个变量都是外部定义的全局变量(用var修饰的),我们在外部更新变量值的时候,页面不会自动渲染,所以我写了一个函数手动进行数据更新,这个原因我也不太懂,有知道的小伙伴可以给我留言,更新数据的代码如下
- function updateInfo() {
- var span1 = document.getElementById("span1");
- var span2 = document.getElementById("span2");
- var span3 = document.getElementById("span3");
- span1.innerText = numSurvivors + " / " + numAll;
- span2.innerText = rounds;
- if (state == 0) {
- span3.innerText = "未开始迭代";
- } else if (state == 1) {
- span3.innerText = "迭代进行中";
- } else {
- span3.innerText = "未知状态";
- }
- }
不过我觉得,像这种简单页面的数据渲染,也用不到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