20行代码能干嘛?

 在csdn上看到一位大神用20行代码就写出了一个贪吃蛇的小游戏,感觉被惊艳到了,就试着读了一下这段代码,阅读过程中不断为作者写法的巧妙而叫绝,其中我发现自己对运算符优先级和一些js的技巧不是很清楚,所以看完之后决定把思路分享出来,方便和我一样的小白学习。

<!DOCTYPE html>
<html lang="en">
<head>    <meta charset="UTF-8">    <title>贪吃蛇重构</title>    <style>
        body {            
            display: flex;            
            height: 100vh;            
            margin: 0;            
            padding: 0;            
            justify-content: center;            
            align-items: center;
        }    
    </style>
</head>
<body>    
    <canvas id="can" width="400" height="400" style="background-color: black">对不起,您的浏览器不支持canvas</canvas>    
    <script>
        var snake = [41, 40],
            //snake队列表示蛇身,初始节点存在但不显示
            direction = 1,      
            //1表示向右,-1表示向左,20表示向下,-20表示向上
            food = 43,          
            //食物的位置
            n,                  
            //与下次移动的位置有关
            box = document.getElementById('can').getContext('2d');                                    
            //从0到399表示box里[0~19]*[0~19]的所有节点,每20px一个节点
        function draw(seat, color) {
            box.fillStyle = color;
            box.fillRect(seat % 20 *20 + 1, ~~(seat / 20) * 20 + 1, 18, 18);                                    
            //用color填充一个矩形,以前两个参数为x,y坐标,后两个参数为宽和高。
        }        
        document.onkeydown = function(evt) {    
            //当键盘上下左右键摁下的时候改变direction
            direction = snake[1] - snake[0] == (n = [-1, -20, 1, 20][(evt || event).keyCode - 37] || direction) ? direction : n;
        };
       !function() {
            snake.unshift(n = snake[0] + direction);    
            //此时的n为下次蛇头出现的位置,n进入队列
            if(snake.indexOf(n, 1) > 0 || n < 0 || n > 399 || direction == 1 && n % 20 == 0 || direction == -1 && n % 20 == 19) {                                    //if语句判断贪吃蛇是否撞到自己或者墙壁,碰到时返回,结束程序
                return alert("GAME OVER!");
            }
            draw(n, "lime");    
            //画出蛇头下次出现的位置
            if(n == food) {     
            //如果吃到食物时,产生一个蛇身以外的随机的点,不会去掉蛇尾
                while (snake.indexOf(food = ~~(Math.random() * 400)) > 0);
                draw(food, "yellow");
            } else {            
            //没有吃到食物时正常移动,蛇尾出队列
                draw(snake.pop(),"black");
            }
            setTimeout(arguments.callee, 150);      
            //每隔0.15秒执行函数一次,可以调节蛇的速度
        }();    
    </script>
</body>
</html>

我对代码稍稍做了些修改
并添加了一些注释,方便理解

首先,我们要知道做一个贪吃蛇最主要的是什么,是做出蛇活动的场所和如何使蛇动起来。
我们先看蛇活动的场所:


<!-- html -->
<canvas id="can" width="400" height="400" style="background-color: black">    对不起,您的浏览器不支持canvas
</canvas>
<!-- js -->
box = document.getElementById('can').getContext('2d');
这是一个400px*400px的canvas,思路是以20px*20px为一个方格,组成20行20列的方阵,总共400格,然后绿色填充的格子表示蛇身,用黄色表示食物。这400个格子和数字0~399一一对应,对应的方式就是以20作为基数,n / 20再取整表示第几行,n % 20表示第几列。行数和列数都用0~19表示。


蛇用一个一维数组表示,每个值都是这400个数中的一个,用var snake = [41, 40];初始化这条蛇,索引0为蛇头。food表示食物的位置,direction表示蛇头下一次运动的转向。蛇的运动就用添加和删除数组元素来实现,每次执行绘制蛇头,去掉蛇尾,循环执行使蛇运动。


下边从函数运行的起始处开始看:


!function() {}();
什么鬼?这其实是立即执行函数IIFE的另一种写法。继续往下看,给蛇头添加一个节点n,其值为当前蛇头的值加direction的值,如此一来就能理解为什么要用20表示向下,-20表示向上了。


再下一行是一个if语句,其中值得提醒的是&&的优先级高于||,这个语句就是判断即将出现的蛇头是不是属于蛇身,或者跑到box外边去了。如果没有死亡,就把这个蛇头绘制出来,下边就看看绘制的代码:


function draw(seat, color) {
   box.fillStyle = color;
   box.fillRect(seat % 20 *20 + 1, ~~(seat / 20) * 20 + 1, 18, 18);
}
填充时填充18*18的像素,留1px边框。.fillRect()中第一个参数就是要绘制的矩形的x坐标seat % 20 *20 + 1,即先得到所要绘制的矩形块在方阵中的位置:第~~(seat / 20)行,第seat % 20列,再* 20 + 1具体到像素点。


可能这个~~有点难理解,我感觉在这里的用处应该和Math.floor()差不多,对一个浮点型的数取反再取反,得到的数就是去掉小数位的整数了。


回到前面,又是一个判断语句,判断下次蛇头出现的位置是不是和当前的食物的位置相同,如果相同,生成下一个食物,食物的位置为一个随机数,但是要判断这个点不是出现在当前的蛇身上,绘制食物。


如果没有吃到食物,即蛇在正常运动时,每向前一次,将蛇尾弹出,并利用其返回值将这个点重新绘制为黑色。
最后的setTimeout,循环执行当前函数,设置执行周期来调蛇的移动速度。


到了这里,我们发现这条蛇已经可以动了,加上键盘的操作就完成了:


document.onkeydown = function(evt) {    
   direction = snake[1] - snake[0] == (n = [-1, -20, 1, 20][(evt || event).keyCode - 37] || direction) ? direction : n;};
将这个函数绑定到键盘事件上,evt || event用法的原因这里有详细的解释,是为了兼容ie。


三目运算符?前边的判断语句又可分为两部分:


snake[1] - snake[0]的值应该就是-direction,按理说此处写成-direction应该和原来是一个效果,那为什么没有这么做呢,因为如果这样写,玩家可能在一个函数周期中多次改变direction的值,最后使得direction和当前真正的运动方向不一致,导致游戏崩溃。
在==后边,[-1, -20, 1, 20][(evt || event).keyCode - 37]中前边的[]是一个数组,后边的[]是取索引,左上右下四个键的keyCode分别为37, 38, 39, 40,计算后的索引为0, 1, 2, 3,使方向键与direction的取值对应起来。这里的巧妙之处在于如果按下的按键不是方向键,在数组中将得不到对应的值,返回undefine。此时,由于之后的||运算符,n会取到direction原来的值。
再用三目运算符来判断,如果按键方向不是反方向,就更新direction的值。

### DeepSeek 功能特性 DeepSeek 是一个综合性数据分析和搜索平台,旨在帮助用户在大规模数据集中快速找到所需的信息,并提供深度的分析能力。其核心功能包括: #### 智能搜索 - **全文搜索**:支持对各种格式的数据(如文本、PDF、Word 文档等)进全文搜索。用户可以输入关键词或短语,DeepSeek 会在所有数据集中查找匹配的内容[^2]。 #### 数据挖掘 - 提供强大的数据挖掘工具,能够从大量结构化和非结构化数据中提取有价值的信息,帮助企业出更明智的决策。 #### 可视化展示 - 支持多种图表形式的数据可视化,使复杂的统计数据变得直观易懂,便于用户理解和分享。 #### 机器学习支持 - 集成了先进的机器学习算法和技术,可以帮助用户构建预测模型并优化业务流程。 ### 使用场景 根据不同版本的核心特性和应用场景,DeepSeek 主要有以下几个使用方向: #### 实时对话与移动端应用 - **DeepSeek-Lite** 版本具有低延迟、高吞吐的特点,适合用于实时对话系统及移动设备上的轻量化应用程序开发[^4]。 ```python import deepseek_lite as dl # 初始化Lite版客户端 client = dl.Client() # 发送查询请求 response = client.query("你好世界") print(response.text) ``` #### 企业级客服与数据分析 - **DeepSeek-Pro** 版本具备良好的性能平衡,在多任务处理方面表现出色,适用于大型企业的客户服务和支持部门,同时也可用于深入的数据分析工作。 ```python from deepseek_pro import DataAnalyzer, CustomerServiceBot analyzer = DataAnalyzer() bot = CustomerServiceBot() # 执数据分析任务 results = analyzer.analyze_dataset('sales_data.csv') # 处理客户咨询 reply = bot.respond_to_customer_query("如何重置密码?") print(results.summary()) print(reply.message) ``` #### 科研探索与金融高频决策 - **DeepSeek-Max** 版本拥有超过70亿参数规模,擅长处理复杂的推理问题,特别适合科学研究领域以及需要高度精确性的金融市场交易策略制定等工作。 ```python from deepseek_max import ResearchAssistant, FinancialAdvisor researcher = ResearchAssistant() advisor = FinancialAdvisor() # 协助科研项目 insights = researcher.explore_topic("量子计算进展") # 制定投资建议 advice = advisor.generate_advice(portfolio='tech_stocks') print(insights.key_findings) print(advice.recommendations) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值