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

news2024/11/19 0:21:14

前言

本文将介绍一个基于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<cellWidth; i++) {
            for(let j=0; j<cellHeight; 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<cellWidth && cells[i+1][j]==1) num++;
                if (i+1<cellWidth && j-1>=0 && cells[i+1][j-1]==1) num++;
                if (i+1<cellWidth && j+1<cellHeight && cells[i+1][j+1]==1) num++;
                if (j-1>=0 && cells[i][j-1]==1) num++;
                if (j+1<cellHeight && cells[i][j+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<cellWidth; i++) {
            for(let j=0; j<cellHeight; j++) {
                cells[i][j] = tmp[i][j];
            }
        }
        rounds++;
    }

代码

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

<!DOCTYPE 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">
    <!-- 引入vue3 -->
    <script src="https://unpkg.com/vue@3"></script>
    <!-- 引入element plus -->
    <script src="https://cdn.bootcdn.net/ajax/libs/element-plus/2.3.3/index.full.js"></script>
    
    <title>hello world</title>
    <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<height/cellSize; i++) {
            for(let j=0; j<width/cellSize; j++) {
                if (cells[i][j] == 1) {
                    numSurvivors++;
                }
            }
        }
    }

    // 随机初始化细胞
    function randomInitLife() {
        for(let i=0; i<height/cellSize; i++) {
            for(let j=0; j<width/cellSize; 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<height/cellSize; i++) {
            for(let j=0; j<width/cellSize; 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<cellWidth; i++) {
            for(let j=0; j<cellHeight; 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<cellWidth && cells[i+1][j]==1) num++;
                if (i+1<cellWidth && j-1>=0 && cells[i+1][j-1]==1) num++;
                if (i+1<cellWidth && j+1<cellHeight && cells[i+1][j+1]==1) num++;
                if (j-1>=0 && cells[i][j-1]==1) num++;
                if (j+1<cellHeight && cells[i][j+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<cellWidth; i++) {
            for(let j=0; j<cellHeight; 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游戏,不过如果要做游戏的话,可能需要自己实现一下逻辑,还是挺复杂的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1095365.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue-3.2自定义创建项目

基于VueCli自定义创建项目架子 选择第三个 空格选中&#xff0c;再空格取消 选择vue2 其实就是mode模式&#xff0c;之后再去修改就可以&#xff0c;history和hash 选择less 无分号规范&#xff08;标准化&#xff09;&#xff0c;目前最流行的 将配置文件放在单独的文件中 是否…

Linux环境配置安装Redis

Windows版本因官网不在提供与支持&#xff0c;以下基于linux环境安装 前提&#xff1a; 1.一台linux服务器 2.服务器已安装gcc 安装 1、官网下载 https://redis.io/download/ 对应压缩包 2、上传压缩包至服务器并解压缩 tar -zxvf redis-stable.tar.gz3、cd 至该目录下 4、…

双周总结#002 - 红树林

红树林公园&#xff0c;一棵单独生长在海岸边的树&#xff0c;下面一根根树立的幼苗&#xff0c;是从它的根茎上生长出来的。傍晚落潮后&#xff0c;会有一只只小螃蟹在这里浪荡。当然&#xff0c;也会有海鸟在这里进食。 文档 深入了解 Commonjs 和 Es Module1 Web 开发中&am…

两道关于顺序表的经典算法

文章目录 力扣&#xff1a;[移除元素](https://leetcode.cn/problems/remove-element/)[力扣&#xff1a;88. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) 力扣&#xff1a;移除元素 题目 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移…

JAVA实战项目 超市商品管理系统

师傅开发的实战项目&#xff0c;感觉不错&#xff0c;拿出来分享分享。 目录 一、摘要1.1 简介1.2 项目录屏 二、研究内容三、系统设计3.1 用例图3.2 时序图3.3 类图3.4 E-R图 四、系统实现4.1 登录4.2 注册4.3 主页4.4 超市区域管理4.5 超市货架管理4.6 商品类型管理4.7 超市商…

JDBC操作BLOB类型字段

JDBC中Statement接口本身不能直接操作BLOB数据类型 操作BLOB数据类型需要使用PreparedStatement或者CallableStatement(存储过程) 这里演示通过PreparedStatement操作数据库BLOB字段 设置最大传入字节 一般是4M 可以通过以下命令修改 set global max_allowed_packet1024*1…

C语言,洛谷题,赦免战俘

先上答案&#xff0c;再对答案进行解释&#xff1a; #include <stdio.h> int arr[1025][1025] { 0 }; void fun(int bian,int x ,int y) {if (bian 2)//进入if再出去if之后&#xff0c;结束递归&#xff0c;因为递归在else里面{arr[x][y] 0;}else{int i 0;int j 0;…

【Linux】:Linux中Shell命令及其运行原理/权限的理解

Shell命令以及运行原理 Linux严格意义上说的是一个操作系统&#xff0c;我们称之为“核心&#xff08;kernel&#xff09;“ &#xff0c;但我们一般用户&#xff0c;不能直接使用kernel 而是通过kernel的“外壳”程序&#xff0c;也就是所谓的shell&#xff0c;来与kernel沟通…

SpringCloud之Gateway整合Sentinel服务降级和限流

1.下载Sentinel.jar可以图形界面配置限流和降级规则 地址:可能需要翻墙 下载jar文件 2.引入maven依赖 <!-- spring cloud gateway整合sentinel的依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-s…

从基础到卷积神经网络(第14天)

1. PyTorch 神经网络基础 1.1 模型构造 1. 块和层 首先&#xff0c;回顾一下多层感知机 import torch from torch import nn from torch.nn import functional as Fnet nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))X torch.rand(2, 20) # 生成随机…

苍穹外卖(七) Spring Task 完成订单状态定时处理

Spring Task 完成订单状态定时处理, 如处理支付超时订单 Spring Task介绍 Spring Task 是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑。 应用场景: 信用卡每月还款提醒 火车票售票系统处理未支付订单 入职纪念日为用户发送通知 点外…

C++:多态讲解

多态 1.多态的概念2.多态的定义和实现2.1多态构成条件2.2虚函数2.3虚函数的重写(覆盖)2.4 C11 override 和 final2.5重载、重写(覆盖)、隐藏(重定义)的对比 3.抽象类4.多态的原理5.单继承和多继承关系的虚函数表5.1单继承5.2多继承5.3菱形继承和多态 1.多态的概念 多态的概念&…

【Vue面试题二十三】、你了解vue的diff算法吗?说说看

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;你了解vue的diff算法吗&…

MFC-对话框

目录 1、模态和非模态对话框&#xff1a; &#xff08;1&#xff09;、对话框的创建 &#xff08;2&#xff09;、更改默认的对话框名称 &#xff08;3&#xff09;、创建模态对话框 1&#xff09;、创建按钮跳转的界面 2&#xff09;、在跳转的窗口添加类 3&#xff0…

树莓派:64位 RPI OS(Bookworm) 更换国内源

几天前新的RPI OS发布了。官方的发版说明里明确注明已经基于Debian Bookworm了。总的来说切到国内源&#xff08;清华&#xff09;跟Bullseye差不多&#xff0c;细节上只有一丢丢不同&#xff08;non-free变成了non-free-firmware&#xff09;。 老规矩&#xff0c;仍然是修改…

二、深度测试(Z Test)

1.是什么 ①从渲染管线出发 ②书面上理解 所谓深度测试&#xff0c;就是针对当前对象在屏幕上&#xff08;更准确的说是frame buffer&#xff09;对应的像素点&#xff0c;讲对象自身的深度值与当前该像素点缓存的深度值进行比较&#xff0c;如果通过了&#xff0c;本对象再改…

七、三层交换机不同网段通信实验

拓扑图&#xff1a; 首先将所有端口ip配置完毕&#xff0c;之后对SW1三层交换机进行配置 创建Vlan10 20网段 进入g0/0/1物理端口&#xff0c;只允许vlan10通过 再进入到g0/0/2端口配置允许通过vlan20的数据包 之后进入vlan的虚拟接口去配置网关ip&#xff0c;一定要先配置物理…

vue:diff库实现文本对比

官方文档 https://www.npmjs.com/package/diff 安装&#xff1a;npm install diff 内容 <template><div><div style"white-space: pre-line;display: flex;"><div><span class"default">{{oldStr}}</span></div&…

游游的字母串 (环形数组两点之间的位置)

题目链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目&#xff1a; 样例&#xff1a; 输入 yab 输出 3 思路&#xff1a; 暴力枚举&#xff0c;全部变成对应的26个字母字符需要的操作步数&#xff0c;取最少的一个操作步数&#xff0c; 这里的操作步数&#xff0…

2021年12月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python编程&#xff08;1~6级&#xff09;全部真题・点这里 C/C编程&#xff08;1~8级&#xff09;全部真题・点这里 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 第1题 执行以下程序 a[33,55,22,77] a.sort() for i in a:print(i)运行…