用TypeScript完成的贪吃蛇小游戏

news2024/11/27 18:41:31

在这里插入图片描述

食物类Fod

// 定义
class Food {
    // 定义一个属性表示食物所对应的元素
    element:HTMLElement;
    constructor(){
        //加个!表示不能为空,非空断言操作符 
        //获取页面中的food元素并将其赋值给element 
        this.element=document.getElementById('food')!;
    }
    // 定义一个获取食物x轴坐标的方法
    get X(){
        return this.element.offsetLeft
    }                                                    
    //定义一个获取食物Y轴坐标的方法
    get Y(){
        return this.element.offsetTop
    }
    //改变食物的位置
    change(){
        //生成一个随机的位置
        //食物的位置最小是0,最大时290
        //蛇移动一次就是一格,一格的大小就是10,所以就要求食物的大小必须是整10
        // Math.random()*290  0-290 不包括0,也不包括290;  Math.round(Math.random()*290) 四舍五入包括0也包括290,0到290的整数  不行
        //包括0,包括29
        let top=Math.round(Math.random()*29)*10
        let left=Math.round(Math.random()*29)*10
        this.element.style.left=left+'px'
        this.element.style.top=top+'px'
    }
}
export default Food;

游戏控制器,,控制其他的所有类

//引入其他的类
import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";
//游戏控制器,控制其他的所有类
class GameControl {
    snake: Snake;
    food: Food;
    scorePanel: ScorePanel;
    //创建一个属性来存储蛇的移动方向(也就是按键的方向),默认值是空串
    direction: string = '';
    //创建一个属性用来记录游戏是否结束,false代表游戏结束
    isLive = true;
    constructor() {
        this.snake = new Snake();
        this.food = new Food();
        this.scorePanel = new ScorePanel(10,2);
        this.init();
    }
    //游戏的初始化方法,调用后游戏即开始
    init() {
        //绑定键盘按键按下的事件
        document.addEventListener('keydown', this.keydownHandler.bind(this))
        //调用run方法,使蛇移动
        this.run();
    }

    //创建一个键盘按下的响应函数  //  ArrowUp ArrowDown ArrowLeft ArrowRight  
    keydownHandler(event: KeyboardEvent) {
        //需要检查event.key的值是否合法(用户是否按了正确的按钮,也就是方向键)
        //修改direction属性
        this.direction = event.key
    }
    //创建一个控制蛇移动的方法
    run() {
        //根据方向(this.direction)来使蛇的位置改变 向上 top 减少 向下top 增加 向左left 减少 向右left 增加
        //获取蛇现在的坐标
        let X = this.snake.X;
        let Y = this.snake.Y;
        // ArrowUp Up ArrowDown Down ArrowLeft Left ArrowRight Right 
        //根据按键的方向修改X值和Y值
        switch (this.direction) {
            case "ArrowUp":
            case "up":
                //向上移动
                Y -= 10;
                break;
            case "ArrowDown":
            case "Down":
                //向下移动 top值增加
                Y += 10;
                break;
            case "ArrowLeft":
            case "Left":
                //向左移动 left 值减少
                X -= 10
                break;
            case "ArrowRight":
            case "Right":
                //向右移动 left增加
                X += 10;
                break;
        }
        //检查蛇是否吃到了食物
        this.checkEat(X, Y);
        //修改蛇的X和Y值
        try {
            this.snake.X = X;
            this.snake.Y = Y;
        } catch (e) {
            //进入到catch,说明出现了异常,游戏结束,弹出了一个提示信息
            alert(e.message + 'GAME OVER!')
            //将isLive设置为false
            this.isLive = false
        }

        //开启一个定时调用,每隔300毫秒去调用一下,随着等级的提高速度变慢;当isLive是true的时候开启定时器
        this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30)
    }
    //定义一个方法检查蛇是否吃到了食物
    checkEat(X: number, Y: number) {
        if (X === this.food.X && Y === this.food.Y) {
            //食物的位置要进行重置
            this.food.change()
            //分数增加
            this.scorePanel.addScore();
            //蛇要增加一节
            this.snake.addBody();
        }

    }
}
export default GameControl;

记分牌的类

//定义表示记分牌的类
class ScorePanel{
    //score和level用来代表分数和等级
    score=0;
    level=1;
    //分数和等级所在的元素,在构造函数中进行初始化
    scoreEle:HTMLElement;
    levelEle:HTMLElement;

    //设置一个变量限制等级
    maxLevel:number;
    //设置一个变量表示多少分时升级
    upScore:number;

    //传参100就使用100,不传参就使用默认值是10
    constructor(maxLevel:number=10, upScore:number=10){
        //! 代表不可能为空
        this.scoreEle=document.getElementById('score')!
        this.levelEle=document.getElementById('level')!
        this.maxLevel=maxLevel;
        this.upScore=upScore;
    }
    //设置一个加分的方法
    addScore(){
        //使分数自增
        this.score++;
        this.scoreEle.innerHTML=this.score+''
        //判断分数是多少
        if(this.score%this.upScore===0){
           this.levelUp()
        }
    }
    //提升等级的方法
    levelUp(){
        if (this.level< this.maxLevel){
         this.levelEle.innerHTML=++this.level+''
        }
    
    }
}
// const scorePanel=new ScorePanel()

// for(let i=0;i<200;i++){
//     scorePanel.addScore()
// }
export default ScorePanel;

class Snake {
    //表示蛇头的元素
    head: HTMLElement;
    //蛇的身体(包括蛇头)
    bodies: HTMLCollection;
    //获取蛇的容器
    element: HTMLElement;
    constructor() {
        this.element = document.getElementById('snake')
        // querySelector 匹配指定 CSS 选择器的第一个元素。
        this.head = document.querySelector('#snake>div') as HTMLElement;
        //	document.querySelectorAll() 是 HTML5中引入的新方法,返回文档中匹配的CSS选择器的所有元素节点列表 是静态的
        this.bodies = this.element!.getElementsByTagName('div')
    }
    //获取蛇的坐标(蛇头坐标)
    get X() {
        return this.head.offsetLeft;
    }
    //获取蛇的Y轴坐标
    get Y() {
        return this.head.offsetTop;
    }
    //设置蛇头的坐标
    set X(value: number) {
        //如果新值和旧值相同,则直接返回不再修改
        if (this.X === value) {
            return
        }
        //X值的合法范围 0-290之间
        if (value < 0 || value > 290) {
            //进入判断说明蛇撞墙了
            throw new Error('蛇撞墙了!')
        }
        //修改X时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
            console.log('水平方向发生了掉头')
            //如果发生了掉头,让蛇向反方向继续移动
            if (value > this.X) {
                //如果新值value大于旧值X,则说明蛇在向右走,此时发生掉头,应该使蛇继续向左走
                value = this.X - 10;
            } else {
                value = this.X + 10
            }
        }
        //移动身体
        this.moveBody();
        this.head.style.left = value + 'px'
        //检查有没有撞到自己
        this.checkHeadBody();
    }
    set Y(value: number) {
        //如果新值和旧值相同,则直接返回不再修改
        if (this.Y === value) {
            return
        }
        if (value < 0 || value > 290) {
            //进入判断说明蛇撞墙了
            throw new Error('蛇撞墙了!')
        }
        //修改y时,是在修改垂直坐标,蛇在上下移动,蛇在向上移动时,不能向下掉头,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {

            //如果发生了掉头,让蛇向反方向继续移动
            if (value > this.Y) {
                //如果新值value大于旧值X,则说明蛇在向上走,此时发生掉头,应该使蛇继续向下走
                value = this.Y - 10;
            } else {
                value = this.Y + 10
            }
        }


        //移动身体
        this.moveBody();
        this.head.style.top = value + 'px'
         //检查有没有撞到自己
         this.checkHeadBody();
    }
    //蛇增加身体的方法
    addBody() {
        this.element.insertAdjacentHTML('beforeend', '<div></div>')
    }
    //添加一个蛇身体移动的方法
    moveBody() {
        // 将后边的身体设置为前边身体的位置 举例子 第4节=第3节的位置  第3节等于第2节的位置 第2节等于蛇头的位置
        // 遍历获取所有的身体
        for (let i = this.bodies.length - 1; i > 0; i--) {
            //获取前边身体的位置
            let X = (this.bodies[i - 1] as HTMLElement).offsetLeft;
            let Y = (this.bodies[i - 1] as HTMLElement).offsetTop;
            console.log(this.bodies[i]);
            //将值设置到当前身体上
            (this.bodies[i] as HTMLElement).style.left = X + 'px';
            (this.bodies[i] as HTMLElement).style.top = Y + 'px';
        }
    }
    //检查舌头是否撞到身体的方法
    checkHeadBody(){
        //获取所有的身体,检查其是否和蛇头的坐标发生重叠
        for(let i=1;i<this.bodies.length;i++){
            let bd=this.bodies[i] as HTMLElement;
            if(this.X===bd.offsetLeft&&this.Y===bd.offsetTop){
                //进入判断说明蛇头撞到了身体,游戏结束
                throw new Error('撞到自己了!')
            }
        }
    }



}
export default Snake;

样式


@bg-color:#b7d4a8;
// 清除默认的样式
*{
   margin:0;
   padding:0;
   //改变盒子模型的计算方式
   box-sizing: border-box;
}
body{
    font:bold 20px "Courier";
    //禁用滚动条
    overflow-x: hidden;
    overflow-y: hidden;
}
//设置主窗口的样式
#main{
    width:360px;
    height:420px;
    //设置背景颜色
    background-color:@bg-color ;
    //设置居中
    margin:100px auto;
    border:10px solid  black;
    border-radius:40px;
    //开启弹性盒模型
    display: flex;
    flex-flow: column;
    align-items: center;
    justify-content: space-around;
}
//游戏舞台
#stage{
    width:304px;
    height:304px;
    border:2px solid black;
    position: relative;
    //设置蛇的样式
    #snake{
      &>div{
         width:10px;
         height:10px;
         background-color: #000; 
       border: 1px solid @bg-color;
       position: absolute;
      }  
     
    }
    &>#food{
        width:10px;
        height:10px;
        position: absolute;
        left:40px;
        top:100px;
       //开启弹性盒
        display:flex;
        //设置横轴为主轴,wrap表示会自动换行
        flex-flow:row wrap;
        //设置主轴和侧轴的空白空间分配到元素之间
        justify-content: space-between;
        align-content: space-around;
        div{
            width:4px;
            height:4px;
            background-color: black;
            //使四个div 旋转45度
            transform: rotate(45deg);
           
        }
    }
}
//记分牌
#score-panel{
    width:300px;
    display:flex;
    //设置株洲的对齐方式
    justify-content: space-between;
}

html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>贪吃蛇</title>

</head>

<body>
    <!-- 创建游戏的主窗口 -->
    <div id="main">
        <!-- 设置游戏的舞台 -->
        <div id="stage">
            <!-- 设置蛇 -->
            <div id="snake">
                <!-- snake内部的div表示蛇的各部分 -->
                <div></div>

            </div>
            <!-- 设置食物 -->
            <div id="food">
                <!-- 添加4个小div来设置食物的样式 -->
                <div></div>
                <div></div>
                <div></div>
                <div></div>
            </div>
           

        </div>
         <!-- 设置游戏的积分牌 -->
        <div id="score-panel">
            <div>
                SCORE:<span id="score">0</span>
            </div>
            <div>
                level:<span id="level">1</span>
            </div>


        </div>
    </div>
</body>

</html>

index.ts

//引入样式
import './style/index.less'
import GameControl from './modules/GameControl'
const gc=new GameControl();

在这里插入图片描述

尚硅谷TypeScript教程(李立超老师TS新课) https://www.bilibili.com/video/BV1Xy4y1v7S2?p=1&vd_source=c2279a533bc837e2d5b9ca3570c0a841

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

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

相关文章

【C++】c++语法基础

引入&#xff0c;第一个c程序 这是用c写的helloworld程序 #include<iostream> using namespace std; int main() {cout << "hello,world\n" << endl;return 0;} 接下来我们将根据上述的代码来学习c的基本语法。 命名空间&#xff08;namespace…

PHP:连接钉钉接口-钉钉回调事件,本地测试数据

前置数据参考 数据说明:参见官方文档回调事件消息体加解密 - 钉钉开放平台 (dingtalk.com) URL后面带的参数: signature=5a65ceeef9aab2d149439f82dc191dd6c5cbe2c0&timestamp=1445827045067&nonce=nEXhMP4r Post参数: { "encrypt":"1a3NB…

日常开发记录分享——C#控件ToolTip实现分栏显示内容

文章目录 需求来源实现思路实施请看VCR等等别走&#xff0c;有优化 需求来源 需要在鼠标浮动到指定位置后提示出详细的信息&#xff0c;一开始使用的tooltip实现&#xff0c;但是里面的内容效果并不理想&#xff0c;需要有条理性&#xff0c;于是就想到能不能将展示的东西分列…

邮件推送API如何集成到现有系统发送邮件?

邮件推送API安全性策略&#xff1f;如何选择邮件推送API服务商&#xff1f; 在当今数字化时代&#xff0c;邮件通信是企业和个人交流的重要方式之一。集成邮件推送API到现有系统可以大大提升通信效率和自动化程度。AokSend将介绍如何将邮件推送API集成到现有系统中&#xff0c…

关于P2P(点对点)

P2P 是一种客户端与客户端之间&#xff0c;点对点连接的技术&#xff0c;在早前的客户端都是公网IP&#xff0c;没有NAT的情况下&#xff0c;P2P是较为容易实现的。 但现在的P2P&#xff0c;实现上面会略微有一些复杂&#xff1a;需要采取UDP打洞的技术&#xff0c;但UDP打出来…

自动控制: 时间最优的PID控制算法

自动控制&#xff1a; 时间最优的PID控制算法 在计算机控制系统中&#xff0c;时间最优控制旨在使系统从一个初始状态转到另一个目标状态所经历的过渡时间最短。利用最大值原理&#xff0c;可以设计出控制量只在 u ( t ) ≤ 1 u(t) \leq 1 u(t)≤1范围内取值的时间最优控制系…

(39)智能电池

文章目录 前言 1 通过任务规划器进行设置 2 补充信息 3 限制条件 4 参数说明 前言 虽然还不是很普遍&#xff0c;但智能电池更容易从飞行器上安装和拆卸&#xff0c;并且能够提供更多关于电池状态的信息&#xff0c;包括容量、单个电池电压、温度等。 ArduPilot 支持几种…

【分布式系统】 单机架构 | 分布式架构 | 集群 | 主从架构 | 分库分表 | 冷热分离 | 微服务

文章目录 [toc] 分布式系统一、单机架构二、分布式系统三、应用服务器集群四、读写分离 / 主从分离架构五、引入缓存/冷热分离架构六、垂直分库七、微服务架构——业务拆分代价优势 八、名词解释1.应用&#xff08;Application&#xff09;/系统(System)2.模块&#xff08;Mode…

解决“QtCreator无法呼出搜狗输入法“问题

由于在Ubuntu系统上&#xff0c;QtCreator软件默认使用IBus类型的输入法&#xff0c;而搜狗输入法是fcitx类型的&#xff0c;所以需要在Linux的系统设置 -->区域与语言 里 -->勾选 fcitx类型&#xff0c;如图(1)所示。     这里以QtCreator 4.5.2Ubuntu 18为例&#xf…

学习测试14-实战3-复习-使用CANoe打开半成品

数据 链接: https://pan.baidu.com/s/1k0SFq0luDvEbqimFgtfyKg?pwd9a5t 提取码: 9a5t 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 1&#xff0c;导入信号、报文、节点 2&#xff0c;导入数据库 3&#xff0c;导入can代码 4&#xff0c;导入环境变量 5&#x…

RTP协议基础

概述 1. 基本概念 RTP协议&#xff0c;全称为Real-time Transport Protocol&#xff08;实时传输协议&#xff09;是一种用于在IP网络上传输音频、视频等实时数据的网络协议。 在流媒体&#xff08;流媒体就是指可在线/实时观看音视频的互联网产品&#xff09;数据传输过程中&…

抄作业-跟着《React通关秘籍》捣鼓React-playground-上集

文章目录 前言1. 搭建react 开发环境2、react hooks 知识3. 目标&#xff1a;跟着小册实现 react-playground3.1 整体布局初始化项目使用Alloment 来实现左右分屏的拖拉功能 3.2 代码编辑器Monaco Editor 3.3 实现了多文件的切换用 useContext 来共享数据。优化 tab的样式&…

Vue响应式的原理

一. Vue响应式原理的核心概念 1. Vue响应式原理基于以下核心概念&#xff1a; ① 响应式对象&#xff1a;Vue使用Object.defineProperty()来 reactive&#xff08;反应&#xff09;对象中的属性&#xff0c;使其变化可以被检测。 注意&#xff1a; ★ Object.definePropert…

Python字符串处理技巧:一个小技巧竟然能省下你一半时间!

获取Pyhon及副业知识&#xff0c;关注公众号【软件测试圈】 效率翻倍的秘密&#xff1a;Python字符串操作的5个惊人技巧 在Python编程中&#xff0c;字符串处理在数据分析、Web开发、自动化脚本等多个领域都有广泛应用。Python提供了一系列强大的字符串处理函数&#xff0c;能够…

蚓链数字化生态平台:构建城市智能商业,引领协同发展新潮流

​在当今数字化飞速发展的时代&#xff0c;城市商业的运行模式正在经历着数字化变革。蚓链数字化生态平台应运而生&#xff0c;以其强大的功能和创新的理念&#xff0c;成为构建城市智能商业枢纽中心的关键力量&#xff0c;推动着平台互通、业务贯通、管理协同的全新发展格局。…

MySQL数据库-索引和视图

一、视图 1.什么是视图 MySQL中的视图&#xff08;view&#xff09;是一种虚拟表&#xff0c;其内容由查询定义&#xff0c;视图本身并不包含数据。视图看起来和真实的表完全相同&#xff0c;但其中的数据来自定义视图时用到的基本表&#xff0c;并且在打开视图时动态生成&am…

【JavaWeb项目】——外卖订餐系统之登入、登入后显示餐品信息、用户注册、注销部分

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大二学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门专栏&#xff1a;&#x1f387;【MySQL&#xff0…

什么是内网ip地址?如何查询电脑内网ip地址

在数字化时代&#xff0c;互联网已经成为我们日常生活和工作中不可或缺的一部分。无论是家庭网络还是企业办公环境&#xff0c;每台接入网络的设备都需要一个独特的标识来区分彼此&#xff0c;这个标识就是IP地址。IP地址全称为“互联网协议地址”&#xff0c;是设备在网络中的…

springboot整合junit-用于测试用例

package impl;public interface BookDao {public void save(); }第一步&#xff1a;打开软件&#xff0c;点击file&#xff0c;点击new 然后选择module&#xff0c;在右侧选择springboot 第二步&#xff1a;选择配置和JDK以及java版本 ①选择maven类型 ②选择JDK1.8版本 ③选…

react中路由懒加载

// 1.引入方法&#xff0c;用于创建路由实例 // createBrowserRouter是用于创建history模式 // createHashRouter是用于创建hash模式 // 路由模式的切换只需要更改创建路由实例的方法就行了&#xff0c;其他地方不需要更改 import { createBrowserRouter,createHashRouter } fr…